Skip to content

Commit 329c2a0

Browse files
authored
refactor: enhance version parsing with protocol support and validation (#30)
1 parent da17a10 commit 329c2a0

6 files changed

Lines changed: 52 additions & 34 deletions

File tree

src/providers/completion-item/version.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { Extractor } from '#types/extractor'
22
import type { CompletionItemProvider, Position, TextDocument } from 'vscode'
33
import { config } from '#state'
44
import { getPackageInfo } from '#utils/api/package'
5-
import { formatVersion, parseVersion } from '#utils/package'
5+
import { formatVersion, isSupportedProtocol, parseVersion } from '#utils/package'
66
import { CompletionItem, CompletionItemKind } from 'vscode'
77

88
export class VersionCompletionItemProvider<T extends Extractor> implements CompletionItemProvider {
@@ -29,7 +29,7 @@ export class VersionCompletionItemProvider<T extends Extractor> implements Compl
2929
} = info
3030

3131
const parsed = parseVersion(version)
32-
if (!parsed)
32+
if (!parsed || !isSupportedProtocol(parsed.protocol))
3333
return
3434

3535
const pkg = await getPackageInfo(name)

src/providers/diagnostics/rules/deprecation.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import type { DiagnosticRule } from '..'
22
import { npmxPackageUrl } from '#utils/links'
3-
import { parseVersion } from '#utils/package'
3+
import { isSupportedProtocol, parseVersion } from '#utils/package'
44
import { DiagnosticSeverity, DiagnosticTag, Uri } from 'vscode'
55

66
export const checkDeprecation: DiagnosticRule = (dep, pkg) => {
77
const parsed = parseVersion(dep.version)
8-
if (!parsed)
8+
if (!parsed || !isSupportedProtocol(parsed.protocol))
99
return
1010

1111
const { semver } = parsed

src/providers/diagnostics/rules/vulnerability.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { OsvSeverityLevel } from '#utils/api/vulnerability'
22
import type { DiagnosticRule } from '..'
33
import { getVulnerability, SEVERITY_LEVELS } from '#utils/api/vulnerability'
44
import { npmxPackageUrl } from '#utils/links'
5-
import { parseVersion } from '#utils/package'
5+
import { isSupportedProtocol, parseVersion } from '#utils/package'
66
import { DiagnosticSeverity, Uri } from 'vscode'
77

88
const DIAGNOSTIC_MAPPING: Record<Exclude<OsvSeverityLevel, 'unknown'>, DiagnosticSeverity> = {
@@ -14,7 +14,7 @@ const DIAGNOSTIC_MAPPING: Record<Exclude<OsvSeverityLevel, 'unknown'>, Diagnosti
1414

1515
export const checkVulnerability: DiagnosticRule = async (dep, pkg) => {
1616
const parsed = parseVersion(dep.version)
17-
if (!parsed)
17+
if (!parsed || !isSupportedProtocol(parsed.protocol))
1818
return
1919

2020
const { semver } = parsed

src/providers/hover/npmx.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { HoverProvider, Position, TextDocument } from 'vscode'
33
import { SPACER } from '#constants'
44
import { getPackageInfo } from '#utils/api/package'
55
import { npmPacakgeUrl, npmxDocsUrl, npmxPackageUrl } from '#utils/links'
6-
import { parseVersion } from '#utils/package'
6+
import { isSupportedProtocol, parseVersion } from '#utils/package'
77
import { Hover, MarkdownString } from 'vscode'
88

99
export class NpmxHoverProvider<T extends Extractor> implements HoverProvider {
@@ -24,7 +24,7 @@ export class NpmxHoverProvider<T extends Extractor> implements HoverProvider {
2424
return
2525

2626
const parsed = parseVersion(dep.version)
27-
if (!parsed)
27+
if (!parsed || !isSupportedProtocol(parsed.protocol))
2828
return
2929

3030
const { name } = dep

src/utils/package.ts

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,45 +9,45 @@ export function encodePackageName(name: string): string {
99
return encodeURIComponent(name)
1010
}
1111

12-
const WORKSPACE_PREFIX = 'workspace:'
13-
const CATALOG_PREFIX = 'catalog:'
14-
const NPM_PREFIX = 'npm:'
15-
const JSR_PREFIX = 'jsr:'
16-
const URL_PREFIXES = ['http://', 'https://', 'git://', 'git+']
12+
export type VersionProtocol = 'workspace' | 'catalog' | 'npm' | 'jsr' | null
1713

18-
export type VersionProtocol = 'npm' | null
14+
const KNOWN_PROTOCOLS = new Set<VersionProtocol>(['workspace', 'catalog', 'npm', 'jsr'])
15+
const URL_PREFIXES = ['http://', 'https://', 'git://', 'git+']
16+
const UNSUPPORTED_PROTOCOLS = new Set<VersionProtocol>(['workspace', 'catalog', 'jsr'])
1917

2018
export interface ParsedVersion {
2119
protocol: VersionProtocol
2220
prefix: '' | '^' | '~'
2321
semver: string
2422
}
2523

24+
export function isSupportedProtocol(protocol: VersionProtocol): boolean {
25+
return !UNSUPPORTED_PROTOCOLS.has(protocol)
26+
}
27+
2628
export function formatVersion(parsed: ParsedVersion): string {
2729
const protocol = parsed.protocol ? `${parsed.protocol}:` : ''
2830
return `${protocol}${parsed.prefix}${parsed.semver}`
2931
}
3032

3133
export function parseVersion(rawVersion: string): ParsedVersion | null {
32-
// Skip special protocols that aren't standard npm versions
33-
if (
34-
[
35-
WORKSPACE_PREFIX,
36-
CATALOG_PREFIX,
37-
JSR_PREFIX,
38-
...URL_PREFIXES,
39-
].some((p) => rawVersion.startsWith(p))
40-
) {
34+
rawVersion = rawVersion.trim()
35+
// Skip URL-based versions
36+
if (URL_PREFIXES.some((p) => rawVersion.startsWith(p)))
4137
return null
42-
}
4338

4439
let protocol: VersionProtocol = null
4540
let versionStr = rawVersion
4641

47-
// Handle npm: protocol (e.g., npm:^1.0.0)
48-
if (rawVersion.startsWith(NPM_PREFIX)) {
49-
protocol = 'npm'
50-
versionStr = rawVersion.slice(4 /* NPM_PREFIX.length */)
42+
// Parse protocol if present (e.g., npm:^1.0.0 -> protocol: 'npm')
43+
const colonIndex = rawVersion.indexOf(':')
44+
if (colonIndex !== -1) {
45+
protocol = rawVersion.slice(0, colonIndex) as VersionProtocol
46+
47+
if (!KNOWN_PROTOCOLS.has(protocol))
48+
return null
49+
50+
versionStr = rawVersion.slice(colonIndex + 1)
5151
}
5252

5353
const firstChar = versionStr[0]

tests/package.test.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,33 @@ describe('parseVersion', () => {
5252
})
5353
})
5454

55-
it('should return null for workspace:', () => {
56-
expect(parseVersion('workspace:*')).toBeNull()
55+
it('should parse workspace: protocol', () => {
56+
expect(parseVersion('workspace:*')).toEqual({
57+
protocol: 'workspace',
58+
prefix: '',
59+
semver: '*',
60+
})
61+
})
62+
63+
it('should parse catalog: protocol', () => {
64+
expect(parseVersion('catalog:default')).toEqual({
65+
protocol: 'catalog',
66+
prefix: '',
67+
semver: 'default',
68+
})
5769
})
5870

59-
it('should return null for catalog:', () => {
60-
expect(parseVersion('catalog:default')).toBeNull()
71+
it('should parse jsr: protocol', () => {
72+
expect(parseVersion('jsr:^1.1.4')).toEqual({
73+
protocol: 'jsr',
74+
prefix: '^',
75+
semver: '1.1.4',
76+
})
6177
})
6278

63-
it('should return null for jsr:', () => {
64-
expect(parseVersion('jsr:@std/fs')).toBeNull()
79+
it('should return null for URL-based versions', () => {
80+
expect(parseVersion('https://github.com/user/repo')).toBeNull()
81+
expect(parseVersion('git://github.com/user/repo')).toBeNull()
82+
expect(parseVersion('git+https://github.com/user/repo')).toBeNull()
6583
})
6684
})

0 commit comments

Comments
 (0)