Skip to content

Commit 31ce997

Browse files
authored
added gating for user preferences for auto imports (#1793)
1 parent 543240f commit 31ce997

File tree

109 files changed

+3545
-92
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+3545
-92
lines changed

internal/core/core.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -688,9 +688,9 @@ func DeduplicateSorted[T any](slice []T, isEqual func(a, b T) bool) []T {
688688
// CompareBooleans treats true as greater than false.
689689
func CompareBooleans(a, b bool) int {
690690
if a && !b {
691-
return -1
692-
} else if !a && b {
693691
return 1
692+
} else if !a && b {
693+
return -1
694694
}
695695
return 0
696696
}

internal/fourslash/_scripts/convertFourslash.mts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,9 @@ function parseFourslashStatement(statement: ts.Statement): Cmd[] | undefined {
184184
case "importFixAtPosition":
185185
// `verify.importFixAtPosition(...)`
186186
return parseImportFixAtPositionArgs(callExpression.arguments);
187+
case "importFixModuleSpecifiers":
188+
// `verify.importFixModuleSpecifiers(...)`
189+
return parseImportFixModuleSpecifiersArgs(callExpression.arguments);
187190
case "quickInfoAt":
188191
case "quickInfoExists":
189192
case "quickInfoIs":
@@ -619,6 +622,53 @@ function parseImportFixAtPositionArgs(args: readonly ts.Expression[]): VerifyImp
619622
}];
620623
}
621624

625+
function parseImportFixModuleSpecifiersArgs(args: readonly ts.Expression[]): [VerifyImportFixModuleSpecifiersCmd] | undefined {
626+
if (args.length < 2 || args.length > 3) {
627+
console.error(`Expected 2-3 arguments in verify.importFixModuleSpecifiers, got ${args.length}`);
628+
return undefined;
629+
}
630+
631+
const markerArg = getStringLiteralLike(args[0]);
632+
if (!markerArg) {
633+
console.error(`Expected string literal for marker in verify.importFixModuleSpecifiers, got ${args[0].getText()}`);
634+
return undefined;
635+
}
636+
const markerName = getGoStringLiteral(markerArg.text);
637+
638+
const arrayArg = getArrayLiteralExpression(args[1]);
639+
if (!arrayArg) {
640+
console.error(`Expected array literal for module specifiers in verify.importFixModuleSpecifiers, got ${args[1].getText()}`);
641+
return undefined;
642+
}
643+
644+
const moduleSpecifiers: string[] = [];
645+
for (const elem of arrayArg.elements) {
646+
const strElem = getStringLiteralLike(elem);
647+
if (!strElem) {
648+
console.error(`Expected string literal in module specifiers array, got ${elem.getText()}`);
649+
return undefined;
650+
}
651+
moduleSpecifiers.push(getGoStringLiteral(strElem.text));
652+
}
653+
654+
let preferences = "nil /*preferences*/";
655+
if (args.length > 2 && ts.isObjectLiteralExpression(args[2])) {
656+
const parsedPrefs = parseUserPreferences(args[2]);
657+
if (!parsedPrefs) {
658+
console.error(`Unrecognized user preferences in verify.importFixModuleSpecifiers: ${args[2].getText()}`);
659+
return undefined;
660+
}
661+
preferences = parsedPrefs;
662+
}
663+
664+
return [{
665+
kind: "verifyImportFixModuleSpecifiers",
666+
markerName,
667+
moduleSpecifiers,
668+
preferences,
669+
}];
670+
}
671+
622672
const completionConstants = new Map([
623673
["completion.globals", "CompletionGlobals"],
624674
["completion.globalTypes", "CompletionGlobalTypes"],
@@ -1420,6 +1470,45 @@ function parseUserPreferences(arg: ts.ObjectLiteralExpression): string | undefin
14201470
case "quotePreference":
14211471
preferences.push(`QuotePreference: lsutil.QuotePreference(${prop.initializer.getText()})`);
14221472
break;
1473+
case "autoImportSpecifierExcludeRegexes":
1474+
const regexArrayArg = getArrayLiteralExpression(prop.initializer);
1475+
if (!regexArrayArg) {
1476+
return undefined;
1477+
}
1478+
const regexes: string[] = [];
1479+
for (const elem of regexArrayArg.elements) {
1480+
const strElem = getStringLiteralLike(elem);
1481+
if (!strElem) {
1482+
return undefined;
1483+
}
1484+
regexes.push(getGoStringLiteral(strElem.text));
1485+
}
1486+
preferences.push(`AutoImportSpecifierExcludeRegexes: []string{${regexes.join(", ")}}`);
1487+
break;
1488+
case "importModuleSpecifierPreference":
1489+
if (!ts.isStringLiteralLike(prop.initializer)) {
1490+
return undefined;
1491+
}
1492+
preferences.push(`ImportModuleSpecifierPreference: ${prop.initializer.getText()}`);
1493+
break;
1494+
case "importModuleSpecifierEnding":
1495+
if (!ts.isStringLiteralLike(prop.initializer)) {
1496+
return undefined;
1497+
}
1498+
preferences.push(`ImportModuleSpecifierEnding: ${prop.initializer.getText()}`);
1499+
break;
1500+
case "includePackageJsonAutoImports":
1501+
if (!ts.isStringLiteralLike(prop.initializer)) {
1502+
return undefined;
1503+
}
1504+
preferences.push(`IncludePackageJsonAutoImports: ${prop.initializer.getText()}`);
1505+
break;
1506+
case "allowRenameOfImportPath":
1507+
preferences.push(`AllowRenameOfImportPath: ${prop.initializer.getText()}`);
1508+
break;
1509+
case "preferTypeOnlyAutoImports":
1510+
preferences.push(`PreferTypeOnlyAutoImports: ${prop.initializer.getText()}`);
1511+
break;
14231512
case "autoImportFileExcludePatterns":
14241513
const arrayArg = getArrayLiteralExpression(prop.initializer);
14251514
if (!arrayArg) {
@@ -2508,6 +2597,13 @@ interface VerifyImportFixAtPositionCmd {
25082597
preferences: string;
25092598
}
25102599

2600+
interface VerifyImportFixModuleSpecifiersCmd {
2601+
kind: "verifyImportFixModuleSpecifiers";
2602+
markerName: string;
2603+
moduleSpecifiers: string[];
2604+
preferences: string;
2605+
}
2606+
25112607
interface GoToCmd {
25122608
kind: "goTo";
25132609
// !!! `selectRange` and `rangeStart` require parsing variables and `test.ranges()[n]`
@@ -2616,6 +2712,7 @@ type Cmd =
26162712
| VerifyNavTreeCmd
26172713
| VerifyBaselineInlayHintsCmd
26182714
| VerifyImportFixAtPositionCmd
2715+
| VerifyImportFixModuleSpecifiersCmd
26192716
| VerifyDiagnosticsCmd
26202717
| VerifyBaselineDiagnosticsCmd
26212718
| VerifyOutliningSpansCmd;
@@ -2739,6 +2836,13 @@ function generateImportFixAtPosition({ expectedTexts, preferences }: VerifyImpor
27392836
return `f.VerifyImportFixAtPosition(t, []string{\n${expectedTexts.join(",\n")},\n}, ${preferences})`;
27402837
}
27412838

2839+
function generateImportFixModuleSpecifiers({ markerName, moduleSpecifiers, preferences }: VerifyImportFixModuleSpecifiersCmd): string {
2840+
const specifiersArray = moduleSpecifiers.length === 0
2841+
? "[]string{}"
2842+
: `[]string{${moduleSpecifiers.join(", ")}}`;
2843+
return `f.VerifyImportFixModuleSpecifiers(t, ${markerName}, ${specifiersArray}, ${preferences})`;
2844+
}
2845+
27422846
function generateSignatureHelpExpected(opts: VerifySignatureHelpOptions): string {
27432847
const fields: string[] = [];
27442848

@@ -2904,6 +3008,8 @@ function generateCmd(cmd: Cmd): string {
29043008
return generateBaselineInlayHints(cmd);
29053009
case "verifyImportFixAtPosition":
29063010
return generateImportFixAtPosition(cmd);
3011+
case "verifyImportFixModuleSpecifiers":
3012+
return generateImportFixModuleSpecifiers(cmd);
29073013
case "verifyDiagnostics":
29083014
const funcName = cmd.isSuggestion ? "VerifySuggestionDiagnostics" : "VerifyNonSuggestionDiagnostics";
29093015
return `f.${funcName}(t, ${cmd.arg})`;

internal/fourslash/_scripts/failingTests.txt

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,23 @@ TestAmbientShorthandGotoDefinition
33
TestArgumentsAreAvailableAfterEditsAtEndOfFunction
44
TestAugmentedTypesClass1
55
TestAugmentedTypesClass3Fourslash
6+
TestAutoImportAllowImportingTsExtensionsPackageJsonImports1
67
TestAutoImportCompletionAmbientMergedModule1
78
TestAutoImportCompletionExportListAugmentation1
89
TestAutoImportCompletionExportListAugmentation2
910
TestAutoImportCompletionExportListAugmentation3
1011
TestAutoImportCompletionExportListAugmentation4
12+
TestAutoImportCrossPackage_pathsAndSymlink
1113
TestAutoImportCrossProject_symlinks_stripSrc
1214
TestAutoImportCrossProject_symlinks_toDist
1315
TestAutoImportCrossProject_symlinks_toSrc
16+
TestAutoImportFileExcludePatterns2
1417
TestAutoImportFileExcludePatterns3
1518
TestAutoImportJsDocImport1
1619
TestAutoImportModuleNone1
20+
TestAutoImportNodeModuleSymlinkRenamed
1721
TestAutoImportNodeNextJSRequire
18-
TestAutoImportPathsAliasesAndBarrels
22+
TestAutoImportPackageJsonImportsCaseSensitivity
1923
TestAutoImportProvider_exportMap1
2024
TestAutoImportProvider_exportMap2
2125
TestAutoImportProvider_exportMap3
@@ -32,6 +36,7 @@ TestAutoImportProvider_wildcardExports1
3236
TestAutoImportProvider_wildcardExports2
3337
TestAutoImportProvider_wildcardExports3
3438
TestAutoImportProvider4
39+
TestAutoImportProvider9
3540
TestAutoImportSortCaseSensitivity1
3641
TestAutoImportTypeImport1
3742
TestAutoImportTypeImport2
@@ -185,6 +190,7 @@ TestCompletionsImport_require_addToExisting
185190
TestCompletionsImport_typeOnly
186191
TestCompletionsImport_umdDefaultNoCrash1
187192
TestCompletionsImport_uriStyleNodeCoreModules2
193+
TestCompletionsImport_uriStyleNodeCoreModules3
188194
TestCompletionsImport_windowsPathsProjectRelative
189195
TestCompletionsImportOrExportSpecifier
190196
TestCompletionsInExport
@@ -288,18 +294,19 @@ TestImportNameCodeFix_HeaderComment1
288294
TestImportNameCodeFix_HeaderComment2
289295
TestImportNameCodeFix_importType1
290296
TestImportNameCodeFix_importType2
291-
TestImportNameCodeFix_importType4
292297
TestImportNameCodeFix_importType7
293298
TestImportNameCodeFix_importType8
294299
TestImportNameCodeFix_jsx1
300+
TestImportNameCodeFix_noDestructureNonObjectLiteral
295301
TestImportNameCodeFix_order
296302
TestImportNameCodeFix_order2
297303
TestImportNameCodeFix_preferBaseUrl
298304
TestImportNameCodeFix_reExportDefault
299305
TestImportNameCodeFix_symlink
300306
TestImportNameCodeFix_trailingComma
307+
TestImportNameCodeFix_uriStyleNodeCoreModules2
308+
TestImportNameCodeFix_uriStyleNodeCoreModules3
301309
TestImportNameCodeFix_withJson
302-
TestImportNameCodeFixConvertTypeOnly1
303310
TestImportNameCodeFixExistingImport10
304311
TestImportNameCodeFixExistingImport11
305312
TestImportNameCodeFixExistingImport8
@@ -317,7 +324,6 @@ TestImportNameCodeFixNewImportFileQuoteStyle1
317324
TestImportNameCodeFixNewImportFileQuoteStyle2
318325
TestImportNameCodeFixNewImportFileQuoteStyleMixed0
319326
TestImportNameCodeFixNewImportFileQuoteStyleMixed1
320-
TestImportNameCodeFixNewImportRootDirs0
321327
TestImportNameCodeFixNewImportTypeRoots1
322328
TestImportTypeCompletions1
323329
TestImportTypeCompletions3
@@ -373,8 +379,6 @@ TestMemberListInReopenedEnum
373379
TestMemberListInWithBlock
374380
TestMemberListOfExportedClass
375381
TestMemberListOnContextualThis
376-
TestModuleNodeNextAutoImport2
377-
TestModuleNodeNextAutoImport3
378382
TestNgProxy1
379383
TestNodeModulesImportCompletions1
380384
TestNoQuickInfoForLabel

internal/fourslash/fourslash.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1440,6 +1440,114 @@ func (f *FourslashTest) VerifyImportFixAtPosition(t *testing.T, expectedTexts []
14401440
}
14411441
}
14421442

1443+
func (f *FourslashTest) VerifyImportFixModuleSpecifiers(
1444+
t *testing.T,
1445+
markerName string,
1446+
expectedModuleSpecifiers []string,
1447+
preferences *lsutil.UserPreferences,
1448+
) {
1449+
f.GoToMarker(t, markerName)
1450+
1451+
if preferences != nil {
1452+
reset := f.ConfigureWithReset(t, preferences)
1453+
defer reset()
1454+
}
1455+
1456+
// Get diagnostics at the current position to find errors that need import fixes
1457+
diagParams := &lsproto.DocumentDiagnosticParams{
1458+
TextDocument: lsproto.TextDocumentIdentifier{
1459+
Uri: lsconv.FileNameToDocumentURI(f.activeFilename),
1460+
},
1461+
}
1462+
diagResult := sendRequest(t, f, lsproto.TextDocumentDiagnosticInfo, diagParams)
1463+
1464+
var diagnostics []*lsproto.Diagnostic
1465+
if diagResult.FullDocumentDiagnosticReport != nil && diagResult.FullDocumentDiagnosticReport.Items != nil {
1466+
diagnostics = diagResult.FullDocumentDiagnosticReport.Items
1467+
}
1468+
1469+
params := &lsproto.CodeActionParams{
1470+
TextDocument: lsproto.TextDocumentIdentifier{
1471+
Uri: lsconv.FileNameToDocumentURI(f.activeFilename),
1472+
},
1473+
Range: lsproto.Range{
1474+
Start: f.currentCaretPosition,
1475+
End: f.currentCaretPosition,
1476+
},
1477+
Context: &lsproto.CodeActionContext{
1478+
Diagnostics: diagnostics,
1479+
},
1480+
}
1481+
result := sendRequest(t, f, lsproto.TextDocumentCodeActionInfo, params)
1482+
1483+
// Extract module specifiers from import fix code actions
1484+
var actualModuleSpecifiers []string
1485+
if result.CommandOrCodeActionArray != nil {
1486+
for _, item := range *result.CommandOrCodeActionArray {
1487+
if item.CodeAction != nil && item.CodeAction.Kind != nil && *item.CodeAction.Kind == lsproto.CodeActionKindQuickFix {
1488+
if item.CodeAction.Edit != nil && item.CodeAction.Edit.Changes != nil {
1489+
for _, changeEdits := range *item.CodeAction.Edit.Changes {
1490+
for _, edit := range changeEdits {
1491+
moduleSpec := extractModuleSpecifier(edit.NewText)
1492+
if moduleSpec != "" {
1493+
if !slices.Contains(actualModuleSpecifiers, moduleSpec) {
1494+
actualModuleSpecifiers = append(actualModuleSpecifiers, moduleSpec)
1495+
}
1496+
}
1497+
}
1498+
}
1499+
}
1500+
}
1501+
}
1502+
}
1503+
1504+
// Compare results
1505+
if len(actualModuleSpecifiers) != len(expectedModuleSpecifiers) {
1506+
t.Fatalf("Expected %d module specifiers, got %d.\nExpected: %v\nActual: %v",
1507+
len(expectedModuleSpecifiers), len(actualModuleSpecifiers),
1508+
expectedModuleSpecifiers, actualModuleSpecifiers)
1509+
}
1510+
1511+
for i, expected := range expectedModuleSpecifiers {
1512+
if i >= len(actualModuleSpecifiers) || actualModuleSpecifiers[i] != expected {
1513+
t.Fatalf("Module specifier mismatch at index %d.\nExpected: %v\nActual: %v",
1514+
i, expectedModuleSpecifiers, actualModuleSpecifiers)
1515+
}
1516+
}
1517+
}
1518+
1519+
func extractModuleSpecifier(text string) string {
1520+
// Try to match: from "..." or from '...'
1521+
if idx := strings.Index(text, "from \""); idx != -1 {
1522+
start := idx + 6 // len("from \"")
1523+
if end := strings.Index(text[start:], "\""); end != -1 {
1524+
return text[start : start+end]
1525+
}
1526+
}
1527+
if idx := strings.Index(text, "from '"); idx != -1 {
1528+
start := idx + 6 // len("from '")
1529+
if end := strings.Index(text[start:], "'"); end != -1 {
1530+
return text[start : start+end]
1531+
}
1532+
}
1533+
1534+
// Try to match: require("...") or require('...')
1535+
if idx := strings.Index(text, "require(\""); idx != -1 {
1536+
start := idx + 9 // len("require(\"")
1537+
if end := strings.Index(text[start:], "\""); end != -1 {
1538+
return text[start : start+end]
1539+
}
1540+
}
1541+
if idx := strings.Index(text, "require('"); idx != -1 {
1542+
start := idx + 9 // len("require('")
1543+
if end := strings.Index(text[start:], "'"); end != -1 {
1544+
return text[start : start+end]
1545+
}
1546+
}
1547+
1548+
return ""
1549+
}
1550+
14431551
func (f *FourslashTest) VerifyBaselineFindAllReferences(
14441552
t *testing.T,
14451553
markers ...string,
@@ -3008,6 +3116,11 @@ func (f *FourslashTest) BaselineAutoImportsCompletions(t *testing.T, markerNames
30083116
reset := f.ConfigureWithReset(t, &lsutil.UserPreferences{
30093117
IncludeCompletionsForModuleExports: core.TSTrue,
30103118
IncludeCompletionsForImportStatements: core.TSTrue,
3119+
ImportModuleSpecifierEnding: f.userPreferences.ImportModuleSpecifierEnding,
3120+
ImportModuleSpecifierPreference: f.userPreferences.ImportModuleSpecifierPreference,
3121+
AutoImportFileExcludePatterns: f.userPreferences.AutoImportFileExcludePatterns,
3122+
AutoImportSpecifierExcludeRegexes: f.userPreferences.AutoImportSpecifierExcludeRegexes,
3123+
PreferTypeOnlyAutoImports: f.userPreferences.PreferTypeOnlyAutoImports,
30113124
})
30123125
defer reset()
30133126

0 commit comments

Comments
 (0)