Skip to content

Commit 3fa64aa

Browse files
committed
implemented VerifyImportFixModuleSpecifiers
1 parent 4da16e7 commit 3fa64aa

File tree

84 files changed

+2853
-59
lines changed

Some content is hidden

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

84 files changed

+2853
-59
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 & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +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
1720
TestAutoImportNodeNextJSRequire
21+
TestAutoImportNodeModuleSymlinkRenamed
22+
TestAutoImportPackageJsonImportsCaseSensitivity
1823
TestAutoImportPathsAliasesAndBarrels
1924
TestAutoImportProvider_exportMap1
2025
TestAutoImportProvider_exportMap2
@@ -32,6 +37,7 @@ TestAutoImportProvider_wildcardExports1
3237
TestAutoImportProvider_wildcardExports2
3338
TestAutoImportProvider_wildcardExports3
3439
TestAutoImportProvider4
40+
TestAutoImportProvider9
3541
TestAutoImportSortCaseSensitivity1
3642
TestAutoImportTypeImport1
3743
TestAutoImportTypeImport2
@@ -185,6 +191,7 @@ TestCompletionsImport_require_addToExisting
185191
TestCompletionsImport_typeOnly
186192
TestCompletionsImport_umdDefaultNoCrash1
187193
TestCompletionsImport_uriStyleNodeCoreModules2
194+
TestCompletionsImport_uriStyleNodeCoreModules3
188195
TestCompletionsImport_windowsPathsProjectRelative
189196
TestCompletionsImportOrExportSpecifier
190197
TestCompletionsInExport
@@ -263,6 +270,9 @@ TestGetJavaScriptQuickInfo8
263270
TestGetJavaScriptSyntacticDiagnostics24
264271
TestGetOccurrencesIfElseBroken
265272
TestHoverOverComment
273+
TestImportNameCodeFix_noDestructureNonObjectLiteral
274+
TestImportNameCodeFix_uriStyleNodeCoreModules2
275+
TestImportNameCodeFix_uriStyleNodeCoreModules3
266276
TestImportCompletions_importsMap1
267277
TestImportCompletions_importsMap2
268278
TestImportCompletions_importsMap3

internal/fourslash/fourslash.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1436,6 +1436,121 @@ func (f *FourslashTest) VerifyImportFixAtPosition(t *testing.T, expectedTexts []
14361436
}
14371437
}
14381438

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

internal/fourslash/tests/autoImportFileExcludePatterns_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ export const ignoredSymbol = 2;
2121
mySym/*1*/
2222
ignoredSym/*2*/`
2323

24-
f := fourslash.NewFourslash(t, nil /*capabilities*/, content)
24+
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
25+
defer done()
2526
f.Configure(t, &lsutil.UserPreferences{
2627
AutoImportFileExcludePatterns: []string{"*ignoreme.ts"},
2728
IncludeCompletionsForModuleExports: core.TSTrue,

internal/fourslash/tests/autoImportSpecifierExcludeRegexes_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ export const ignoredSymbol = 2;
2121
mySym/*1*/
2222
ignoredSym/*2*/`
2323

24-
f := fourslash.NewFourslash(t, nil /*capabilities*/, content)
24+
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
25+
defer done()
2526
f.Configure(t, &lsutil.UserPreferences{
2627
AutoImportSpecifierExcludeRegexes: []string{".*ignoreme.*"},
2728
IncludeCompletionsForModuleExports: core.TSTrue,

0 commit comments

Comments
 (0)