Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/language-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"inlinedDependencies": {
"fast-npm-meta": "1.5.1",
"jsonc-parser": "3.3.1",
"module-replacements": "3.0.0-beta.7",
"ofetch": "2.0.0-alpha.3",
"path-browserify": "1.0.1",
"yaml": "2.9.0"
Expand Down
80 changes: 80 additions & 0 deletions packages/language-core/src/extractors/json.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,84 @@ describe('jsonExtractor', () => {
},
})
})

describe('getPackageManifestInfo', () => {
it('extracts overrides and resolutions from all package managers', () => {
const info = extractor.getPackageManifestInfo(`{
"name": "test-pkg",
"overrides": {
"lodash": "npm:lodash-es@^4.17.21",
"semver": "^7.0.0"
},
"resolutions": {
"**/lodash": "npm:lodash-es@^4.17.21",
"semver": "^7.0.0"
},
"pnpm": {
"overrides": {
"lodash": "npm:lodash-es@^4.17.21",
"semver": "^7.0.0"
}
}
}`)

const overrides = info?.dependencies.filter((d) => d.category === 'overrides')
const resolutions = info?.dependencies.filter((d) => d.category === 'resolutions')

expect(overrides).toHaveLength(4)
expect(resolutions).toHaveLength(2)

expect(overrides?.map(({ rawName, rawSpec }) => ({ rawName, rawSpec }))).toEqual([
{ rawName: 'lodash', rawSpec: 'npm:lodash-es@^4.17.21' },
{ rawName: 'semver', rawSpec: '^7.0.0' },
{ rawName: 'lodash', rawSpec: 'npm:lodash-es@^4.17.21' },
{ rawName: 'semver', rawSpec: '^7.0.0' },
])

expect(resolutions?.map(({ rawName, rawSpec }) => ({ rawName, rawSpec }))).toEqual([
{ rawName: 'lodash', rawSpec: 'npm:lodash-es@^4.17.21' },
{ rawName: 'semver', rawSpec: '^7.0.0' },
])
})

it('skips nested override objects', () => {
const info = extractor.getPackageManifestInfo(`{
"name": "test-pkg",
"overrides": {
"lodash": "npm:lodash-es@^4.17.21",
"express": {
"body-parser": "^1.0.0"
}
}
}`)

expect(info?.dependencies.filter((d) => d.category === 'overrides')).toHaveLength(1)
})

it('includes overrides alongside regular dependencies', () => {
const info = extractor.getPackageManifestInfo(`{
"name": "test-pkg",
"dependencies": {
"lodash": "^4.17.21"
},
"devDependencies": {
"vitest": "^4.0.0"
},
"overrides": {
"lodash": "npm:lodash-es@^4.17.21"
},
"resolutions": {
"semver": "npm:semver-ns@^7.0.0"
}
}`)

expect(info?.dependencies).toHaveLength(4)
expect(info?.dependencies.map(({ rawName, category }) => ({ rawName, category }))).toEqual([
{ rawName: 'lodash', category: 'dependencies' },
{ rawName: 'vitest', category: 'devDependencies' },
{ rawName: 'lodash', category: 'overrides' },
{ rawName: 'semver', category: 'resolutions' },
])
})
})
})
33 changes: 33 additions & 0 deletions packages/language-core/src/extractors/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ const DEPENDENCY_SECTIONS: DependencyCategory[] = [
'optionalDependencies',
]

const RESOLUTION_SECTIONS: {
path: string[]
category: 'overrides' | 'resolutions'
}[] = [
{ path: ['overrides'], category: 'overrides' },
{ path: ['resolutions'], category: 'resolutions' },
{ path: ['pnpm', 'overrides'], category: 'overrides' },
]

const RESOLUTIONS_KEY_PREFIX = '**/'

interface CatalogMeta {
category: 'catalog' | 'catalogs'
categoryName?: string
Expand Down Expand Up @@ -138,6 +149,28 @@ export class JsonExtractor implements PackageManifestExtractor, WorkspaceCatalog
}
})

for (const { path, category } of RESOLUTION_SECTIONS) {
const node = findNodeAtLocation(root, path)
if (!node || !node.children)
continue

for (const dep of node.children) {
const info = this.#parseDependencyNode(dep, category)
if (!info)
continue

if (category === 'resolutions' && info.rawName.startsWith(RESOLUTIONS_KEY_PREFIX)) {
result.push({
...info,
rawName: info.rawName.slice(RESOLUTIONS_KEY_PREFIX.length),
nameRange: [info.nameRange[0] + RESOLUTIONS_KEY_PREFIX.length, info.nameRange[1]],
})
} else {
result.push(info)
}
}
}

return result
}

Expand Down
2 changes: 2 additions & 0 deletions packages/language-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export type DependencyCategory
| 'devDependencies'
| 'peerDependencies'
| 'optionalDependencies'
| 'overrides'
| 'resolutions'
| 'catalog'
| 'catalogs'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ function getReplacementDescription(resolvedName: string, replacement: ModuleRepl
}
}

export const checkReplacement: DiagnosticRule = async ({ dep: { nameRange, resolvedName } }, ignoreList) => {
export const checkReplacement: DiagnosticRule = async ({ dep }, ignoreList) => {
const { resolvedName, category } = dep
if (checkIgnored({ ignoreList, name: resolvedName }))
return

Expand All @@ -43,8 +44,12 @@ export const checkReplacement: DiagnosticRule = async ({ dep: { nameRange, resol

const link = resolveDocUrl(replacement.replacement.url)

const range = category === 'overrides' || category === 'resolutions'
? dep.specRange
: dep.nameRange

return {
range: nameRange,
range,
message: description,
severity: 2 satisfies typeof DiagnosticSeverity.Warning,
code: 'replacement',
Expand Down
6 changes: 4 additions & 2 deletions packages/language-service/src/plugins/document-link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function create(workspaceState: IWorkspaceState): LanguageServicePlugin {
if (dep.resolvedProtocol !== 'npm')
continue

const { resolvedName, resolvedSpec, nameRange } = dep
const { resolvedName, resolvedSpec, nameRange, specRange, category } = dep

let targetVersion: string | undefined

Expand All @@ -46,7 +46,9 @@ export function create(workspaceState: IWorkspaceState): LanguageServicePlugin {
? npmxPackageUrl(resolvedName, targetVersion)
: npmxPackageUrl(resolvedName)

const [start, end] = nameRange
const [start, end] = category === 'overrides' || category === 'resolutions'
? specRange
: nameRange
links.push({
range: {
start: document.positionAt(start),
Expand Down
4 changes: 4 additions & 0 deletions playground/npm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,9 @@
"devDependencies": {
"ofetch": "^1.4.0",
"array-includes": "^3.1.8"
},
"overrides": {
"array-includes": "npm:array-includes-es@^3.1.8",
"lodash": "^4.17.20"
}
}
6 changes: 6 additions & 0 deletions playground/pnpm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,11 @@
"ofetch": "^1.4.0",
"array-includes": "^3.1.8",
"is-number": "catalog:dev"
},
"pnpm": {
"overrides": {
"array-includes": "npm:array-includes-es@^3.1.8",
"lodash": "^4.17.20"
}
}
}
4 changes: 4 additions & 0 deletions playground/yarn/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,9 @@
"devDependencies": {
"ofetch": "^1.4.0",
"array-includes": "^3.1.8"
},
"resolutions": {
"**/array-includes": "npm:array-includes-es@^3.1.8",
"lodash": "^4.17.20"
}
}