diff --git a/src/providers/completion-item/version.ts b/src/providers/completion-item/version.ts index 19cff1f..06e415a 100644 --- a/src/providers/completion-item/version.ts +++ b/src/providers/completion-item/version.ts @@ -2,7 +2,7 @@ import type { Extractor } from '#types/extractor' import type { CompletionItemProvider, Position, TextDocument } from 'vscode' import { config } from '#state' import { getPackageInfo } from '#utils/api/package' -import { formatVersion, parseVersion } from '#utils/package' +import { formatVersion, isSupportedProtocol, parseVersion } from '#utils/package' import { CompletionItem, CompletionItemKind } from 'vscode' export class VersionCompletionItemProvider implements CompletionItemProvider { @@ -29,7 +29,7 @@ export class VersionCompletionItemProvider implements Compl } = info const parsed = parseVersion(version) - if (!parsed) + if (!parsed || !isSupportedProtocol(parsed.protocol)) return const pkg = await getPackageInfo(name) diff --git a/src/providers/diagnostics/rules/deprecation.ts b/src/providers/diagnostics/rules/deprecation.ts index 70bfe90..a0b6a6a 100644 --- a/src/providers/diagnostics/rules/deprecation.ts +++ b/src/providers/diagnostics/rules/deprecation.ts @@ -1,11 +1,11 @@ import type { DiagnosticRule } from '..' import { npmxPackageUrl } from '#utils/links' -import { parseVersion } from '#utils/package' +import { isSupportedProtocol, parseVersion } from '#utils/package' import { DiagnosticSeverity, DiagnosticTag, Uri } from 'vscode' export const checkDeprecation: DiagnosticRule = (dep, pkg) => { const parsed = parseVersion(dep.version) - if (!parsed) + if (!parsed || !isSupportedProtocol(parsed.protocol)) return const { semver } = parsed diff --git a/src/providers/diagnostics/rules/vulnerability.ts b/src/providers/diagnostics/rules/vulnerability.ts index e89f67c..01cbc08 100644 --- a/src/providers/diagnostics/rules/vulnerability.ts +++ b/src/providers/diagnostics/rules/vulnerability.ts @@ -2,7 +2,7 @@ import type { OsvSeverityLevel } from '#utils/api/vulnerability' import type { DiagnosticRule } from '..' import { getVulnerability, SEVERITY_LEVELS } from '#utils/api/vulnerability' import { npmxPackageUrl } from '#utils/links' -import { parseVersion } from '#utils/package' +import { isSupportedProtocol, parseVersion } from '#utils/package' import { DiagnosticSeverity, Uri } from 'vscode' const DIAGNOSTIC_MAPPING: Record, DiagnosticSeverity> = { @@ -14,7 +14,7 @@ const DIAGNOSTIC_MAPPING: Record, Diagnosti export const checkVulnerability: DiagnosticRule = async (dep, pkg) => { const parsed = parseVersion(dep.version) - if (!parsed) + if (!parsed || !isSupportedProtocol(parsed.protocol)) return const { semver } = parsed diff --git a/src/providers/hover/npmx.ts b/src/providers/hover/npmx.ts index ab18522..afd32d2 100644 --- a/src/providers/hover/npmx.ts +++ b/src/providers/hover/npmx.ts @@ -3,7 +3,7 @@ import type { HoverProvider, Position, TextDocument } from 'vscode' import { SPACER } from '#constants' import { getPackageInfo } from '#utils/api/package' import { npmPacakgeUrl, npmxDocsUrl, npmxPackageUrl } from '#utils/links' -import { parseVersion } from '#utils/package' +import { isSupportedProtocol, parseVersion } from '#utils/package' import { Hover, MarkdownString } from 'vscode' export class NpmxHoverProvider implements HoverProvider { @@ -24,7 +24,7 @@ export class NpmxHoverProvider implements HoverProvider { return const parsed = parseVersion(dep.version) - if (!parsed) + if (!parsed || !isSupportedProtocol(parsed.protocol)) return const { name } = dep diff --git a/src/utils/package.ts b/src/utils/package.ts index 8043e05..487798e 100644 --- a/src/utils/package.ts +++ b/src/utils/package.ts @@ -9,13 +9,11 @@ export function encodePackageName(name: string): string { return encodeURIComponent(name) } -const WORKSPACE_PREFIX = 'workspace:' -const CATALOG_PREFIX = 'catalog:' -const NPM_PREFIX = 'npm:' -const JSR_PREFIX = 'jsr:' -const URL_PREFIXES = ['http://', 'https://', 'git://', 'git+'] +export type VersionProtocol = 'workspace' | 'catalog' | 'npm' | 'jsr' | null -export type VersionProtocol = 'npm' | null +const KNOWN_PROTOCOLS = new Set(['workspace', 'catalog', 'npm', 'jsr']) +const URL_PREFIXES = ['http://', 'https://', 'git://', 'git+'] +const UNSUPPORTED_PROTOCOLS = new Set(['workspace', 'catalog', 'jsr']) export interface ParsedVersion { protocol: VersionProtocol @@ -23,31 +21,33 @@ export interface ParsedVersion { semver: string } +export function isSupportedProtocol(protocol: VersionProtocol): boolean { + return !UNSUPPORTED_PROTOCOLS.has(protocol) +} + export function formatVersion(parsed: ParsedVersion): string { const protocol = parsed.protocol ? `${parsed.protocol}:` : '' return `${protocol}${parsed.prefix}${parsed.semver}` } export function parseVersion(rawVersion: string): ParsedVersion | null { - // Skip special protocols that aren't standard npm versions - if ( - [ - WORKSPACE_PREFIX, - CATALOG_PREFIX, - JSR_PREFIX, - ...URL_PREFIXES, - ].some((p) => rawVersion.startsWith(p)) - ) { + rawVersion = rawVersion.trim() + // Skip URL-based versions + if (URL_PREFIXES.some((p) => rawVersion.startsWith(p))) return null - } let protocol: VersionProtocol = null let versionStr = rawVersion - // Handle npm: protocol (e.g., npm:^1.0.0) - if (rawVersion.startsWith(NPM_PREFIX)) { - protocol = 'npm' - versionStr = rawVersion.slice(4 /* NPM_PREFIX.length */) + // Parse protocol if present (e.g., npm:^1.0.0 -> protocol: 'npm') + const colonIndex = rawVersion.indexOf(':') + if (colonIndex !== -1) { + protocol = rawVersion.slice(0, colonIndex) as VersionProtocol + + if (!KNOWN_PROTOCOLS.has(protocol)) + return null + + versionStr = rawVersion.slice(colonIndex + 1) } const firstChar = versionStr[0] diff --git a/tests/package.test.ts b/tests/package.test.ts index 3f3895e..7eb5ecd 100644 --- a/tests/package.test.ts +++ b/tests/package.test.ts @@ -52,15 +52,33 @@ describe('parseVersion', () => { }) }) - it('should return null for workspace:', () => { - expect(parseVersion('workspace:*')).toBeNull() + it('should parse workspace: protocol', () => { + expect(parseVersion('workspace:*')).toEqual({ + protocol: 'workspace', + prefix: '', + semver: '*', + }) + }) + + it('should parse catalog: protocol', () => { + expect(parseVersion('catalog:default')).toEqual({ + protocol: 'catalog', + prefix: '', + semver: 'default', + }) }) - it('should return null for catalog:', () => { - expect(parseVersion('catalog:default')).toBeNull() + it('should parse jsr: protocol', () => { + expect(parseVersion('jsr:^1.1.4')).toEqual({ + protocol: 'jsr', + prefix: '^', + semver: '1.1.4', + }) }) - it('should return null for jsr:', () => { - expect(parseVersion('jsr:@std/fs')).toBeNull() + it('should return null for URL-based versions', () => { + expect(parseVersion('https://github.com/user/repo')).toBeNull() + expect(parseVersion('git://github.com/user/repo')).toBeNull() + expect(parseVersion('git+https://github.com/user/repo')).toBeNull() }) })