From 05bf2586c32f88b3e94d4861f7a1c8a57b2ece55 Mon Sep 17 00:00:00 2001 From: Typed SIGTERM Date: Wed, 17 Jun 2026 10:03:32 +0800 Subject: [PATCH] feat: support `overrides` etc. --- packages/language-core/package.json | 1 + .../language-core/src/extractors/json.test.ts | 80 +++++++++++++++++++ packages/language-core/src/extractors/json.ts | 33 ++++++++ packages/language-core/src/types.ts | 2 + .../plugins/diagnostics/rules/replacement.ts | 9 ++- .../src/plugins/document-link.ts | 6 +- playground/npm/package.json | 4 + playground/pnpm/package.json | 6 ++ playground/yarn/package.json | 4 + 9 files changed, 141 insertions(+), 4 deletions(-) diff --git a/packages/language-core/package.json b/packages/language-core/package.json index b6d7f261..49896cb9 100644 --- a/packages/language-core/package.json +++ b/packages/language-core/package.json @@ -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" diff --git a/packages/language-core/src/extractors/json.test.ts b/packages/language-core/src/extractors/json.test.ts index ebf43c08..38ebc564 100644 --- a/packages/language-core/src/extractors/json.test.ts +++ b/packages/language-core/src/extractors/json.test.ts @@ -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' }, + ]) + }) + }) }) diff --git a/packages/language-core/src/extractors/json.ts b/packages/language-core/src/extractors/json.ts index ad18542a..fac83e1d 100644 --- a/packages/language-core/src/extractors/json.ts +++ b/packages/language-core/src/extractors/json.ts @@ -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 @@ -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 } diff --git a/packages/language-core/src/types.ts b/packages/language-core/src/types.ts index 2e3e69d2..36afb0a7 100644 --- a/packages/language-core/src/types.ts +++ b/packages/language-core/src/types.ts @@ -17,6 +17,8 @@ export type DependencyCategory | 'devDependencies' | 'peerDependencies' | 'optionalDependencies' + | 'overrides' + | 'resolutions' | 'catalog' | 'catalogs' diff --git a/packages/language-service/src/plugins/diagnostics/rules/replacement.ts b/packages/language-service/src/plugins/diagnostics/rules/replacement.ts index d4d83368..ac8bf4d1 100644 --- a/packages/language-service/src/plugins/diagnostics/rules/replacement.ts +++ b/packages/language-service/src/plugins/diagnostics/rules/replacement.ts @@ -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 @@ -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', diff --git a/packages/language-service/src/plugins/document-link.ts b/packages/language-service/src/plugins/document-link.ts index ac26e43b..4984548e 100644 --- a/packages/language-service/src/plugins/document-link.ts +++ b/packages/language-service/src/plugins/document-link.ts @@ -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 @@ -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), diff --git a/playground/npm/package.json b/playground/npm/package.json index 41492bcf..2bbd34c8 100644 --- a/playground/npm/package.json +++ b/playground/npm/package.json @@ -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" } } diff --git a/playground/pnpm/package.json b/playground/pnpm/package.json index 7d9ecc01..f6ed7065 100644 --- a/playground/pnpm/package.json +++ b/playground/pnpm/package.json @@ -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" + } } } diff --git a/playground/yarn/package.json b/playground/yarn/package.json index 7bee4520..438eaebe 100644 --- a/playground/yarn/package.json +++ b/playground/yarn/package.json @@ -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" } }