Skip to content

Commit 267e0cf

Browse files
fix: prioritize local imports over node_modules in auto-import suggestions
1 parent f450c1b commit 267e0cf

3 files changed

Lines changed: 83 additions & 1 deletion

File tree

src/services/codefixes/importFixes.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1414,6 +1414,21 @@ function getBestFix(fixes: readonly ImportFixWithModuleSpecifier[], sourceFile:
14141414
}
14151415

14161416
/** @returns `Comparison.LessThan` if `a` is better than `b`. */
1417+
1418+
/** Heuristic approach: Prioritize local/relative imports over node_modules imports. */
1419+
function compareLocalVsExternal(
1420+
a: ImportFixWithModuleSpecifier,
1421+
b: ImportFixWithModuleSpecifier
1422+
): Comparison {
1423+
const aIsExternal = a.moduleSpecifierKind === "node_modules";
1424+
const bIsExternal = b.moduleSpecifierKind === "node_modules";
1425+
if (!aIsExternal && bIsExternal) return Comparison.LessThan;
1426+
if (aIsExternal && !bIsExternal) return Comparison.GreaterThan;
1427+
return Comparison.EqualTo;
1428+
}
1429+
1430+
/** @returns `Comparison.LessThan` if `a` is better than `b`. */
1431+
14171432
function compareModuleSpecifiers(
14181433
a: ImportFixWithModuleSpecifier,
14191434
b: ImportFixWithModuleSpecifier,
@@ -1424,6 +1439,13 @@ function compareModuleSpecifiers(
14241439
toPath: (fileName: string) => Path,
14251440
): Comparison {
14261441
if (a.kind !== ImportFixKind.UseNamespace && b.kind !== ImportFixKind.UseNamespace) {
1442+
// STEP 2: ADD THE COMPARISON LOGIC HERE (INSIDE THE FUNCTION) ✅
1443+
const localVsExternalComparison = compareLocalVsExternal(a, b);
1444+
if (localVsExternalComparison !== Comparison.EqualTo) {
1445+
return localVsExternalComparison;
1446+
}
1447+
1448+
// Continue with existing logic
14271449
return compareBooleans(
14281450
b.moduleSpecifierKind !== "node_modules" || allowsImportingSpecifier(b.moduleSpecifier),
14291451
a.moduleSpecifierKind !== "node_modules" || allowsImportingSpecifier(a.moduleSpecifier),
@@ -1439,6 +1461,7 @@ function compareModuleSpecifiers(
14391461
return Comparison.EqualTo;
14401462
}
14411463

1464+
14421465
function compareModuleSpecifierRelativity(a: ImportFixWithModuleSpecifier, b: ImportFixWithModuleSpecifier, preferences: UserPreferences): Comparison {
14431466
if (preferences.importModuleSpecifierPreference === "non-relative" || preferences.importModuleSpecifierPreference === "project-relative") {
14441467
return compareBooleans(a.moduleSpecifierKind === "relative", b.moduleSpecifierKind === "relative");

src/testRunner/tests.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,4 +229,4 @@ export * from "./unittests/tsserver/typeReferenceDirectives.js";
229229
export * from "./unittests/tsserver/typingsInstaller.js";
230230
export * from "./unittests/tsserver/versionCache.js";
231231
export * from "./unittests/tsserver/watchEnvironment.js";
232-
export * from "./unittests/typeParameterIsPossiblyReferenced.js";
232+
export * from "./unittests/typeParameterIsPossiblyReferenced.js";
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// Test that local imports are prioritized over external node_modules imports
4+
// when the same symbol is exported from both
5+
6+
// @Filename: /node_modules/@mui/material/index.d.ts
7+
//// export function useTheme(): { palette: string };
8+
9+
// @Filename: /node_modules/@mui/material/package.json
10+
//// { "name": "@mui/material", "version": "5.0.0", "types": "index.d.ts" }
11+
12+
// @Filename: /node_modules/zustand/index.d.ts
13+
//// export function useStore(): any;
14+
15+
// @Filename: /node_modules/zustand/package.json
16+
//// { "name": "zustand", "version": "4.0.0", "types": "index.d.ts" }
17+
18+
// @Filename: /utils/store.ts
19+
//// export function useTheme() {
20+
//// return { palette: 'light' };
21+
//// }
22+
//// export function useStore() {
23+
//// return {};
24+
//// }
25+
26+
// @Filename: /components/Button.tsx
27+
//// // Local useTheme should be suggested first
28+
//// const theme = useTheme/*1*/();
29+
30+
// @Filename: /components/Header.tsx
31+
//// // Local useStore should be suggested first
32+
//// const store = useStore/*2*/();
33+
34+
// Test 1: useTheme - should prioritize local import over @mui/material
35+
goTo.marker("1");
36+
verify.importFixAtPosition([
37+
`import { useTheme } from "../utils/store";
38+
39+
// Local useTheme should be suggested first
40+
const theme = useTheme();`,
41+
`import { useTheme } from "@mui/material";
42+
43+
// Local useTheme should be suggested first
44+
const theme = useTheme();`
45+
]);
46+
47+
// Test 2: useStore - should prioritize local import over zustand
48+
goTo.marker("2");
49+
verify.importFixAtPosition([
50+
`import { useStore } from "../utils/store";
51+
52+
// Local useStore should be suggested first
53+
const store = useStore();`,
54+
`import { useStore } from "zustand";
55+
56+
// Local useStore should be suggested first
57+
const store = useStore();`
58+
]);
59+

0 commit comments

Comments
 (0)