From 0d59b538ab4df77f9390d5c9e0047131762a20ba Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 2 Nov 2026 15:34:18 -0800 Subject: [PATCH 1/2] Make auto-import fix ranking a consistent order Restricting ranking preferences to pairs of matching module kinds can produce comparison cycles. The resulting order and preferred fixes then depend on the order in which import candidates are discovered. --- .../importNameCodeFix_barrelExport_test.go | 19 ++++++++++++ ...meCodeFix_uriStyleNodeCoreModules1_test.go | 30 +++++++++++++++++++ tsc/internal/ls/autoimport/fix.go | 18 +++++------ 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/tsc/internal/fourslash/tests/importNameCodeFix_barrelExport_test.go b/tsc/internal/fourslash/tests/importNameCodeFix_barrelExport_test.go index 0a5590792c30c..906c2f5e1fe22 100644 --- a/tsc/internal/fourslash/tests/importNameCodeFix_barrelExport_test.go +++ b/tsc/internal/fourslash/tests/importNameCodeFix_barrelExport_test.go @@ -32,3 +32,22 @@ export * from "./a";` f.VerifyImportFixModuleSpecifiers(t, "sibling", []string{"./a", ".", ".."}, nil /*preferences*/) f.VerifyImportFixModuleSpecifiers(t, "parent", []string{"../foo", "../foo/a", ".."}, nil /*preferences*/) } + +func TestImportNameCodeFix_barrelExportMixedModuleKinds(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @module: commonjs +// @Filename: /node_modules/@types/example/index.d.ts +declare module "example" { export const A: number; } +// @Filename: /foo/a.ts +export const A = 0; +// @Filename: /foo/index.ts +export * from "./a"; +export * from "./b"; +// @Filename: /foo/b.ts +export {}; +A/**/` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.VerifyImportFixModuleSpecifiers(t, "", []string{"example", "./a", "."}, nil /*preferences*/) +} diff --git a/tsc/internal/fourslash/tests/importNameCodeFix_uriStyleNodeCoreModules1_test.go b/tsc/internal/fourslash/tests/importNameCodeFix_uriStyleNodeCoreModules1_test.go index 58b5ece8e1fa4..70e757f2d0e6a 100644 --- a/tsc/internal/fourslash/tests/importNameCodeFix_uriStyleNodeCoreModules1_test.go +++ b/tsc/internal/fourslash/tests/importNameCodeFix_uriStyleNodeCoreModules1_test.go @@ -22,3 +22,33 @@ writeFile/**/` defer done() f.VerifyImportFixModuleSpecifiers(t, "", []string{"fs", "node:fs", "fs/promises", "node:fs/promises"}, nil /*preferences*/) } + +func TestImportNameCodeFix_uriStyleMixedModuleKinds(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @module: commonjs +// @Filename: /node_modules/@types/node/index.d.ts +declare module "path" {} +declare module "node:path" {} +declare module "fs" { export function writeFile(): void } +declare module "node:fs" { export function writeFile(): void } +// @Filename: /node_modules/lodash/package.json +{ "name": "lodash", "types": "index.d.ts" } +// @Filename: /node_modules/lodash/index.d.ts +export declare function writeFile(): void; +// @Filename: /package.json +{ "dependencies": { "lodash": "*" } } +// @Filename: /prefix.ts +import "node:path"; +writeFile/*prefix*/ +// @Filename: /noPrefix.ts +import "path"; +writeFile/*noPrefix*/ +// @Filename: /unknown.ts +writeFile/*unknown*/` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.VerifyImportFixModuleSpecifiers(t, "prefix", []string{"node:fs", "fs", "lodash"}, nil /*preferences*/) + f.VerifyImportFixModuleSpecifiers(t, "noPrefix", []string{"fs", "lodash", "node:fs"}, nil /*preferences*/) + f.VerifyImportFixModuleSpecifiers(t, "unknown", []string{"fs", "lodash", "node:fs"}, nil /*preferences*/) +} diff --git a/tsc/internal/ls/autoimport/fix.go b/tsc/internal/ls/autoimport/fix.go index 1333ef59cc134..59271fd360b3a 100644 --- a/tsc/internal/ls/autoimport/fix.go +++ b/tsc/internal/ls/autoimport/fix.go @@ -1038,18 +1038,14 @@ func (v *View) compareModuleSpecifiersForRanking(a, b *Fix) int { if comparison := compareModuleSpecifierRelativity(a, b, v.preferences); comparison != 0 { return comparison } - if a.ModuleSpecifierKind == modulespecifiers.ResultKindAmbient && b.ModuleSpecifierKind == modulespecifiers.ResultKindAmbient { - if comparison := v.compareNodeCoreModuleSpecifiers(a.ModuleSpecifier, b.ModuleSpecifier, v.importingFile, v.program); comparison != 0 { - return comparison - } + if comparison := v.compareNodeCoreModuleSpecifiers(a.ModuleSpecifier, b.ModuleSpecifier, v.importingFile, v.program); comparison != 0 { + return comparison } - if a.ModuleSpecifierKind == modulespecifiers.ResultKindRelative && b.ModuleSpecifierKind == modulespecifiers.ResultKindRelative { - if comparison := core.CompareBooleans( - isFixPossiblyReExportingImportingFile(a, v.importingFile.FileName(), v.program.CaseSensitivity()), - isFixPossiblyReExportingImportingFile(b, v.importingFile.FileName(), v.program.CaseSensitivity()), - ); comparison != 0 { - return comparison - } + if comparison := core.CompareBooleans( + isFixPossiblyReExportingImportingFile(a, v.importingFile.FileName(), v.program.CaseSensitivity()), + isFixPossiblyReExportingImportingFile(b, v.importingFile.FileName(), v.program.CaseSensitivity()), + ); comparison != 0 { + return comparison } if comparison := tspath.CompareNumberOfDirectorySeparators(a.ModuleSpecifier, b.ModuleSpecifier); comparison != 0 { return comparison From 064e3a1dab6e26729b758d181f1f1c7b3605e15c Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 2 Nov 2026 15:50:44 -0800 Subject: [PATCH 2/2] Separate virtual files in auto-import regression tests --- .../fourslash/tests/importNameCodeFix_barrelExport_test.go | 3 +++ .../importNameCodeFix_uriStyleNodeCoreModules1_test.go | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/tsc/internal/fourslash/tests/importNameCodeFix_barrelExport_test.go b/tsc/internal/fourslash/tests/importNameCodeFix_barrelExport_test.go index 906c2f5e1fe22..7a30d622bee5f 100644 --- a/tsc/internal/fourslash/tests/importNameCodeFix_barrelExport_test.go +++ b/tsc/internal/fourslash/tests/importNameCodeFix_barrelExport_test.go @@ -39,11 +39,14 @@ func TestImportNameCodeFix_barrelExportMixedModuleKinds(t *testing.T) { const content = `// @module: commonjs // @Filename: /node_modules/@types/example/index.d.ts declare module "example" { export const A: number; } + // @Filename: /foo/a.ts export const A = 0; + // @Filename: /foo/index.ts export * from "./a"; export * from "./b"; + // @Filename: /foo/b.ts export {}; A/**/` diff --git a/tsc/internal/fourslash/tests/importNameCodeFix_uriStyleNodeCoreModules1_test.go b/tsc/internal/fourslash/tests/importNameCodeFix_uriStyleNodeCoreModules1_test.go index 70e757f2d0e6a..0cb0898eef602 100644 --- a/tsc/internal/fourslash/tests/importNameCodeFix_uriStyleNodeCoreModules1_test.go +++ b/tsc/internal/fourslash/tests/importNameCodeFix_uriStyleNodeCoreModules1_test.go @@ -32,18 +32,24 @@ declare module "path" {} declare module "node:path" {} declare module "fs" { export function writeFile(): void } declare module "node:fs" { export function writeFile(): void } + // @Filename: /node_modules/lodash/package.json { "name": "lodash", "types": "index.d.ts" } + // @Filename: /node_modules/lodash/index.d.ts export declare function writeFile(): void; + // @Filename: /package.json { "dependencies": { "lodash": "*" } } + // @Filename: /prefix.ts import "node:path"; writeFile/*prefix*/ + // @Filename: /noPrefix.ts import "path"; writeFile/*noPrefix*/ + // @Filename: /unknown.ts writeFile/*unknown*/` f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)