diff --git a/internal/ast/utilities.go b/internal/ast/utilities.go index cba6498933..b381ac436d 100644 --- a/internal/ast/utilities.go +++ b/internal/ast/utilities.go @@ -1391,10 +1391,6 @@ func GetNameOfDeclaration(declaration *Node) *Node { return nil } -func GetImportClauseOfDeclaration(declaration *Declaration) *ImportClause { - return declaration.ImportClause().AsImportClause() -} - func GetNonAssignedNameOfDeclaration(declaration *Node) *Node { // !!! switch declaration.Kind { diff --git a/internal/astnav/tokens.go b/internal/astnav/tokens.go index 27ad1ebdf6..85fa79801e 100644 --- a/internal/astnav/tokens.go +++ b/internal/astnav/tokens.go @@ -672,3 +672,60 @@ func shouldSkipChild(node *ast.Node) bool { ast.IsJSDocLinkLike(node) || ast.IsJSDocTag(node) } + +// FindChildOfKind searches for a child node or token of the specified kind within a containing node. +// This function scans through both AST nodes and intervening tokens to find the first match. +func FindChildOfKind(containingNode *ast.Node, kind ast.Kind, sourceFile *ast.SourceFile) *ast.Node { + lastNodePos := containingNode.Pos() + scan := scanner.GetScannerForSourceFile(sourceFile, lastNodePos) + + var foundChild *ast.Node + visitNode := func(node *ast.Node) bool { + if node == nil || node.Flags&ast.NodeFlagsReparsed != 0 { + return false + } + // Look for child in preceding tokens. + startPos := lastNodePos + for startPos < node.Pos() { + tokenKind := scan.Token() + tokenFullStart := scan.TokenFullStart() + tokenEnd := scan.TokenEnd() + token := sourceFile.GetOrCreateToken(tokenKind, tokenFullStart, tokenEnd, containingNode) + if tokenKind == kind { + foundChild = token + return true + } + startPos = tokenEnd + scan.Scan() + } + if node.Kind == kind { + foundChild = node + return true + } + + lastNodePos = node.End() + scan.ResetPos(lastNodePos) + return false + } + + ast.ForEachChildAndJSDoc(containingNode, sourceFile, visitNode) + + if foundChild != nil { + return foundChild + } + + // Look for child in trailing tokens. + startPos := lastNodePos + for startPos < containingNode.End() { + tokenKind := scan.Token() + tokenFullStart := scan.TokenFullStart() + tokenEnd := scan.TokenEnd() + token := sourceFile.GetOrCreateToken(tokenKind, tokenFullStart, tokenEnd, containingNode) + if tokenKind == kind { + return token + } + startPos = tokenEnd + scan.Scan() + } + return nil +} diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 5b6fde57ec..f9017a92a0 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -2859,7 +2859,7 @@ func (c *Checker) getDeprecatedSuggestionNode(node *ast.Node) *ast.Node { case ast.KindTaggedTemplateExpression: return c.getDeprecatedSuggestionNode(node.AsTaggedTemplateExpression().Tag) case ast.KindJsxOpeningElement, ast.KindJsxSelfClosingElement: - return c.getDeprecatedSuggestionNode(getTagNameOfNode(node)) + return c.getDeprecatedSuggestionNode(node.TagName()) case ast.KindElementAccessExpression: return node.AsElementAccessExpression().ArgumentExpression case ast.KindPropertyAccessExpression: @@ -30342,7 +30342,7 @@ func (c *Checker) hasContextualTypeWithNoGenericTypes(node *ast.Node, checkMode // If check mode has `CheckMode.RestBindingElement`, we skip binding pattern contextual types, // as we want the type of a rest element to be generic when possible. if (ast.IsIdentifier(node) || ast.IsPropertyAccessExpression(node) || ast.IsElementAccessExpression(node)) && - !((ast.IsJsxOpeningElement(node.Parent) || ast.IsJsxSelfClosingElement(node.Parent)) && getTagNameOfNode(node.Parent) == node) { + !((ast.IsJsxOpeningElement(node.Parent) || ast.IsJsxSelfClosingElement(node.Parent)) && node.Parent.TagName() == node) { contextualType := c.getContextualType(node, core.IfElse(checkMode&CheckModeRestBindingElement != 0, ContextFlagsSkipBindingPatterns, ContextFlagsNone)) if contextualType != nil { return !c.isGenericType(contextualType) diff --git a/internal/checker/exports.go b/internal/checker/exports.go index dc61a4a547..da6d28979a 100644 --- a/internal/checker/exports.go +++ b/internal/checker/exports.go @@ -170,3 +170,15 @@ func (c *Checker) GetIndexSignaturesAtLocation(node *ast.Node) []*ast.Node { func (c *Checker) GetResolvedSymbol(node *ast.Node) *ast.Symbol { return c.getResolvedSymbol(node) } + +func (c *Checker) GetJsxNamespace(location *ast.Node) string { + return c.getJsxNamespace(location) +} + +func (c *Checker) ResolveName(name string, location *ast.Node, meaning ast.SymbolFlags, excludeGlobals bool) *ast.Symbol { + return c.resolveName(location, name, meaning, nil, true, excludeGlobals) +} + +func (c *Checker) GetSymbolFlags(symbol *ast.Symbol) ast.SymbolFlags { + return c.getSymbolFlags(symbol) +} diff --git a/internal/checker/utilities.go b/internal/checker/utilities.go index 60af0257ea..640f9da9e9 100644 --- a/internal/checker/utilities.go +++ b/internal/checker/utilities.go @@ -971,6 +971,13 @@ func IsKnownSymbol(symbol *ast.Symbol) bool { return isLateBoundName(symbol.Name) } +func IsPrivateIdentifierSymbol(symbol *ast.Symbol) bool { + if symbol == nil { + return false + } + return strings.HasPrefix(symbol.Name, ast.InternalSymbolNamePrefix+"#") +} + func isLateBoundName(name string) bool { return len(name) >= 2 && name[0] == '\xfe' && name[1] == '@' } @@ -1061,10 +1068,6 @@ func isNonNullAccess(node *ast.Node) bool { return ast.IsAccessExpression(node) && ast.IsNonNullExpression(node.Expression()) } -func getTagNameOfNode(node *ast.Node) *ast.Node { - return node.TagName() -} - func getBindingElementPropertyName(node *ast.Node) *ast.Node { return node.PropertyNameOrName() } diff --git a/internal/fourslash/_scripts/convertFourslash.mts b/internal/fourslash/_scripts/convertFourslash.mts index dedcc358b7..53c52829f3 100644 --- a/internal/fourslash/_scripts/convertFourslash.mts +++ b/internal/fourslash/_scripts/convertFourslash.mts @@ -175,6 +175,9 @@ function parseFourslashStatement(statement: ts.Statement): Cmd[] | undefined { case "applyCodeActionFromCompletion": // `verify.applyCodeActionFromCompletion(...)` return parseVerifyApplyCodeActionFromCompletionArgs(callExpression.arguments); + case "importFixAtPosition": + // `verify.importFixAtPosition(...)` + return parseImportFixAtPositionArgs(callExpression.arguments); case "quickInfoAt": case "quickInfoExists": case "quickInfoIs": @@ -542,6 +545,46 @@ function parseVerifyApplyCodeActionArgs(arg: ts.Expression): string | undefined return `&fourslash.ApplyCodeActionFromCompletionOptions{\n${props.join("\n")}\n}`; } +function parseImportFixAtPositionArgs(args: readonly ts.Expression[]): VerifyImportFixAtPositionCmd[] | undefined { + if (args.length < 1 || args.length > 3) { + console.error(`Expected 1-3 arguments in verify.importFixAtPosition, got ${args.map(arg => arg.getText()).join(", ")}`); + return undefined; + } + const arrayArg = getArrayLiteralExpression(args[0]); + if (!arrayArg) { + console.error(`Expected array literal for first argument in verify.importFixAtPosition, got ${args[0].getText()}`); + return undefined; + } + const expectedTexts: string[] = []; + for (const elem of arrayArg.elements) { + const strElem = getStringLiteralLike(elem); + if (!strElem) { + console.error(`Expected string literal in verify.importFixAtPosition array, got ${elem.getText()}`); + return undefined; + } + expectedTexts.push(getGoMultiLineStringLiteral(strElem.text)); + } + + // If the array is empty, we should still generate valid Go code + if (expectedTexts.length === 0) { + expectedTexts.push(""); // This will be handled specially in code generation + } + + let preferences: string | undefined; + if (args.length > 2 && ts.isObjectLiteralExpression(args[2])) { + preferences = parseUserPreferences(args[2]); + if (!preferences) { + console.error(`Unrecognized user preferences in verify.importFixAtPosition: ${args[2].getText()}`); + return undefined; + } + } + return [{ + kind: "verifyImportFixAtPosition", + expectedTexts, + preferences: preferences || "nil /*preferences*/", + }]; +} + const completionConstants = new Map([ ["completion.globals", "CompletionGlobals"], ["completion.globalTypes", "CompletionGlobalTypes"], @@ -1240,6 +1283,21 @@ function parseUserPreferences(arg: ts.ObjectLiteralExpression): string | undefin case "quotePreference": preferences.push(`QuotePreference: lsutil.QuotePreference(${prop.initializer.getText()})`); break; + case "autoImportFileExcludePatterns": + const arrayArg = getArrayLiteralExpression(prop.initializer); + if (!arrayArg) { + return undefined; + } + const patterns: string[] = []; + for (const elem of arrayArg.elements) { + const strElem = getStringLiteralLike(elem); + if (!strElem) { + return undefined; + } + patterns.push(getGoStringLiteral(strElem.text)); + } + preferences.push(`AutoImportFileExcludePatterns: []string{${patterns.join(", ")}}`); + break; case "includeInlayParameterNameHints": let paramHint; if (!ts.isStringLiteralLike(prop.initializer)) { @@ -1701,6 +1759,12 @@ interface VerifyBaselineInlayHintsCmd { preferences: string; } +interface VerifyImportFixAtPositionCmd { + kind: "verifyImportFixAtPosition"; + expectedTexts: string[]; + preferences: string; +} + interface GoToCmd { kind: "goTo"; // !!! `selectRange` and `rangeStart` require parsing variables and `test.ranges()[n]` @@ -1739,7 +1803,8 @@ type Cmd = | VerifyQuickInfoCmd | VerifyBaselineRenameCmd | VerifyRenameInfoCmd - | VerifyBaselineInlayHintsCmd; + | VerifyBaselineInlayHintsCmd + | VerifyImportFixAtPositionCmd; function generateVerifyCompletions({ marker, args, isNewIdentifierLocation, andApplyCodeActionArgs }: VerifyCompletionsCmd): string { let expectedList: string; @@ -1840,6 +1905,14 @@ function generateBaselineInlayHints({ span, preferences }: VerifyBaselineInlayHi return `f.VerifyBaselineInlayHints(t, ${span}, ${preferences})`; } +function generateImportFixAtPosition({ expectedTexts, preferences }: VerifyImportFixAtPositionCmd): string { + // Handle empty array case + if (expectedTexts.length === 1 && expectedTexts[0] === "") { + return `f.VerifyImportFixAtPosition(t, []string{}, ${preferences})`; + } + return `f.VerifyImportFixAtPosition(t, []string{\n${expectedTexts.join(",\n")},\n}, ${preferences})`; +} + function generateCmd(cmd: Cmd): string { switch (cmd.kind) { case "verifyCompletions": @@ -1878,6 +1951,8 @@ function generateCmd(cmd: Cmd): string { return `f.VerifyRenameFailed(t, ${cmd.preferences})`; case "verifyBaselineInlayHints": return generateBaselineInlayHints(cmd); + case "verifyImportFixAtPosition": + return generateImportFixAtPosition(cmd); default: let neverCommand: never = cmd; throw new Error(`Unknown command kind: ${neverCommand as Cmd["kind"]}`); diff --git a/internal/fourslash/_scripts/failingTests.txt b/internal/fourslash/_scripts/failingTests.txt index 29254c11a0..7e671cbede 100644 --- a/internal/fourslash/_scripts/failingTests.txt +++ b/internal/fourslash/_scripts/failingTests.txt @@ -4,13 +4,19 @@ TestArgumentsAreAvailableAfterEditsAtEndOfFunction TestAugmentedTypesClass1 TestAugmentedTypesClass3Fourslash TestAutoImportCompletionAmbientMergedModule1 -TestAutoImportCompletionExportEqualsWithDefault1 TestAutoImportCompletionExportListAugmentation1 TestAutoImportCompletionExportListAugmentation2 TestAutoImportCompletionExportListAugmentation3 TestAutoImportCompletionExportListAugmentation4 +TestAutoImportCrossProject_symlinks_stripSrc +TestAutoImportCrossProject_symlinks_toDist +TestAutoImportCrossProject_symlinks_toSrc TestAutoImportFileExcludePatterns3 +TestAutoImportJsDocImport1 +TestAutoImportNodeNextJSRequire TestAutoImportPathsAliasesAndBarrels +TestAutoImportPnpm +TestAutoImportProvider4 TestAutoImportProvider_exportMap1 TestAutoImportProvider_exportMap2 TestAutoImportProvider_exportMap3 @@ -22,9 +28,17 @@ TestAutoImportProvider_exportMap8 TestAutoImportProvider_exportMap9 TestAutoImportProvider_globalTypingsCache TestAutoImportProvider_namespaceSameNameAsIntrinsic +TestAutoImportProvider_pnpm TestAutoImportProvider_wildcardExports1 TestAutoImportProvider_wildcardExports2 TestAutoImportProvider_wildcardExports3 +TestAutoImportSortCaseSensitivity1 +TestAutoImportSymlinkCaseSensitive +TestAutoImportTypeImport1 +TestAutoImportTypeImport2 +TestAutoImportTypeImport3 +TestAutoImportTypeImport4 +TestAutoImportTypeOnlyPreferred2 TestAutoImportVerbatimTypeOnly1 TestBestCommonTypeObjectLiterals TestBestCommonTypeObjectLiterals1 @@ -57,7 +71,6 @@ TestCompletionForStringLiteralNonrelativeImport18 TestCompletionForStringLiteralNonrelativeImport2 TestCompletionForStringLiteralNonrelativeImport3 TestCompletionForStringLiteralNonrelativeImport4 -TestCompletionForStringLiteralNonrelativeImport7 TestCompletionForStringLiteralNonrelativeImport8 TestCompletionForStringLiteralNonrelativeImport9 TestCompletionForStringLiteralNonrelativeImportTypings1 @@ -148,10 +161,7 @@ TestCompletionsGenericTypeWithMultipleBases1 TestCompletionsImportOrExportSpecifier TestCompletionsImport_default_alreadyExistedWithRename TestCompletionsImport_default_anonymous -TestCompletionsImport_default_symbolName TestCompletionsImport_details_withMisspelledName -TestCompletionsImport_exportEquals -TestCompletionsImport_exportEquals_anonymous TestCompletionsImport_exportEquals_global TestCompletionsImport_filteredByInvalidPackageJson_direct TestCompletionsImport_filteredByPackageJson_direct @@ -159,11 +169,9 @@ TestCompletionsImport_filteredByPackageJson_nested TestCompletionsImport_filteredByPackageJson_peerDependencies TestCompletionsImport_filteredByPackageJson_typesImplicit TestCompletionsImport_filteredByPackageJson_typesOnly -TestCompletionsImport_importType TestCompletionsImport_jsxOpeningTagImportDefault TestCompletionsImport_mergedReExport TestCompletionsImport_named_didNotExistBefore -TestCompletionsImport_named_namespaceImportExists TestCompletionsImport_noSemicolons TestCompletionsImport_packageJsonImportsPreference TestCompletionsImport_quoteStyle @@ -173,7 +181,6 @@ TestCompletionsImport_require_addToExisting TestCompletionsImport_typeOnly TestCompletionsImport_umdDefaultNoCrash1 TestCompletionsImport_uriStyleNodeCoreModules2 -TestCompletionsImport_weirdDefaultSynthesis TestCompletionsImport_windowsPathsProjectRelative TestCompletionsInExport TestCompletionsInExport_moduleBlock @@ -269,11 +276,48 @@ TestImportCompletions_importsMap2 TestImportCompletions_importsMap3 TestImportCompletions_importsMap4 TestImportCompletions_importsMap5 +TestImportFixesGlobalTypingsCache +TestImportNameCodeFixConvertTypeOnly1 +TestImportNameCodeFixExistingImport10 +TestImportNameCodeFixExistingImport11 +TestImportNameCodeFixExistingImport8 +TestImportNameCodeFixExistingImport9 +TestImportNameCodeFixExistingImportEquals0 TestImportNameCodeFixExportAsDefault +TestImportNameCodeFixNewImportAmbient1 +TestImportNameCodeFixNewImportAmbient3 +TestImportNameCodeFixNewImportBaseUrl0 +TestImportNameCodeFixNewImportBaseUrl1 +TestImportNameCodeFixNewImportBaseUrl2 +TestImportNameCodeFixNewImportFile2 +TestImportNameCodeFixNewImportFileQuoteStyle0 +TestImportNameCodeFixNewImportFileQuoteStyle1 +TestImportNameCodeFixNewImportFileQuoteStyle2 +TestImportNameCodeFixNewImportFileQuoteStyleMixed0 +TestImportNameCodeFixNewImportFileQuoteStyleMixed1 +TestImportNameCodeFixNewImportRootDirs0 +TestImportNameCodeFixNewImportTypeRoots1 +TestImportNameCodeFix_HeaderComment1 +TestImportNameCodeFix_HeaderComment2 +TestImportNameCodeFix_avoidRelativeNodeModules +TestImportNameCodeFix_fileWithNoTrailingNewline +TestImportNameCodeFix_importType1 +TestImportNameCodeFix_importType2 +TestImportNameCodeFix_importType4 +TestImportNameCodeFix_importType7 +TestImportNameCodeFix_importType8 +TestImportNameCodeFix_jsx1 +TestImportNameCodeFix_order +TestImportNameCodeFix_order2 +TestImportNameCodeFix_pnpm1 +TestImportNameCodeFix_preferBaseUrl +TestImportNameCodeFix_reExportDefault +TestImportNameCodeFix_symlink +TestImportNameCodeFix_trailingComma +TestImportNameCodeFix_withJson TestImportTypeCompletions1 TestImportTypeCompletions3 TestImportTypeCompletions4 -TestImportTypeCompletions5 TestImportTypeCompletions6 TestImportTypeCompletions7 TestImportTypeCompletions8 @@ -282,7 +326,6 @@ TestIndexerReturnTypes1 TestIndirectClassInstantiation TestInstanceTypesForGenericType1 TestJavascriptModules20 -TestJavascriptModules21 TestJavascriptModulesTypeImport TestJsDocAugments TestJsDocExtends @@ -327,6 +370,8 @@ TestMemberListInReopenedEnum TestMemberListInWithBlock TestMemberListOfExportedClass TestMemberListOnContextualThis +TestModuleNodeNextAutoImport2 +TestModuleNodeNextAutoImport3 TestNgProxy1 TestNoQuickInfoForLabel TestNoQuickInfoInWhitespace @@ -516,8 +561,6 @@ TestTripleSlashRefPathCompletionExtensionsAllowJSFalse TestTripleSlashRefPathCompletionExtensionsAllowJSTrue TestTripleSlashRefPathCompletionHiddenFile TestTripleSlashRefPathCompletionRootdirs -TestTsxCompletion14 -TestTsxCompletion15 TestTsxCompletionNonTagLessThan TestTsxQuickInfo1 TestTsxQuickInfo4 diff --git a/internal/fourslash/fourslash.go b/internal/fourslash/fourslash.go index 0665e58a24..4a1a78d139 100644 --- a/internal/fourslash/fourslash.go +++ b/internal/fourslash/fourslash.go @@ -148,6 +148,29 @@ func NewFourslash(t *testing.T, capabilities *lsproto.ClientCapabilities, conten } harnessutil.SetCompilerOptionsFromTestConfig(t, testData.GlobalOptions, compilerOptions, rootDir) + // Skip tests with deprecated/removed compiler options + if compilerOptions.BaseUrl != "" { + t.Skipf("Test uses deprecated 'baseUrl' option") + } + if compilerOptions.OutFile != "" { + t.Skipf("Test uses deprecated 'outFile' option") + } + if compilerOptions.Module == core.ModuleKindAMD { + t.Skipf("Test uses deprecated 'module: AMD' option") + } + if compilerOptions.Module == core.ModuleKindSystem { + t.Skipf("Test uses deprecated 'module: System' option") + } + if compilerOptions.Module == core.ModuleKindUMD { + t.Skipf("Test uses deprecated 'module: UMD' option") + } + if compilerOptions.ModuleResolution == core.ModuleResolutionKindClassic { + t.Skipf("Test uses deprecated 'moduleResolution: Classic' option") + } + if compilerOptions.AllowSyntheticDefaultImports == core.TSFalse { + t.Skipf("Test uses unsupported 'allowSyntheticDefaultImports: false' option") + } + inputReader, inputWriter := newLSPPipe() outputReader, outputWriter := newLSPPipe() fs := bundled.WrapFS(vfstest.FromMap(testfs, true /*useCaseSensitiveFileNames*/)) @@ -1032,6 +1055,141 @@ func (f *FourslashTest) VerifyApplyCodeActionFromCompletion(t *testing.T, marker } } +func (f *FourslashTest) VerifyImportFixAtPosition(t *testing.T, expectedTexts []string, preferences *lsutil.UserPreferences) { + fileName := f.activeFilename + ranges := f.Ranges() + var filteredRanges []*RangeMarker + for _, r := range ranges { + if r.FileName() == fileName { + filteredRanges = append(filteredRanges, r) + } + } + if len(filteredRanges) > 1 { + t.Fatalf("Exactly one range should be specified in the testfile.") + } + var rangeMarker *RangeMarker + if len(filteredRanges) == 1 { + rangeMarker = filteredRanges[0] + } + + if preferences != nil { + reset := f.ConfigureWithReset(t, preferences) + defer reset() + } + + // Get diagnostics at the current position to find errors that need import fixes + diagParams := &lsproto.DocumentDiagnosticParams{ + TextDocument: lsproto.TextDocumentIdentifier{ + Uri: lsconv.FileNameToDocumentURI(f.activeFilename), + }, + } + _, diagResult, diagOk := sendRequest(t, f, lsproto.TextDocumentDiagnosticInfo, diagParams) + if !diagOk { + t.Fatalf("Failed to get diagnostics") + } + + var diagnostics []*lsproto.Diagnostic + if diagResult.FullDocumentDiagnosticReport != nil && diagResult.FullDocumentDiagnosticReport.Items != nil { + diagnostics = diagResult.FullDocumentDiagnosticReport.Items + } + + params := &lsproto.CodeActionParams{ + TextDocument: lsproto.TextDocumentIdentifier{ + Uri: lsconv.FileNameToDocumentURI(f.activeFilename), + }, + Range: lsproto.Range{ + Start: f.currentCaretPosition, + End: f.currentCaretPosition, + }, + Context: &lsproto.CodeActionContext{ + Diagnostics: diagnostics, + }, + } + resMsg, result, resultOk := sendRequest(t, f, lsproto.TextDocumentCodeActionInfo, params) + if resMsg == nil { + t.Fatalf("Nil response received for code action request at pos %v", f.currentCaretPosition) + } + if !resultOk { + t.Fatalf("Unexpected code action response type at pos %v: %T", f.currentCaretPosition, resMsg.AsResponse().Result) + } + + // Find all auto-import code actions (fixes with fixId/fixName related to imports) + var importActions []*lsproto.CodeAction + if result.CommandOrCodeActionArray != nil { + for _, item := range *result.CommandOrCodeActionArray { + if item.CodeAction != nil && item.CodeAction.Kind != nil && *item.CodeAction.Kind == lsproto.CodeActionKindQuickFix { + importActions = append(importActions, item.CodeAction) + } + } + } + + if len(importActions) == 0 { + if len(expectedTexts) != 0 { + t.Fatalf("No codefixes returned.") + } + return + } + + // Save the original content before any edits + script := f.getScriptInfo(f.activeFilename) + originalContent := script.content + + // For each import action, apply it and check the result + actualTextArray := make([]string, 0, len(importActions)) + for _, action := range importActions { + // Apply the code action + var edits []*lsproto.TextEdit + if action.Edit != nil && action.Edit.Changes != nil { + if len(*action.Edit.Changes) != 1 { + t.Fatalf("Expected exactly 1 change, got %d", len(*action.Edit.Changes)) + } + for uri, changeEdits := range *action.Edit.Changes { + if uri != lsconv.FileNameToDocumentURI(f.activeFilename) { + t.Fatalf("Expected change to file %s, got %s", f.activeFilename, uri) + } + edits = changeEdits + f.applyTextEdits(t, changeEdits) + } + } + + // Get the result text + var text string + if rangeMarker != nil { + text = f.getRangeText(rangeMarker) + } else { + text = f.getScriptInfo(f.activeFilename).content + } + actualTextArray = append(actualTextArray, text) + + // Undo changes to perform next fix + for _, textChange := range edits { + start := int(f.converters.LineAndCharacterToPosition(script, textChange.Range.Start)) + end := int(f.converters.LineAndCharacterToPosition(script, textChange.Range.End)) + deletedText := originalContent[start:end] + insertedText := textChange.NewText + f.editScriptAndUpdateMarkers(t, f.activeFilename, start, start+len(insertedText), deletedText) + } + } + + // Compare results + if len(expectedTexts) != len(actualTextArray) { + var actualJoined strings.Builder + for i, actual := range actualTextArray { + if i > 0 { + actualJoined.WriteString("\n\n" + strings.Repeat("-", 20) + "\n\n") + } + actualJoined.WriteString(actual) + } + t.Fatalf("Expected %d import fixes, got %d:\n\n%s", len(expectedTexts), len(actualTextArray), actualJoined.String()) + } + for i, expected := range expectedTexts { + actual := actualTextArray[i] + if expected != actual { + t.Fatalf("Import fix at index %d doesn't match.\nExpected:\n%s\n\nActual:\n%s", i, expected, actual) + } + } +} + func (f *FourslashTest) VerifyBaselineFindAllReferences( t *testing.T, markers ...string, diff --git a/internal/fourslash/test_parser.go b/internal/fourslash/test_parser.go index 3215f5619f..147824081b 100644 --- a/internal/fourslash/test_parser.go +++ b/internal/fourslash/test_parser.go @@ -93,10 +93,13 @@ func ParseTestData(t *testing.T, contents string, fileName string) TestData { var markers []*Marker var ranges []*RangeMarker - filesWithMarker, symlinks, _, globalOptions, e := testrunner.ParseTestFilesAndSymlinks( + filesWithMarker, symlinks, _, globalOptions, e := testrunner.ParseTestFilesAndSymlinksWithOptions( contents, fileName, parseFileContent, + testrunner.ParseTestFilesOptions{ + AllowImplicitFirstFile: true, + }, ) if e != nil { t.Fatalf("Error parsing fourslash data: %s", e.Error()) diff --git a/internal/fourslash/tests/gen/autoImportCompletionExportEqualsWithDefault1_test.go b/internal/fourslash/tests/gen/autoImportCompletionExportEqualsWithDefault1_test.go index e5036d3293..c87ea24c91 100644 --- a/internal/fourslash/tests/gen/autoImportCompletionExportEqualsWithDefault1_test.go +++ b/internal/fourslash/tests/gen/autoImportCompletionExportEqualsWithDefault1_test.go @@ -12,7 +12,7 @@ import ( func TestAutoImportCompletionExportEqualsWithDefault1(t *testing.T) { t.Parallel() - t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `// @strict: true // @module: commonjs diff --git a/internal/fourslash/tests/gen/autoImportCrossProject_paths_sharedOutDir_test.go b/internal/fourslash/tests/gen/autoImportCrossProject_paths_sharedOutDir_test.go new file mode 100644 index 0000000000..1c6c889718 --- /dev/null +++ b/internal/fourslash/tests/gen/autoImportCrossProject_paths_sharedOutDir_test.go @@ -0,0 +1,50 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestAutoImportCrossProject_paths_sharedOutDir(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /home/src/workspaces/project/tsconfig.base.json +{ + "compilerOptions": { + "module": "commonjs", + "baseUrl": ".", + "paths": { + "packages/*": ["./packages/*"] + } + } +} +// @Filename: /home/src/workspaces/project/packages/app/tsconfig.json +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { "outDir": "../../dist/packages/app" }, + "references": [{ "path": "../dep" }] +} +// @Filename: /home/src/workspaces/project/packages/app/index.ts +dep/**/ +// @Filename: /home/src/workspaces/project/packages/app/utils.ts +import "packages/dep"; +// @Filename: /home/src/workspaces/project/packages/dep/tsconfig.json +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { "outDir": "../../dist/packages/dep" } +} +// @Filename: /home/src/workspaces/project/packages/dep/index.ts +import "./sub/folder"; +// @Filename: /home/src/workspaces/project/packages/dep/sub/folder/index.ts +export const dep = 0;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { dep } from "packages/dep/sub/folder"; + +dep`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/autoImportCrossProject_paths_stripSrc_test.go b/internal/fourslash/tests/gen/autoImportCrossProject_paths_stripSrc_test.go new file mode 100644 index 0000000000..d55afda187 --- /dev/null +++ b/internal/fourslash/tests/gen/autoImportCrossProject_paths_stripSrc_test.go @@ -0,0 +1,60 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestAutoImportCrossProject_paths_stripSrc(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /home/src/workspaces/project/packages/app/package.json +{ "name": "app", "dependencies": { "dep": "*" } } +// @Filename: /home/src/workspaces/project/packages/app/tsconfig.json +{ + "compilerOptions": { + "module": "commonjs", + "outDir": "dist", + "rootDir": "src", + "baseUrl": ".", + "paths": { + "dep": ["../dep/src/main"], + "dep/*": ["../dep/src/*"] + } + } + "references": [{ "path": "../dep" }] +} +// @Filename: /home/src/workspaces/project/packages/app/src/index.ts +dep1/*1*/; +// @Filename: /home/src/workspaces/project/packages/app/src/utils.ts +dep2/*2*/; +// @Filename: /home/src/workspaces/project/packages/app/src/a.ts +import "dep"; +// @Filename: /home/src/workspaces/project/packages/dep/package.json +{ "name": "dep", "main": "dist/main.js", "types": "dist/main.d.ts" } +// @Filename: /home/src/workspaces/project/packages/dep/tsconfig.json +{ + "compilerOptions": { "outDir": "dist", "rootDir": "src", "module": "commonjs" } +} +// @Filename: /home/src/workspaces/project/packages/dep/src/main.ts +import "./sub/folder"; +export const dep1 = 0; +// @Filename: /home/src/workspaces/project/packages/dep/src/sub/folder/index.ts +export const dep2 = 0;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "1") + f.VerifyImportFixAtPosition(t, []string{ + `import { dep1 } from "dep"; + +dep1;`, + }, nil /*preferences*/) + f.GoToMarker(t, "2") + f.VerifyImportFixAtPosition(t, []string{ + `import { dep2 } from "dep/sub/folder"; + +dep2;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/autoImportCrossProject_paths_toDist_test.go b/internal/fourslash/tests/gen/autoImportCrossProject_paths_toDist_test.go new file mode 100644 index 0000000000..41428f10d4 --- /dev/null +++ b/internal/fourslash/tests/gen/autoImportCrossProject_paths_toDist_test.go @@ -0,0 +1,60 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestAutoImportCrossProject_paths_toDist(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /home/src/workspaces/project/packages/app/package.json +{ "name": "app", "dependencies": { "dep": "*" } } +// @Filename: /home/src/workspaces/project/packages/app/tsconfig.json +{ + "compilerOptions": { + "module": "commonjs", + "outDir": "dist", + "rootDir": "src", + "baseUrl": ".", + "paths": { + "dep": ["../dep/src/main"], + "dep/dist/*": ["../dep/src/*"] + } + } + "references": [{ "path": "../dep" }] +} +// @Filename: /home/src/workspaces/project/packages/app/src/index.ts +dep1/*1*/; +// @Filename: /home/src/workspaces/project/packages/app/src/utils.ts +dep2/*2*/; +// @Filename: /home/src/workspaces/project/packages/app/src/a.ts +import "dep"; +// @Filename: /home/src/workspaces/project/packages/dep/package.json +{ "name": "dep", "main": "dist/main.js", "types": "dist/main.d.ts" } +// @Filename: /home/src/workspaces/project/packages/dep/tsconfig.json +{ + "compilerOptions": { "outDir": "dist", "rootDir": "src", "module": "commonjs" } +} +// @Filename: /home/src/workspaces/project/packages/dep/src/main.ts +import "./sub/folder"; +export const dep1 = 0; +// @Filename: /home/src/workspaces/project/packages/dep/src/sub/folder/index.ts +export const dep2 = 0;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "1") + f.VerifyImportFixAtPosition(t, []string{ + `import { dep1 } from "dep"; + +dep1;`, + }, nil /*preferences*/) + f.GoToMarker(t, "2") + f.VerifyImportFixAtPosition(t, []string{ + `import { dep2 } from "dep/dist/sub/folder"; + +dep2;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/autoImportCrossProject_paths_toSrc_test.go b/internal/fourslash/tests/gen/autoImportCrossProject_paths_toSrc_test.go new file mode 100644 index 0000000000..a3d25d2a86 --- /dev/null +++ b/internal/fourslash/tests/gen/autoImportCrossProject_paths_toSrc_test.go @@ -0,0 +1,60 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestAutoImportCrossProject_paths_toSrc(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /home/src/workspaces/project/packages/app/package.json +{ "name": "app", "dependencies": { "dep": "*" } } +// @Filename: /home/src/workspaces/project/packages/app/tsconfig.json +{ + "compilerOptions": { + "module": "commonjs", + "outDir": "dist", + "rootDir": "src", + "baseUrl": ".", + "paths": { + "dep": ["../dep/src/main"], + "dep/*": ["../dep/*"] + } + } + "references": [{ "path": "../dep" }] +} +// @Filename: /home/src/workspaces/project/packages/app/src/index.ts +dep1/*1*/; +// @Filename: /home/src/workspaces/project/packages/app/src/utils.ts +dep2/*2*/; +// @Filename: /home/src/workspaces/project/packages/app/src/a.ts +import "dep"; +// @Filename: /home/src/workspaces/project/packages/dep/package.json +{ "name": "dep", "main": "dist/main.js", "types": "dist/main.d.ts" } +// @Filename: /home/src/workspaces/project/packages/dep/tsconfig.json +{ + "compilerOptions": { "outDir": "dist", "rootDir": "src", "module": "commonjs" } +} +// @Filename: /home/src/workspaces/project/packages/dep/src/main.ts +import "./sub/folder"; +export const dep1 = 0; +// @Filename: /home/src/workspaces/project/packages/dep/src/sub/folder/index.ts +export const dep2 = 0;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "1") + f.VerifyImportFixAtPosition(t, []string{ + `import { dep1 } from "dep"; + +dep1;`, + }, nil /*preferences*/) + f.GoToMarker(t, "2") + f.VerifyImportFixAtPosition(t, []string{ + `import { dep2 } from "dep/src/sub/folder"; + +dep2;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/autoImportCrossProject_symlinks_stripSrc_test.go b/internal/fourslash/tests/gen/autoImportCrossProject_symlinks_stripSrc_test.go new file mode 100644 index 0000000000..e369963053 --- /dev/null +++ b/internal/fourslash/tests/gen/autoImportCrossProject_symlinks_stripSrc_test.go @@ -0,0 +1,49 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestAutoImportCrossProject_symlinks_stripSrc(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /home/src/workspaces/project/packages/app/package.json +{ "name": "app", "dependencies": { "dep": "*" } } +// @Filename: /home/src/workspaces/project/packages/app/tsconfig.json +{ + "compilerOptions": { + "module": "commonjs", + "outDir": "dist", + "rootDir": "src", + "baseUrl": ".", + "paths": { + "dep/*": ["../dep/src/*"] + } + } + "references": [{ "path": "../dep" }] +} +// @Filename: /home/src/workspaces/project/packages/app/src/index.ts +dep/**/ +// @Filename: /home/src/workspaces/project/packages/dep/package.json +{ "name": "dep", "main": "dist/index.js", "types": "dist/index.d.ts" } +// @Filename: /home/src/workspaces/project/packages/dep/tsconfig.json +{ + "compilerOptions": { "outDir": "dist", "rootDir": "src", "module": "commonjs" } +} +// @Filename: /home/src/workspaces/project/packages/dep/src/index.ts +import "./sub/folder"; +// @Filename: /home/src/workspaces/project/packages/dep/src/sub/folder/index.ts +export const dep = 0; +// @link: /home/src/workspaces/project/packages/dep -> /home/src/workspaces/project/packages/app/node_modules/dep` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { dep } from "dep/sub/folder"; + +dep`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/autoImportCrossProject_symlinks_toDist_test.go b/internal/fourslash/tests/gen/autoImportCrossProject_symlinks_toDist_test.go new file mode 100644 index 0000000000..b577b3b28d --- /dev/null +++ b/internal/fourslash/tests/gen/autoImportCrossProject_symlinks_toDist_test.go @@ -0,0 +1,49 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestAutoImportCrossProject_symlinks_toDist(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /home/src/workspaces/project/packages/app/package.json +{ "name": "app", "dependencies": { "dep": "*" } } +// @Filename: /home/src/workspaces/project/packages/app/tsconfig.json +{ + "compilerOptions": { + "module": "commonjs", + "outDir": "dist", + "rootDir": "src", + "baseUrl": ".", + "paths": { + "dep/dist/*": ["../dep/src/*"] + } + } + "references": [{ "path": "../dep" }] +} +// @Filename: /home/src/workspaces/project/packages/app/src/index.ts +dep/**/ +// @Filename: /home/src/workspaces/project/packages/dep/package.json +{ "name": "dep", "main": "dist/index.js", "types": "dist/index.d.ts" } +// @Filename: /home/src/workspaces/project/packages/dep/tsconfig.json +{ + "compilerOptions": { "outDir": "dist", "rootDir": "src", "module": "commonjs" } +} +// @Filename: /home/src/workspaces/project/packages/dep/src/index.ts +import "./sub/folder"; +// @Filename: /home/src/workspaces/project/packages/dep/src/sub/folder/index.ts +export const dep = 0; +// @link: /home/src/workspaces/project/packages/dep -> /home/src/workspaces/project/packages/app/node_modules/dep` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { dep } from "dep/dist/sub/folder"; + +dep`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/autoImportCrossProject_symlinks_toSrc_test.go b/internal/fourslash/tests/gen/autoImportCrossProject_symlinks_toSrc_test.go new file mode 100644 index 0000000000..17ae9884b0 --- /dev/null +++ b/internal/fourslash/tests/gen/autoImportCrossProject_symlinks_toSrc_test.go @@ -0,0 +1,46 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestAutoImportCrossProject_symlinks_toSrc(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /home/src/workspaces/project/packages/app/package.json +{ "name": "app", "dependencies": { "dep": "*" } } +// @Filename: /home/src/workspaces/project/packages/app/tsconfig.json +{ + "compilerOptions": { + "module": "commonjs", + "outDir": "dist", + "rootDir": "src", + "baseUrl": "." + } + "references": [{ "path": "../dep" }] +} +// @Filename: /home/src/workspaces/project/packages/app/src/index.ts +dep/**/ +// @Filename: /home/src/workspaces/project/packages/dep/package.json +{ "name": "dep", "main": "dist/index.js", "types": "dist/index.d.ts" } +// @Filename: /home/src/workspaces/project/packages/dep/tsconfig.json +{ + "compilerOptions": { "outDir": "dist", "rootDir": "src", "module": "commonjs" } +} +// @Filename: /home/src/workspaces/project/packages/dep/src/index.ts +import "./sub/folder"; +// @Filename: /home/src/workspaces/project/packages/dep/src/sub/folder/index.ts +export const dep = 0; +// @link: /home/src/workspaces/project/packages/dep -> /home/src/workspaces/project/packages/app/node_modules/dep` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { dep } from "dep/src/sub/folder"; + +dep`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/autoImportJsDocImport1_test.go b/internal/fourslash/tests/gen/autoImportJsDocImport1_test.go new file mode 100644 index 0000000000..38b88edb01 --- /dev/null +++ b/internal/fourslash/tests/gen/autoImportJsDocImport1_test.go @@ -0,0 +1,50 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestAutoImportJsDocImport1(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @verbatimModuleSyntax: true +// @target: esnext +// @allowJs: true +// @checkJs: true +// @Filename: /foo.ts + export const A = 1; + export type B = { x: number }; + export type C = 1; + export class D { y: string } +// @Filename: /test.js +/** + * @import { A, D, C } from "./foo" + */ + +/** + * @param { typeof A } a + * @param { B/**/ | C } b + * @param { C } c + * @param { D } d + */ +export function f(a, b, c, d) { }` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `/** + * @import { A, D, C, B } from "./foo" + */ + +/** + * @param { typeof A } a + * @param { B | C } b + * @param { C } c + * @param { D } d + */ +export function f(a, b, c, d) { }`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/autoImportNodeNextJSRequire_test.go b/internal/fourslash/tests/gen/autoImportNodeNextJSRequire_test.go new file mode 100644 index 0000000000..0afe25bc07 --- /dev/null +++ b/internal/fourslash/tests/gen/autoImportNodeNextJSRequire_test.go @@ -0,0 +1,35 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestAutoImportNodeNextJSRequire(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @module: node18 +// @allowJs: true +// @checkJs: true +// @noEmit: true +// @Filename: /matrix.js +exports.variants = []; +// @Filename: /main.js +exports.dedupeLines = data => { + variants/**/ +} +// @Filename: /totally-irrelevant-no-way-this-changes-things-right.js +export default 0;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/main.js") + f.VerifyImportFixAtPosition(t, []string{ + `const { variants } = require("./matrix") + +exports.dedupeLines = data => { + variants +}`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/autoImportPackageJsonFilterExistingImport2_test.go b/internal/fourslash/tests/gen/autoImportPackageJsonFilterExistingImport2_test.go new file mode 100644 index 0000000000..7d881d0e9f --- /dev/null +++ b/internal/fourslash/tests/gen/autoImportPackageJsonFilterExistingImport2_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestAutoImportPackageJsonFilterExistingImport2(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @module: preserve +// @Filename: /home/src/workspaces/project/node_modules/@types/react/index.d.ts +export declare function useMemo(): void; +export declare function useState(): void; +// @Filename: /home/src/workspaces/project/package.json +{} +// @Filename: /home/src/workspaces/project/index.ts +useMemo/**/` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{}, nil /*preferences*/) + f.GoToBOF(t) + f.InsertLine(t, "import { useState } from \"react\";") + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { useMemo, useState } from "react"; +useMemo`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/autoImportPackageJsonFilterExistingImport3_test.go b/internal/fourslash/tests/gen/autoImportPackageJsonFilterExistingImport3_test.go new file mode 100644 index 0000000000..8e8144e077 --- /dev/null +++ b/internal/fourslash/tests/gen/autoImportPackageJsonFilterExistingImport3_test.go @@ -0,0 +1,34 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestAutoImportPackageJsonFilterExistingImport3(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @module: preserve +// @Filename: /home/src/workspaces/project/node_modules/@types/node/index.d.ts +declare module "node:fs" { + export function readFile(): void; + export function writeFile(): void; +} +// @Filename: /home/src/workspaces/project/package.json +{} +// @Filename: /home/src/workspaces/project/index.ts +readFile/**/` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{}, nil /*preferences*/) + f.GoToBOF(t) + f.InsertLine(t, "import { writeFile } from \"node:fs\";") + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { readFile, writeFile } from "node:fs"; +readFile`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/autoImportPnpm_test.go b/internal/fourslash/tests/gen/autoImportPnpm_test.go new file mode 100644 index 0000000000..1f05b32afc --- /dev/null +++ b/internal/fourslash/tests/gen/autoImportPnpm_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestAutoImportPnpm(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /tsconfig.json +{ "compilerOptions": { "module": "commonjs" } } +// @Filename: /node_modules/.pnpm/mobx@6.0.4/node_modules/mobx/package.json +{ "types": "dist/mobx.d.ts" } +// @Filename: /node_modules/.pnpm/mobx@6.0.4/node_modules/mobx/dist/mobx.d.ts +export declare function autorun(): void; +// @Filename: /index.ts +autorun/**/ +// @Filename: /utils.ts +import "mobx"; +// @link: /node_modules/.pnpm/mobx@6.0.4/node_modules/mobx -> /node_modules/mobx +// @link: /node_modules/.pnpm/mobx@6.0.4/node_modules/mobx -> /node_modules/.pnpm/cool-mobx-dependent@1.2.3/node_modules/mobx` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { autorun } from "mobx"; + +autorun`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/autoImportProvider4_test.go b/internal/fourslash/tests/gen/autoImportProvider4_test.go new file mode 100644 index 0000000000..44dcb137e8 --- /dev/null +++ b/internal/fourslash/tests/gen/autoImportProvider4_test.go @@ -0,0 +1,34 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestAutoImportProvider4(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /home/src/workspaces/project/a/package.json +{ "dependencies": { "b": "*" } } +// @Filename: /home/src/workspaces/project/a/tsconfig.json +{ "compilerOptions": { "module": "commonjs", "target": "esnext" }, "references": [{ "path": "../b" }] } +// @Filename: /home/src/workspaces/project/a/index.ts +new Shape/**/ +// @Filename: /home/src/workspaces/project/b/package.json +{ "types": "out/index.d.ts" } +// @Filename: /home/src/workspaces/project/b/tsconfig.json +{ "compilerOptions": { "outDir": "out", "composite": true } } +// @Filename: /home/src/workspaces/project/b/index.ts +export class Shape {} +// @link: /home/src/workspaces/project/b -> /home/src/workspaces/project/a/node_modules/b` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { Shape } from "b"; + +new Shape`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/autoImportProvider5_test.go b/internal/fourslash/tests/gen/autoImportProvider5_test.go new file mode 100644 index 0000000000..147ea11146 --- /dev/null +++ b/internal/fourslash/tests/gen/autoImportProvider5_test.go @@ -0,0 +1,34 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestAutoImportProvider5(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /home/src/workspaces/project/package.json +{ "dependencies": { "react-hook-form": "*" } } +// @Filename: /home/src/workspaces/project/node_modules/react-hook-form/package.json +{ "types": "dist/index.d.ts" } +// @Filename: /home/src/workspaces/project/node_modules/react-hook-form/dist/index.d.ts +export * from "./useForm"; +// @Filename: /home/src/workspaces/project/node_modules/react-hook-form/dist/useForm.d.ts +export declare function useForm(): void; +// @Filename: /home/src/workspaces/project/index.ts +useForm/**/` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { useForm } from "react-hook-form"; + +useForm`, + `import { useForm } from "react-hook-form/dist/useForm"; + +useForm`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/autoImportProvider_pnpm_test.go b/internal/fourslash/tests/gen/autoImportProvider_pnpm_test.go new file mode 100644 index 0000000000..d2b4646c72 --- /dev/null +++ b/internal/fourslash/tests/gen/autoImportProvider_pnpm_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestAutoImportProvider_pnpm(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /home/src/workspaces/project/tsconfig.json +{ "compilerOptions": { "module": "commonjs" } } +// @Filename: /home/src/workspaces/project/package.json +{ "dependencies": { "mobx": "*" } } +// @Filename: /home/src/workspaces/project/node_modules/.pnpm/mobx@6.0.4/node_modules/mobx/package.json +{ "types": "dist/mobx.d.ts" } +// @Filename: /home/src/workspaces/project/node_modules/.pnpm/mobx@6.0.4/node_modules/mobx/dist/mobx.d.ts +export declare function autorun(): void; +// @Filename: /home/src/workspaces/project/index.ts +autorun/**/ +// @link: /home/src/workspaces/project/node_modules/.pnpm/mobx@6.0.4/node_modules/mobx -> /home/src/workspaces/project/node_modules/mobx` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { autorun } from "mobx"; + +autorun`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/autoImportSortCaseSensitivity1_test.go b/internal/fourslash/tests/gen/autoImportSortCaseSensitivity1_test.go new file mode 100644 index 0000000000..020cfb9d4e --- /dev/null +++ b/internal/fourslash/tests/gen/autoImportSortCaseSensitivity1_test.go @@ -0,0 +1,54 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestAutoImportSortCaseSensitivity1(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /exports1.ts +export const a = 0; +export const A = 1; +export const b = 2; +export const B = 3; +export const c = 4; +export const C = 5; +// @Filename: /exports2.ts +export const d = 0; +export const D = 1; +export const e = 2; +export const E = 3; +// @Filename: /index0.ts +import { A, B, C } from "./exports1"; +a/*0*/ +// @Filename: /index1.ts +import { A, a, B, b } from "./exports1"; +import { E } from "./exports2"; +d/*1*/` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "0") + f.VerifyImportFixAtPosition(t, []string{ + `import { a, A, B, C } from "./exports1"; +a`, + }, nil /*preferences*/) + f.VerifyImportFixAtPosition(t, []string{ + `import { a, A, B, C } from "./exports1"; +a`, + }, nil /*preferences*/) + f.GoToMarker(t, "1") + f.VerifyImportFixAtPosition(t, []string{ + `import { A, a, B, b } from "./exports1"; +import { d, E } from "./exports2"; +d`, + }, nil /*preferences*/) + f.VerifyImportFixAtPosition(t, []string{ + `import { A, a, B, b } from "./exports1"; +import { E, d } from "./exports2"; +d`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/autoImportSymlinkCaseSensitive_test.go b/internal/fourslash/tests/gen/autoImportSymlinkCaseSensitive_test.go new file mode 100644 index 0000000000..e8553396d2 --- /dev/null +++ b/internal/fourslash/tests/gen/autoImportSymlinkCaseSensitive_test.go @@ -0,0 +1,31 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestAutoImportSymlinkCaseSensitive(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /tsconfig.json +{ "compilerOptions": { "module": "commonjs" } } +// @Filename: /node_modules/.pnpm/mobx@6.0.4/node_modules/MobX/Foo.d.ts +export declare function autorun(): void; +// @Filename: /index.ts +autorun/**/ +// @Filename: /utils.ts +import "MobX/Foo"; +// @link: /node_modules/.pnpm/mobx@6.0.4/node_modules/MobX -> /node_modules/MobX +// @link: /node_modules/.pnpm/mobx@6.0.4/node_modules/MobX -> /node_modules/.pnpm/cool-mobx-dependent@1.2.3/node_modules/MobX` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { autorun } from "MobX/Foo"; + +autorun`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/autoImportTypeImport1_test.go b/internal/fourslash/tests/gen/autoImportTypeImport1_test.go new file mode 100644 index 0000000000..7fc9f0a982 --- /dev/null +++ b/internal/fourslash/tests/gen/autoImportTypeImport1_test.go @@ -0,0 +1,42 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestAutoImportTypeImport1(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @verbatimModuleSyntax: true +// @target: esnext +// @Filename: /foo.ts +export const A = 1; +export type B = { x: number }; +export type C = 1; +export class D = { y: string }; +// @Filename: /test.ts +import { A, D, type C } from './foo'; +const b: B/**/ | C; +console.log(A, D);` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { A, D, type C, type B } from './foo'; +const b: B | C; +console.log(A, D);`, + }, nil /*preferences*/) + f.VerifyImportFixAtPosition(t, []string{ + `import { A, D, type B, type C } from './foo'; +const b: B | C; +console.log(A, D);`, + }, nil /*preferences*/) + f.VerifyImportFixAtPosition(t, []string{ + `import { A, D, type C, type B } from './foo'; +const b: B | C; +console.log(A, D);`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/autoImportTypeImport2_test.go b/internal/fourslash/tests/gen/autoImportTypeImport2_test.go new file mode 100644 index 0000000000..202dfc0dd1 --- /dev/null +++ b/internal/fourslash/tests/gen/autoImportTypeImport2_test.go @@ -0,0 +1,42 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestAutoImportTypeImport2(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @verbatimModuleSyntax: true +// @target: esnext +// @Filename: /foo.ts +export const A = 1; +export type B = { x: number }; +export type C = 1; +export class D = { y: string }; +// @Filename: /test.ts +import { A, type C, D } from './foo'; +const b: B/**/ | C; +console.log(A, D);` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { A, type B, type C, D } from './foo'; +const b: B | C; +console.log(A, D);`, + }, nil /*preferences*/) + f.VerifyImportFixAtPosition(t, []string{ + `import { A, type C, D, type B } from './foo'; +const b: B | C; +console.log(A, D);`, + }, nil /*preferences*/) + f.VerifyImportFixAtPosition(t, []string{ + `import { A, type C, D, type B } from './foo'; +const b: B | C; +console.log(A, D);`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/autoImportTypeImport3_test.go b/internal/fourslash/tests/gen/autoImportTypeImport3_test.go new file mode 100644 index 0000000000..20dd8d97fd --- /dev/null +++ b/internal/fourslash/tests/gen/autoImportTypeImport3_test.go @@ -0,0 +1,42 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestAutoImportTypeImport3(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @verbatimModuleSyntax: true +// @target: esnext +// @Filename: /foo.ts +export const A = 1; +export type B = { x: number }; +export type C = 1; +export class D = { y: string }; +// @Filename: /test.ts +import { A, type B, type C } from './foo'; +const b: B | C; +console.log(A, D/**/);` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { A, D, type B, type C } from './foo'; +const b: B | C; +console.log(A, D);`, + }, nil /*preferences*/) + f.VerifyImportFixAtPosition(t, []string{ + `import { A, type B, type C, D } from './foo'; +const b: B | C; +console.log(A, D);`, + }, nil /*preferences*/) + f.VerifyImportFixAtPosition(t, []string{ + `import { A, type B, type C, D } from './foo'; +const b: B | C; +console.log(A, D);`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/autoImportTypeImport4_test.go b/internal/fourslash/tests/gen/autoImportTypeImport4_test.go new file mode 100644 index 0000000000..ae19d60f17 --- /dev/null +++ b/internal/fourslash/tests/gen/autoImportTypeImport4_test.go @@ -0,0 +1,123 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestAutoImportTypeImport4(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @verbatimModuleSyntax: true +// @target: esnext +// @Filename: /exports1.ts +export const a = 0; +export const A = 1; +export const b = 2; +export const B = 3; +export const c = 4; +export const C = 5; +export type x = 6; +export const X = 7; +export const Y = 8; +export const Z = 9; +// @Filename: /exports2.ts +export const d = 0; +export const D = 1; +export const e = 2; +export const E = 3; +// @Filename: /index0.ts +import { A, B, C } from "./exports1"; +a/*0*//*0a*/; +b; +// @Filename: /index1.ts +import { A, B, C, type Y, type Z } from "./exports1"; +a/*1*//*1a*//*1b*//*1c*/; +b; +// @Filename: /index2.ts +import { A, a, B, b, type Y, type Z } from "./exports1"; +import { E } from "./exports2"; +d/*2*//*2a*//*2b*//*2c*/` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "0") + f.VerifyImportFixAtPosition(t, []string{ + `import { a, A, B, C } from "./exports1"; +a; +b;`, + `import { A, b, B, C } from "./exports1"; +a; +b;`, + }, nil /*preferences*/) + f.GoToMarker(t, "0a") + f.VerifyImportFixAtPosition(t, []string{ + `import { a, A, B, C } from "./exports1"; +a; +b;`, + `import { A, b, B, C } from "./exports1"; +a; +b;`, + }, nil /*preferences*/) + f.GoToMarker(t, "1") + f.VerifyImportFixAtPosition(t, []string{ + `import { a, A, B, C, type Y, type Z } from "./exports1"; +a; +b;`, + `import { A, b, B, C, type Y, type Z } from "./exports1"; +a; +b;`, + }, nil /*preferences*/) + f.GoToMarker(t, "1a") + f.VerifyImportFixAtPosition(t, []string{ + `import { a, A, B, C, type Y, type Z } from "./exports1"; +a; +b;`, + `import { A, b, B, C, type Y, type Z } from "./exports1"; +a; +b;`, + }, nil /*preferences*/) + f.GoToMarker(t, "1b") + f.VerifyImportFixAtPosition(t, []string{ + `import { a, A, B, C, type Y, type Z } from "./exports1"; +a; +b;`, + `import { A, b, B, C, type Y, type Z } from "./exports1"; +a; +b;`, + }, nil /*preferences*/) + f.GoToMarker(t, "1c") + f.VerifyImportFixAtPosition(t, []string{ + `import { a, A, B, C, type Y, type Z } from "./exports1"; +a; +b;`, + `import { A, b, B, C, type Y, type Z } from "./exports1"; +a; +b;`, + }, nil /*preferences*/) + f.GoToMarker(t, "2") + f.VerifyImportFixAtPosition(t, []string{ + `import { A, a, B, b, type Y, type Z } from "./exports1"; +import { d, E } from "./exports2"; +d`, + }, nil /*preferences*/) + f.GoToMarker(t, "2a") + f.VerifyImportFixAtPosition(t, []string{ + `import { A, a, B, b, type Y, type Z } from "./exports1"; +import { E, d } from "./exports2"; +d`, + }, nil /*preferences*/) + f.GoToMarker(t, "2b") + f.VerifyImportFixAtPosition(t, []string{ + `import { A, a, B, b, type Y, type Z } from "./exports1"; +import { d, E } from "./exports2"; +d`, + }, nil /*preferences*/) + f.GoToMarker(t, "2c") + f.VerifyImportFixAtPosition(t, []string{ + `import { A, a, B, b, type Y, type Z } from "./exports1"; +import { E, d } from "./exports2"; +d`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/autoImportTypeImport5_test.go b/internal/fourslash/tests/gen/autoImportTypeImport5_test.go new file mode 100644 index 0000000000..85077ac85c --- /dev/null +++ b/internal/fourslash/tests/gen/autoImportTypeImport5_test.go @@ -0,0 +1,108 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestAutoImportTypeImport5(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @verbatimModuleSyntax: true +// @target: esnext +// @Filename: /exports1.ts +export const a = 0; +export const A = 1; +export const b = 2; +export const B = 3; +export const c = 4; +export const C = 5; +export type x = 6; +export const X = 7; +export type y = 8 +export const Y = 9; +export const Z = 10; +// @Filename: /exports2.ts +export const d = 0; +export const D = 1; +export const e = 2; +export const E = 3; +// @Filename: /index0.ts +import { type X, type Y, type Z } from "./exports1"; +const foo: x/*0*/; +const bar: y; +// @Filename: /index1.ts +import { A, B, type X, type Y, type Z } from "./exports1"; +const foo: x/*1*/; +const bar: y;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "0") + f.VerifyImportFixAtPosition(t, []string{ + `import { type x, type X, type Y, type Z } from "./exports1"; +const foo: x; +const bar: y;`, + `import { type X, type y, type Y, type Z } from "./exports1"; +const foo: x; +const bar: y;`, + }, nil /*preferences*/) + f.VerifyImportFixAtPosition(t, []string{ + `import { type x, type X, type Y, type Z } from "./exports1"; +const foo: x; +const bar: y;`, + `import { type X, type y, type Y, type Z } from "./exports1"; +const foo: x; +const bar: y;`, + }, nil /*preferences*/) + f.VerifyImportFixAtPosition(t, []string{ + `import { type x, type X, type Y, type Z } from "./exports1"; +const foo: x; +const bar: y;`, + `import { type X, type y, type Y, type Z } from "./exports1"; +const foo: x; +const bar: y;`, + }, nil /*preferences*/) + f.VerifyImportFixAtPosition(t, []string{ + `import { type x, type X, type Y, type Z } from "./exports1"; +const foo: x; +const bar: y;`, + `import { type X, type y, type Y, type Z } from "./exports1"; +const foo: x; +const bar: y;`, + }, nil /*preferences*/) + f.VerifyImportFixAtPosition(t, []string{ + `import { type x, type X, type Y, type Z } from "./exports1"; +const foo: x; +const bar: y;`, + `import { type X, type y, type Y, type Z } from "./exports1"; +const foo: x; +const bar: y;`, + }, nil /*preferences*/) + f.VerifyImportFixAtPosition(t, []string{ + `import { type x, type X, type Y, type Z } from "./exports1"; +const foo: x; +const bar: y;`, + `import { type X, type y, type Y, type Z } from "./exports1"; +const foo: x; +const bar: y;`, + }, nil /*preferences*/) + f.GoToMarker(t, "1") + f.VerifyImportFixAtPosition(t, []string{ + `import { A, B, type x, type X, type Y, type Z } from "./exports1"; +const foo: x; +const bar: y;`, + `import { A, B, type X, type y, type Y, type Z } from "./exports1"; +const foo: x; +const bar: y;`, + }, nil /*preferences*/) + f.VerifyImportFixAtPosition(t, []string{ + `import { A, B, type x, type X, type Y, type Z } from "./exports1"; +const foo: x; +const bar: y;`, + `import { A, B, type X, type y, type Y, type Z } from "./exports1"; +const foo: x; +const bar: y;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/autoImportTypeOnlyPreferred2_test.go b/internal/fourslash/tests/gen/autoImportTypeOnlyPreferred2_test.go new file mode 100644 index 0000000000..c79ec30ad6 --- /dev/null +++ b/internal/fourslash/tests/gen/autoImportTypeOnlyPreferred2_test.go @@ -0,0 +1,50 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestAutoImportTypeOnlyPreferred2(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /node_modules/react/index.d.ts +export interface ComponentType {} +export interface ComponentProps {} +export declare function useState(initialState: T): [T, (newState: T) => void]; +export declare function useEffect(callback: () => void, deps: any[]): void; +// @Filename: /main.ts +import type { ComponentType } from "react"; +import { useState } from "react"; + +export function Component({ prop } : { prop: ComponentType }) { + const codeIsUnimportant = useState(1); + useEffect/*1*/(() => {}, []); +} +// @Filename: /main2.ts +import { useState } from "react"; +import type { ComponentType } from "react"; + +type _ = ComponentProps/*2*/;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "1") + f.VerifyImportFixAtPosition(t, []string{ + `import type { ComponentType } from "react"; +import { useEffect, useState } from "react"; + +export function Component({ prop } : { prop: ComponentType }) { + const codeIsUnimportant = useState(1); + useEffect(() => {}, []); +}`, + }, nil /*preferences*/) + f.GoToMarker(t, "2") + f.VerifyImportFixAtPosition(t, []string{ + `import { useState } from "react"; +import type { ComponentProps, ComponentType } from "react"; + +type _ = ComponentProps;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/autoImportsNodeNext1_test.go b/internal/fourslash/tests/gen/autoImportsNodeNext1_test.go new file mode 100644 index 0000000000..a174ed157c --- /dev/null +++ b/internal/fourslash/tests/gen/autoImportsNodeNext1_test.go @@ -0,0 +1,47 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + . "github.com/microsoft/typescript-go/internal/fourslash/tests/util" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestAutoImportsNodeNext1(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @module: node18 +// @Filename: /node_modules/pack/package.json +{ + "name": "pack", + "version": "1.0.0", + "exports": { + ".": "./main.mjs" + } +} +// @Filename: /node_modules/pack/main.d.mts +import {} from "./unreachable.mjs"; +export const fromMain = 0; +// @Filename: /node_modules/pack/unreachable.d.mts +export const fromUnreachable = 0; +// @Filename: /index.mts +import { fromMain } from "pack"; +fromUnreachable/**/` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{}, nil /*preferences*/) + f.VerifyCompletions(t, "", &fourslash.CompletionsExpectedList{ + IsIncomplete: false, + ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{ + CommitCharacters: &DefaultCommitCharacters, + EditRange: Ignored, + }, + Items: &fourslash.CompletionsExpectedItems{ + Excludes: []string{ + "fromUnreachable", + }, + }, + }) +} diff --git a/internal/fourslash/tests/gen/completionForStringLiteralNonrelativeImport7_test.go b/internal/fourslash/tests/gen/completionForStringLiteralNonrelativeImport7_test.go index 59b0088b59..5c6740e18f 100644 --- a/internal/fourslash/tests/gen/completionForStringLiteralNonrelativeImport7_test.go +++ b/internal/fourslash/tests/gen/completionForStringLiteralNonrelativeImport7_test.go @@ -10,7 +10,7 @@ import ( func TestCompletionForStringLiteralNonrelativeImport7(t *testing.T) { t.Parallel() - t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `// @baseUrl: tests/cases/fourslash/modules // @Filename: tests/test0.ts diff --git a/internal/fourslash/tests/gen/completionsImport_default_symbolName_test.go b/internal/fourslash/tests/gen/completionsImport_default_symbolName_test.go index 4145b4d095..bae20c6a6d 100644 --- a/internal/fourslash/tests/gen/completionsImport_default_symbolName_test.go +++ b/internal/fourslash/tests/gen/completionsImport_default_symbolName_test.go @@ -12,7 +12,7 @@ import ( func TestCompletionsImport_default_symbolName(t *testing.T) { t.Parallel() - t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `// @module: commonjs // @esModuleInterop: false diff --git a/internal/fourslash/tests/gen/completionsImport_exportEquals_anonymous_test.go b/internal/fourslash/tests/gen/completionsImport_exportEquals_anonymous_test.go index 40d012f3b6..264ddff310 100644 --- a/internal/fourslash/tests/gen/completionsImport_exportEquals_anonymous_test.go +++ b/internal/fourslash/tests/gen/completionsImport_exportEquals_anonymous_test.go @@ -12,7 +12,7 @@ import ( func TestCompletionsImport_exportEquals_anonymous(t *testing.T) { t.Parallel() - t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `// @noLib: true // @module: commonjs diff --git a/internal/fourslash/tests/gen/completionsImport_exportEquals_test.go b/internal/fourslash/tests/gen/completionsImport_exportEquals_test.go index 071fb88853..5a1ef9edd0 100644 --- a/internal/fourslash/tests/gen/completionsImport_exportEquals_test.go +++ b/internal/fourslash/tests/gen/completionsImport_exportEquals_test.go @@ -12,7 +12,7 @@ import ( func TestCompletionsImport_exportEquals(t *testing.T) { t.Parallel() - t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `// @module: commonjs // @esModuleInterop: false diff --git a/internal/fourslash/tests/gen/completionsImport_importType_test.go b/internal/fourslash/tests/gen/completionsImport_importType_test.go index 28c24d84d2..9f71101db2 100644 --- a/internal/fourslash/tests/gen/completionsImport_importType_test.go +++ b/internal/fourslash/tests/gen/completionsImport_importType_test.go @@ -12,7 +12,7 @@ import ( func TestCompletionsImport_importType(t *testing.T) { t.Parallel() - t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `// @allowJs: true // @Filename: /a.js diff --git a/internal/fourslash/tests/gen/completionsImport_named_namespaceImportExists_test.go b/internal/fourslash/tests/gen/completionsImport_named_namespaceImportExists_test.go index a8eb3a70da..2451350049 100644 --- a/internal/fourslash/tests/gen/completionsImport_named_namespaceImportExists_test.go +++ b/internal/fourslash/tests/gen/completionsImport_named_namespaceImportExists_test.go @@ -12,7 +12,7 @@ import ( func TestCompletionsImport_named_namespaceImportExists(t *testing.T) { t.Parallel() - t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `// @Filename: /a.ts export function foo() {} diff --git a/internal/fourslash/tests/gen/completionsImport_weirdDefaultSynthesis_test.go b/internal/fourslash/tests/gen/completionsImport_weirdDefaultSynthesis_test.go index 463ea288be..5514308ba0 100644 --- a/internal/fourslash/tests/gen/completionsImport_weirdDefaultSynthesis_test.go +++ b/internal/fourslash/tests/gen/completionsImport_weirdDefaultSynthesis_test.go @@ -10,7 +10,7 @@ import ( func TestCompletionsImport_weirdDefaultSynthesis(t *testing.T) { t.Parallel() - t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `// @module: commonjs // @esModuleInterop: false diff --git a/internal/fourslash/tests/gen/importFixWithMultipleModuleExportAssignment_test.go b/internal/fourslash/tests/gen/importFixWithMultipleModuleExportAssignment_test.go new file mode 100644 index 0000000000..e0f9edde84 --- /dev/null +++ b/internal/fourslash/tests/gen/importFixWithMultipleModuleExportAssignment_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportFixWithMultipleModuleExportAssignment(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @module: esnext +// @allowJs: true +// @checkJs: true +// @Filename: /a.js +function f() {} +module.exports = f; +module.exports = 42; +// @Filename: /b.js +export const foo = 0; +// @Filename: /c.js +foo` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/c.js") + f.VerifyImportFixAtPosition(t, []string{ + `const { foo } = require("./b"); + +foo`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importFixesGlobalTypingsCache_test.go b/internal/fourslash/tests/gen/importFixesGlobalTypingsCache_test.go new file mode 100644 index 0000000000..b0393b485a --- /dev/null +++ b/internal/fourslash/tests/gen/importFixesGlobalTypingsCache_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportFixesGlobalTypingsCache(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /project/tsconfig.json + { "compilerOptions": { "allowJs": true, "checkJs": true } } +// @Filename: /home/src/Library/Caches/typescript/node_modules/@types/react-router-dom/package.json + { "name": "@types/react-router-dom", "version": "16.8.4", "types": "index.d.ts" } +// @Filename: /home/src/Library/Caches/typescript/node_modules/@types/react-router-dom/index.d.ts +export class BrowserRouter {} +// @Filename: /project/node_modules/react-router-dom/package.json + { "name": "react-router-dom", "version": "16.8.4", "main": "index.js" } +// @Filename: /project/node_modules/react-router-dom/index.js + export const BrowserRouter = () => null; +// @Filename: /project/index.js +BrowserRouter/**/` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/project/index.js") + f.VerifyImportFixAtPosition(t, []string{ + `const { BrowserRouter } = require("react-router-dom"); + +BrowserRouter`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importFixes_quotePreferenceDouble_importHelpers_test.go b/internal/fourslash/tests/gen/importFixes_quotePreferenceDouble_importHelpers_test.go new file mode 100644 index 0000000000..06b9436e0a --- /dev/null +++ b/internal/fourslash/tests/gen/importFixes_quotePreferenceDouble_importHelpers_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportFixes_quotePreferenceDouble_importHelpers(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @importHelpers: true +// @filename: /a.ts +export default () => {}; +// @filename: /b.ts +export default () => {}; +// @filename: /test.ts +import a from "./a"; +[|b|];` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/test.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import b from "./b"; +b`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importFixes_quotePreferenceSingle_importHelpers_test.go b/internal/fourslash/tests/gen/importFixes_quotePreferenceSingle_importHelpers_test.go new file mode 100644 index 0000000000..228e3087e2 --- /dev/null +++ b/internal/fourslash/tests/gen/importFixes_quotePreferenceSingle_importHelpers_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportFixes_quotePreferenceSingle_importHelpers(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @importHelpers: true +// @filename: /a.ts +export default () => {}; +// @filename: /b.ts +export default () => {}; +// @filename: /test.ts +import a from './a'; +[|b|];` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/test.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import b from './b'; +b`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixConvertTypeOnly1_test.go b/internal/fourslash/tests/gen/importNameCodeFixConvertTypeOnly1_test.go new file mode 100644 index 0000000000..fdf289e16a --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixConvertTypeOnly1_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixConvertTypeOnly1(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /a.ts +export class A {} +export class B {} +// @Filename: /b.ts +import type { A } from './a'; +new B` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/b.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import { B, type A } from './a'; +new B`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixDefaultExport1_test.go b/internal/fourslash/tests/gen/importNameCodeFixDefaultExport1_test.go new file mode 100644 index 0000000000..52cea508c2 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixDefaultExport1_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixDefaultExport1(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /foo-bar.ts +export default function fooBar(); +// @Filename: /b.ts +[|import * as fb from "./foo-bar"; +foo/**/Bar|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/b.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import fooBar, * as fb from "./foo-bar"; +fooBar`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixDefaultExport2_test.go b/internal/fourslash/tests/gen/importNameCodeFixDefaultExport2_test.go new file mode 100644 index 0000000000..de4fe9d2f6 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixDefaultExport2_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixDefaultExport2(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /lib.ts +class Base { } +export default Base; +// @Filename: /test.ts +[|class Derived extends Base { }|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/test.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import Base from "./lib"; + +class Derived extends Base { }`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixDefaultExport3_test.go b/internal/fourslash/tests/gen/importNameCodeFixDefaultExport3_test.go new file mode 100644 index 0000000000..51fb3c50a6 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixDefaultExport3_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixDefaultExport3(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /foo-bar/index.ts +export default 0; +// @Filename: /b.ts +[|foo/**/Bar|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/b.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import fooBar from "./foo-bar"; + +fooBar`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixDefaultExport4_test.go b/internal/fourslash/tests/gen/importNameCodeFixDefaultExport4_test.go new file mode 100644 index 0000000000..7b911543b5 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixDefaultExport4_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixDefaultExport4(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /foo.ts +const a = () => {}; +export default a; +// @Filename: /test.ts +[|foo|];` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/test.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import foo from "./foo"; + +foo`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixDefaultExport5_test.go b/internal/fourslash/tests/gen/importNameCodeFixDefaultExport5_test.go new file mode 100644 index 0000000000..c06e07ba0e --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixDefaultExport5_test.go @@ -0,0 +1,27 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixDefaultExport5(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @moduleResolution: bundler +// @Filename: /node_modules/hooks/useFoo.ts +declare const _default: () => void; +export default _default; +// @Filename: /test.ts +[|useFoo|];` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/test.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import useFoo from "hooks/useFoo"; + +useFoo`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixDefaultExport7_test.go b/internal/fourslash/tests/gen/importNameCodeFixDefaultExport7_test.go new file mode 100644 index 0000000000..1ca3fbf64a --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixDefaultExport7_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixDefaultExport7(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @lib: dom +// @Filename: foo.ts +export default globalThis.localStorage; +// @Filename: index.ts +foo/**/` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import foo from "./foo"; + +foo`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixDefaultExport_test.go b/internal/fourslash/tests/gen/importNameCodeFixDefaultExport_test.go new file mode 100644 index 0000000000..32c7ac6873 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixDefaultExport_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixDefaultExport(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /foo-bar.ts +export default 0; +// @Filename: /b.ts +[|foo/**/Bar|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/b.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import fooBar from "./foo-bar"; + +fooBar`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixExistingImport0_test.go b/internal/fourslash/tests/gen/importNameCodeFixExistingImport0_test.go new file mode 100644 index 0000000000..4553d948be --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixExistingImport0_test.go @@ -0,0 +1,23 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixExistingImport0(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `import [|{ v1 }|] from "./module"; +f1/*0*/(); +// @Filename: module.ts +export function f1() {} +export var v1 = 5;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `{ f1, v1 }`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixExistingImport10_test.go b/internal/fourslash/tests/gen/importNameCodeFixExistingImport10_test.go new file mode 100644 index 0000000000..836756a4f6 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixExistingImport10_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixExistingImport10(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `import [|{ + v1, + v2 +}|] from "./module"; +f1/*0*/(); +// @Filename: module.ts +export function f1() {} +export var v1 = 5; +export var v2 = 5; +export var v3 = 5;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `{ + f1, + v1, + v2 +}`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixExistingImport11_test.go b/internal/fourslash/tests/gen/importNameCodeFixExistingImport11_test.go new file mode 100644 index 0000000000..da6b393b5b --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixExistingImport11_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixExistingImport11(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `import [|{ + v1, v2, + v3 +}|] from "./module"; +f1/*0*/(); +// @Filename: module.ts + export function f1() {} + export var v1 = 5; + export var v2 = 5; + export var v3 = 5;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `{ + f1, + v1, v2, + v3 +}`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixExistingImport12_test.go b/internal/fourslash/tests/gen/importNameCodeFixExistingImport12_test.go new file mode 100644 index 0000000000..9b5c7e8d75 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixExistingImport12_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixExistingImport12(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `import [|{}|] from "./module"; +f1/*0*/(); +// @Filename: module.ts +export function f1() {} +export var v1 = 5; +export var v2 = 5; +export var v3 = 5;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `{ f1 }`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixExistingImport1_test.go b/internal/fourslash/tests/gen/importNameCodeFixExistingImport1_test.go new file mode 100644 index 0000000000..31c9f9ea01 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixExistingImport1_test.go @@ -0,0 +1,24 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixExistingImport1(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `import d, [|{ v1 }|] from "./module"; +f1/*0*/(); +// @Filename: module.ts +export function f1() {} +export var v1 = 5; +export default var d1 = 6;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `{ f1, v1 }`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixExistingImport2_test.go b/internal/fourslash/tests/gen/importNameCodeFixExistingImport2_test.go new file mode 100644 index 0000000000..0602242203 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixExistingImport2_test.go @@ -0,0 +1,30 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixExistingImport2(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `import * as ns from "./module"; +// Comment +f1/*0*/(); +// @Filename: module.ts + export function f1() {} + export var v1 = 5;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import * as ns from "./module"; +// Comment +ns.f1();`, + `import * as ns from "./module"; +import { f1 } from "./module"; +// Comment +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixExistingImport3_test.go b/internal/fourslash/tests/gen/importNameCodeFixExistingImport3_test.go new file mode 100644 index 0000000000..041f86f0e2 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixExistingImport3_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixExistingImport3(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|import d, * as ns from "./module" ; +f1/*0*/();|] +// @Filename: module.ts +export function f1() {} +export var v1 = 5; +export default var d1 = 6;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import d, * as ns from "./module" ; +ns.f1();`, + `import d, * as ns from "./module" ; +import { f1 } from "./module"; +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixExistingImport4_test.go b/internal/fourslash/tests/gen/importNameCodeFixExistingImport4_test.go new file mode 100644 index 0000000000..19e959db2d --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixExistingImport4_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixExistingImport4(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|import d from "./module"; +f1/*0*/();|] +// @Filename: module.ts +export function f1() {} +export var v1 = 5; +export default var d1 = 6;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import d, { f1 } from "./module"; +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixExistingImport5_test.go b/internal/fourslash/tests/gen/importNameCodeFixExistingImport5_test.go new file mode 100644 index 0000000000..31610760a8 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixExistingImport5_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixExistingImport5(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|import "./module"; +f1/*0*/();|] +// @Filename: module.ts +export function f1() {} +export var v1 = 5;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import "./module"; +import { f1 } from "./module"; +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixExistingImport6_test.go b/internal/fourslash/tests/gen/importNameCodeFixExistingImport6_test.go new file mode 100644 index 0000000000..bc15f5e573 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixExistingImport6_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixExistingImport6(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `import [|{ v1 }|] from "fake-module"; +f1/*0*/(); +// @Filename: ../package.json +{ "dependencies": { "fake-module": "latest" } } +// @Filename: ../node_modules/fake-module/index.ts +export var v1 = 5; +export function f1();` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `{ f1, v1 }`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixExistingImport7_test.go b/internal/fourslash/tests/gen/importNameCodeFixExistingImport7_test.go new file mode 100644 index 0000000000..e82d0ed3fd --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixExistingImport7_test.go @@ -0,0 +1,23 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixExistingImport7(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `import [|{ v1 }|] from "../other_dir/module"; +f1/*0*/(); +// @Filename: ../other_dir/module.ts +export var v1 = 5; +export function f1();` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `{ f1, v1 }`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixExistingImport8_test.go b/internal/fourslash/tests/gen/importNameCodeFixExistingImport8_test.go new file mode 100644 index 0000000000..0ae3edc2e4 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixExistingImport8_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixExistingImport8(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `import [|{v1, v2, v3,}|] from "./module"; +v4/*0*/(); +// @Filename: module.ts +export function v4() {} +export var v1 = 5; +export var v2 = 5; +export var v3 = 5;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `{v1, v2, v3, v4,}`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixExistingImport9_test.go b/internal/fourslash/tests/gen/importNameCodeFixExistingImport9_test.go new file mode 100644 index 0000000000..71964dca5f --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixExistingImport9_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixExistingImport9(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `import [|{ + v1 +}|] from "./module"; +f1/*0*/(); +// @Filename: module.ts +export function f1() {} +export var v1 = 5;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `{ + f1, + v1 +}`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixExistingImportEquals0_test.go b/internal/fourslash/tests/gen/importNameCodeFixExistingImportEquals0_test.go new file mode 100644 index 0000000000..0cf8875bb5 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixExistingImportEquals0_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixExistingImportEquals0(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|import ns = require("ambient-module"); +var x = v1/*0*/ + 5;|] +// @Filename: ambientModule.ts +declare module "ambient-module" { + export function f1(); + export var v1; +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import ns = require("ambient-module"); +var x = ns.v1 + 5;`, + `import { v1 } from "ambient-module"; +import ns = require("ambient-module"); +var x = v1 + 5;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixExportAsDefaultExistingImport_test.go b/internal/fourslash/tests/gen/importNameCodeFixExportAsDefaultExistingImport_test.go new file mode 100644 index 0000000000..97df987bbe --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixExportAsDefaultExistingImport_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixExportAsDefaultExistingImport(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `import [|{ v1, v2, v3 }|] from "./module"; +v4/*0*/(); +// @Filename: module.ts +const v4 = 5; +export { v4 as default }; +export const v1 = 5; +export const v2 = 5; +export const v3 = 5;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `v4, { v1, v2, v3 }`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixIndentedIdentifier_test.go b/internal/fourslash/tests/gen/importNameCodeFixIndentedIdentifier_test.go new file mode 100644 index 0000000000..b5c0e91aec --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixIndentedIdentifier_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixIndentedIdentifier(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /a.ts +[|import * as b from "./b"; +{ + x/**/ +}|] +// @Filename: /b.ts +export const x = 0;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import * as b from "./b"; +{ + b.x +}`, + `import * as b from "./b"; +import { x } from "./b"; +{ + x +}`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports0_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports0_test.go new file mode 100644 index 0000000000..4c8fc42141 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports0_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportAllowSyntheticDefaultImports0(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @AllowSyntheticDefaultImports: true +// @Filename: a/f1.ts +[|export var x = 0; +bar/*0*/();|] +// @Filename: a/foo.d.ts +declare function bar(): number; +export = bar; +export as namespace bar;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import bar from "./foo"; + +export var x = 0; +bar();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports1_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports1_test.go new file mode 100644 index 0000000000..4e197d35c8 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports1_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportAllowSyntheticDefaultImports1(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Module: system +// @Filename: a/f1.ts +[|export var x = 0; +bar/*0*/();|] +// @Filename: a/foo.d.ts +declare function bar(): number; +export = bar; +export as namespace bar;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import bar from "./foo"; + +export var x = 0; +bar();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports2_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports2_test.go new file mode 100644 index 0000000000..3b3daf56b3 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports2_test.go @@ -0,0 +1,30 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportAllowSyntheticDefaultImports2(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @AllowSyntheticDefaultImports: false +// @Module: system +// @Filename: a/f1.ts +[|export var x = 0; +bar/*0*/();|] +// @Filename: a/foo.d.ts +declare function bar(): number; +export = bar; +export as namespace bar;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import * as bar from "./foo"; + +export var x = 0; +bar();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports3_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports3_test.go new file mode 100644 index 0000000000..4da5bbf181 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports3_test.go @@ -0,0 +1,30 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportAllowSyntheticDefaultImports3(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @AllowSyntheticDefaultImports: false +// @Module: commonjs +// @Filename: a/f1.ts +[|export var x = 0; +bar/*0*/();|] +// @Filename: a/foo.d.ts +declare function bar(): number; +export = bar; +export as namespace bar;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import bar = require("./foo"); + +export var x = 0; +bar();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports4_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports4_test.go new file mode 100644 index 0000000000..8611d94c55 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports4_test.go @@ -0,0 +1,30 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportAllowSyntheticDefaultImports4(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @AllowSyntheticDefaultImports: false +// @Module: amd +// @Filename: a/f1.ts +[|export var x = 0; +bar/*0*/();|] +// @Filename: a/foo.d.ts +declare function bar(): number; +export = bar; +export as namespace bar;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import bar = require("./foo"); + +export var x = 0; +bar();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports5_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports5_test.go new file mode 100644 index 0000000000..97912e6a85 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports5_test.go @@ -0,0 +1,30 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportAllowSyntheticDefaultImports5(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @AllowSyntheticDefaultImports: false +// @Module: umd +// @Filename: a/f1.ts +[|export var x = 0; +bar/*0*/();|] +// @Filename: a/foo.d.ts +declare function bar(): number; +export = bar; +export as namespace bar;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import bar = require("./foo"); + +export var x = 0; +bar();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportAmbient0_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportAmbient0_test.go new file mode 100644 index 0000000000..3b2776f970 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportAmbient0_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportAmbient0(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|f1/*0*/();|] +// @Filename: ambientModule.ts +declare module "ambient-module" { + export function f1(); + export var v1; +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { f1 } from "ambient-module"; + +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportAmbient1_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportAmbient1_test.go new file mode 100644 index 0000000000..22892b732c --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportAmbient1_test.go @@ -0,0 +1,38 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportAmbient1(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `import d from "other-ambient-module"; +import * as ns from "yet-another-ambient-module"; +var x = v1/*0*/ + 5; +// @Filename: ambientModule.ts +declare module "ambient-module" { + export function f1(); + export var v1; +} +// @Filename: otherAmbientModule.ts +declare module "other-ambient-module" { + export default function f2(); +} +// @Filename: yetAnotherAmbientModule.ts +declare module "yet-another-ambient-module" { + export function f3(); + export var v3; +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { v1 } from "ambient-module"; +import d from "other-ambient-module"; +import * as ns from "yet-another-ambient-module"; +var x = v1 + 5;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportAmbient2_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportAmbient2_test.go new file mode 100644 index 0000000000..592328caa5 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportAmbient2_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportAmbient2(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|/*! + * I'm a license or something + */ +f1/*0*/();|] +// @Filename: ambientModule.ts + declare module "ambient-module" { + export function f1(); + export var v1; + }` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `/*! + * I'm a license or something + */ + +import { f1 } from "ambient-module"; + +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportAmbient3_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportAmbient3_test.go new file mode 100644 index 0000000000..d20e88242d --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportAmbient3_test.go @@ -0,0 +1,40 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportAmbient3(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `let a = "I am a non-trivial statement that appears before imports"; +import d from "other-ambient-module" +import * as ns from "yet-another-ambient-module" +var x = v1/*0*/ + 5; +// @Filename: ambientModule.ts +declare module "ambient-module" { + export function f1(); + export var v1; +} +// @Filename: otherAmbientModule.ts +declare module "other-ambient-module" { + export default function f2(); +} +// @Filename: yetAnotherAmbientModule.ts +declare module "yet-another-ambient-module" { + export function f3(); + export var v3; +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `let a = "I am a non-trivial statement that appears before imports"; +import { v1 } from "ambient-module"; +import d from "other-ambient-module" +import * as ns from "yet-another-ambient-module" +var x = v1 + 5;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportBaseUrl0_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportBaseUrl0_test.go new file mode 100644 index 0000000000..dd4b002813 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportBaseUrl0_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportBaseUrl0(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|f1/*0*/();|] +// @Filename: tsconfig.json +{ + "compilerOptions": { + "baseUrl": "./a" + } +} +// @Filename: a/b.ts +export function f1() { };` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { f1 } from "b"; + +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportBaseUrl1_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportBaseUrl1_test.go new file mode 100644 index 0000000000..a3ce15ee9c --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportBaseUrl1_test.go @@ -0,0 +1,36 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportBaseUrl1(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /tsconfig.json +{ + "compilerOptions": { + "baseUrl": "./a" + } +} +// @Filename: /a/b/x.ts +export function f1() { }; +// @Filename: /a/b/y.ts +[|f1/*0*/();|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/a/b/y.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import { f1 } from "./x"; + +f1();`, + }, nil /*preferences*/) + f.VerifyImportFixAtPosition(t, []string{ + `import { f1 } from "b/x"; + +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportBaseUrl2_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportBaseUrl2_test.go new file mode 100644 index 0000000000..0796d349b6 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportBaseUrl2_test.go @@ -0,0 +1,36 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportBaseUrl2(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /tsconfig.json +{ + "compilerOptions": { + "baseUrl": "./a" + } +} +// @Filename: /a/b/x.ts +export function f1() { }; +// @Filename: /a/c/y.ts +[|f1/*0*/();|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/a/c/y.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import { f1 } from "b/x"; + +f1();`, + }, nil /*preferences*/) + f.VerifyImportFixAtPosition(t, []string{ + `import { f1 } from "../b/x"; + +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportDefault0_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportDefault0_test.go new file mode 100644 index 0000000000..f74774cf90 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportDefault0_test.go @@ -0,0 +1,23 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportDefault0(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|f1/*0*/();|] +// @Filename: module.ts +export default function f1() { };` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import f1 from "./module"; + +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportExportEqualsCommonJSInteropOn_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportExportEqualsCommonJSInteropOn_test.go new file mode 100644 index 0000000000..b058fb5b34 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportExportEqualsCommonJSInteropOn_test.go @@ -0,0 +1,62 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportExportEqualsCommonJSInteropOn(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Module: commonjs +// @EsModuleInterop: true +// @Filename: /foo.d.ts +declare module "bar" { + const bar: number; + export = bar; +} +declare module "foo" { + const foo: number; + export = foo; +} +declare module "es" { + const es = 0; + export default es; +} +// @Filename: /a.ts +import bar = require("bar"); + +foo +// @Filename: /b.ts +foo +// @Filename: /c.ts +import es from "es"; +import bar = require("bar"); + +foo` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/a.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import bar = require("bar"); +import foo = require("foo"); + +foo`, + }, nil /*preferences*/) + f.GoToFile(t, "/b.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import foo from "foo"; + +foo`, + }, nil /*preferences*/) + f.GoToFile(t, "/c.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import es from "es"; +import bar = require("bar"); +import foo = require("foo"); + +foo`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportExportEqualsESNextInteropOff_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportExportEqualsESNextInteropOff_test.go new file mode 100644 index 0000000000..b8b70b3191 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportExportEqualsESNextInteropOff_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportExportEqualsESNextInteropOff(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Module: esnext +// @Filename: /foo.d.ts +declare module "foo" { + const foo: number; + export = foo; +} +// @Filename: /index.ts +foo` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/index.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import foo from "foo"; + +foo`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportExportEqualsESNextInteropOn_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportExportEqualsESNextInteropOn_test.go new file mode 100644 index 0000000000..acb8eaf0d3 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportExportEqualsESNextInteropOn_test.go @@ -0,0 +1,30 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportExportEqualsESNextInteropOn(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @EsModuleInterop: true +// @Module: es2015 +// @Filename: /foo.d.ts +declare module "foo" { + const foo: number; + export = foo; +} +// @Filename: /index.ts +[|foo|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/index.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import foo from "foo"; + +foo`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportFile0_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportFile0_test.go new file mode 100644 index 0000000000..b72c4f7f00 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportFile0_test.go @@ -0,0 +1,24 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportFile0(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|f1/*0*/();|] +// @Filename: jalapeño.ts +export function f1() {} +export var v1 = 5;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { f1 } from "./jalapeño"; + +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportFile1_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportFile1_test.go new file mode 100644 index 0000000000..a4babb34d7 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportFile1_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportFile1(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|/// +f1/*0*/();|] +// @Filename: Module.ts +export function f1() {} +export var v1 = 5; +// @Filename: tripleSlashReference.ts +var x = 5;/*dummy*/` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `/// + +import { f1 } from "./Module"; + +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportFile2_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportFile2_test.go new file mode 100644 index 0000000000..a1e91b616c --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportFile2_test.go @@ -0,0 +1,24 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportFile2(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|f1/*0*/();|] +// @Filename: ../../other_dir/module.ts +export var v1 = 5; +export function f1();` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { f1 } from "../../other_dir/module"; + +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportFile3_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportFile3_test.go new file mode 100644 index 0000000000..056d7c4900 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportFile3_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportFile3(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|let t: XXX/*0*/.I;|] +// @Filename: ./module.ts +export module XXX { + export interface I { + } +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { XXX } from "./module"; + +let t: XXX.I;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportFile4_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportFile4_test.go new file mode 100644 index 0000000000..d1adddbd81 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportFile4_test.go @@ -0,0 +1,27 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportFile4(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|let t: A/*0*/.B.I;|] +// @Filename: ./module.ts +export namespace A { + export namespace B { + export interface I { } + } +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { A } from "./module"; + +let t: A.B.I;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportFileAllComments_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportFileAllComments_test.go new file mode 100644 index 0000000000..f80f1c7ec9 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportFileAllComments_test.go @@ -0,0 +1,47 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportFileAllComments(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|/*! + * This is a license or something + */ +/// +/// +/// +/** + * This is a comment intended to be attached to this interface + */ +export interface SomeInterface { +} +f1/*0*/();|] +// @Filename: module.ts +export function f1() {} +export var v1 = 5;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `/*! + * This is a license or something + */ +/// +/// +/// + +import { f1 } from "./module"; + +/** + * This is a comment intended to be attached to this interface + */ +export interface SomeInterface { +} +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportFileDetachedComments_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportFileDetachedComments_test.go new file mode 100644 index 0000000000..9cb3620715 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportFileDetachedComments_test.go @@ -0,0 +1,34 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportFileDetachedComments(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|/** + * This is a comment intended to be attached to this interface + */ +export interface SomeInterface { +} +f1/*0*/();|] +// @Filename: module.ts +export function f1() {} +export var v1 = 5;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { f1 } from "./module"; + +/** + * This is a comment intended to be attached to this interface + */ +export interface SomeInterface { +} +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyle0_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyle0_test.go new file mode 100644 index 0000000000..40922da0e1 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyle0_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportFileQuoteStyle0(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|import { v2 } from './module2'; + +f1/*0*/();|] +// @Filename: module1.ts +export function f1() {} +// @Filename: module2.ts +export var v2 = 6;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { f1 } from './module1'; +import { v2 } from './module2'; + +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyle1_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyle1_test.go new file mode 100644 index 0000000000..04cdc80d4b --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyle1_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportFileQuoteStyle1(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|import { v2 } from "./module2"; + +f1/*0*/();|] +// @Filename: module1.ts +export function f1() {} +// @Filename: module2.ts +export var v2 = 6;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { f1 } from "./module1"; +import { v2 } from "./module2"; + +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyle2_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyle2_test.go new file mode 100644 index 0000000000..464fae8baa --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyle2_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportFileQuoteStyle2(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|import m2 = require('./module2'); + +f1/*0*/();|] +// @Filename: module1.ts +export function f1() {} +// @Filename: module2.ts +export var v2 = 6;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { f1 } from './module1'; +import m2 = require('./module2'); + +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyle3_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyle3_test.go new file mode 100644 index 0000000000..f7799d0470 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyle3_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportFileQuoteStyle3(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|export { v2 } from './module2'; + +f1/*0*/();|] +// @Filename: module1.ts +export function f1() {} +// @Filename: module2.ts +export var v2 = 6;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { f1 } from './module1'; + +export { v2 } from './module2'; + +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyleMixed0_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyleMixed0_test.go new file mode 100644 index 0000000000..5be114b185 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyleMixed0_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportFileQuoteStyleMixed0(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|import { v2 } from "./module2"; +import { v3 } from './module3'; + +f1/*0*/();|] +// @Filename: module1.ts +export function f1() {} +// @Filename: module2.ts +export var v2 = 6; +// @Filename: module3.ts +export var v3 = 6;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { f1 } from "./module1"; +import { v2 } from "./module2"; +import { v3 } from './module3'; + +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyleMixed1_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyleMixed1_test.go new file mode 100644 index 0000000000..ea402be77d --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyleMixed1_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportFileQuoteStyleMixed1(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|import { v2 } from './module2'; +import { v3 } from "./module3"; + +f1/*0*/();|] +// @Filename: module1.ts +export function f1() {} +// @Filename: module2.ts +export var v2 = 6; +// @Filename: module3.ts +export var v3 = 6;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { f1 } from './module1'; +import { v2 } from './module2'; +import { v3 } from "./module3"; + +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportFromAtTypesScopedPackage_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportFromAtTypesScopedPackage_test.go new file mode 100644 index 0000000000..1bddf142ab --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportFromAtTypesScopedPackage_test.go @@ -0,0 +1,24 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportFromAtTypesScopedPackage(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|f1/*0*/();|] +// @Filename: node_modules/@types/myLib__scoped/index.d.ts +export function f1() {} +export var v1 = 5;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { f1 } from "@myLib/scoped"; + +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportFromAtTypes_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportFromAtTypes_test.go new file mode 100644 index 0000000000..9999dd1b94 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportFromAtTypes_test.go @@ -0,0 +1,24 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportFromAtTypes(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|f1/*0*/();|] +// @Filename: node_modules/@types/myLib/index.d.ts +export function f1() {} +export var v1 = 5;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { f1 } from "myLib"; + +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportIndex_notForClassicResolution_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportIndex_notForClassicResolution_test.go new file mode 100644 index 0000000000..28987429d2 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportIndex_notForClassicResolution_test.go @@ -0,0 +1,37 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportIndex_notForClassicResolution(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @moduleResolution: classic +// @Filename: /a/index.ts +export const foo = 0; +// @Filename: /node_modules/x/index.d.ts +export const bar = 0; +// @Filename: /b.ts +[|foo;|] +// @Filename: /c.ts +[|bar;|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/a/index.ts") + f.GoToFile(t, "/b.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import { foo } from "./a/index"; + +foo;`, + }, nil /*preferences*/) + f.GoToFile(t, "/c.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import { bar } from "./node_modules/x/index"; + +bar;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportIndex_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportIndex_test.go new file mode 100644 index 0000000000..997183b8bb --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportIndex_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportIndex(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /a/index.ts +export const foo = 0; +// @Filename: /b.ts +[|/**/foo;|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/a/index.ts") + f.GoToFile(t, "/b.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import { foo } from "./a"; + +foo;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportNodeModules0_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportNodeModules0_test.go new file mode 100644 index 0000000000..37276238c6 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportNodeModules0_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportNodeModules0(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|f1/*0*/();|] +// @Filename: ../package.json +{ "dependencies": { "fake-module": "latest" } } +// @Filename: ../node_modules/fake-module/index.ts +export var v1 = 5; +export function f1(); +// @Filename: ../node_modules/fake-module/package.json +{}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { f1 } from "fake-module"; + +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportNodeModules1_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportNodeModules1_test.go new file mode 100644 index 0000000000..36d5ac9762 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportNodeModules1_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportNodeModules1(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|f1/*0*/();|] +// @Filename: ../package.json +{ "dependencies": { "fake-module": "latest" } } +// @Filename: ../node_modules/fake-module/nested.ts +export var v1 = 5; +export function f1();` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { f1 } from "fake-module/nested"; + +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportNodeModules2_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportNodeModules2_test.go new file mode 100644 index 0000000000..1c3c9f578b --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportNodeModules2_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportNodeModules2(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|f1/*0*/();|] +// @Filename: ../package.json +{ "dependencies": { "fake-module": "latest" } } +// @Filename: ../node_modules/fake-module/notindex.d.ts +export var v1 = 5; +export function f1(); +// @Filename: ../node_modules/fake-module/notindex.js +module.exports = { + v1: 5, + f1: function () {} +}; +// @Filename: ../node_modules/fake-module/package.json +{ "main":"./notindex.js", "typings":"./notindex.d.ts" }` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { f1 } from "fake-module"; + +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportNodeModules3_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportNodeModules3_test.go new file mode 100644 index 0000000000..93e39ffc5b --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportNodeModules3_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportNodeModules3(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /a.ts +[|f1/*0*/();|] +// @Filename: /node_modules/@types/random/index.d.ts +export var v1 = 5; +export function f1();` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { f1 } from "random"; + +f1();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportNodeModules4_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportNodeModules4_test.go new file mode 100644 index 0000000000..db140c6eaf --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportNodeModules4_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportNodeModules4(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|f1/*0*/('');|] +// @Filename: package.json +{ "dependencies": { "package-name": "latest" } } +// @Filename: node_modules/package-name/bin/lib/libfile.d.ts +export function f1(text: string): string; +// @Filename: node_modules/package-name/bin/lib/libfile.js +function f1(text) { } +exports.f1 = f1; +// @Filename: node_modules/package-name/package.json +{ + "main": "bin/lib/libfile.js", + "types": "bin/lib/libfile.d.ts" +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { f1 } from "package-name"; + +f1('');`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportNodeModules6_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportNodeModules6_test.go new file mode 100644 index 0000000000..93c9eb9174 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportNodeModules6_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportNodeModules6(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|f1/*0*/('');|] +// @Filename: package.json +{ "dependencies": { "package-name": "latest" } } +// @Filename: node_modules/package-name/bin/lib/index.d.ts +export function f1(text: string): string; +// @Filename: node_modules/package-name/bin/lib/index.js +function f1(text) { } +exports.f1 = f1; +// @Filename: node_modules/package-name/package.json +{ + "main": "bin/lib/index.js", + "types": "bin/lib/index.d.ts" +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { f1 } from "package-name"; + +f1('');`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportNodeModules7_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportNodeModules7_test.go new file mode 100644 index 0000000000..3b0216ab89 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportNodeModules7_test.go @@ -0,0 +1,30 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportNodeModules7(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|f1/*0*/('');|] +// @Filename: package.json +{ "dependencies": { "package-name": "0.0.1" } } +// @Filename: node_modules/package-name/bin/lib/libfile.d.ts +export declare function f1(text: string): string; +// @Filename: node_modules/package-name/bin/lib/libfile.js +function f1(text) {} +exports.f1 = f1; +// @Filename: node_modules/package-name/package.json +{ "main": "bin/lib/libfile.js" }` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { f1 } from "package-name"; + +f1('');`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportNodeModules8_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportNodeModules8_test.go new file mode 100644 index 0000000000..3be79505b8 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportNodeModules8_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportNodeModules8(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|f1/*0*/('');|] +// @Filename: package.json +{ "dependencies": { "@scope/package-name": "latest" } } +// @Filename: node_modules/@scope/package-name/bin/lib/index.d.ts +export function f1(text: string): string; +// @Filename: node_modules/@scope/package-name/bin/lib/index.js +function f1(text) { } +exports.f1 = f1; +// @Filename: node_modules/@scope/package-name/package.json +{ + "main": "bin/lib/index.js", + "types": "bin/lib/index.d.ts" +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { f1 } from "@scope/package-name"; + +f1('');`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportPaths0_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportPaths0_test.go new file mode 100644 index 0000000000..508996c694 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportPaths0_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportPaths0(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|foo/*0*/();|] +// @Filename: folder_a/f2.ts +export function foo() {}; +// @Filename: tsconfig.json +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "a": [ "folder_a/f2" ] + } + } +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { foo } from "a"; + +foo();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportPaths1_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportPaths1_test.go new file mode 100644 index 0000000000..96f8460d4f --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportPaths1_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportPaths1(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|foo/*0*/();|] +// @Filename: folder_b/f2.ts +export function foo() {}; +// @Filename: tsconfig.json +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "b/*": [ "folder_b/*" ] + } + } +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { foo } from "b/f2"; + +foo();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportPaths2_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportPaths2_test.go new file mode 100644 index 0000000000..b291f27f7b --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportPaths2_test.go @@ -0,0 +1,37 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportPaths2(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `[|foo/*0*/();|] +// @Filename: folder_b/index.ts +export function foo() {}; +// @Filename: tsconfig.path.json +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "b": [ "folder_b/index" ] + } + } +} +// @Filename: tsconfig.json +{ + "extends": "./tsconfig.path", + "compilerOptions": { } +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { foo } from "b"; + +foo();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportPaths_withExtension_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportPaths_withExtension_test.go new file mode 100644 index 0000000000..8c137c06c9 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportPaths_withExtension_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportPaths_withExtension(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /src/a.ts +[|foo|] +// @Filename: /src/thisHasPathMapping.ts +export function foo() {}; +// @Filename: /tsconfig.json +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "foo": ["src/thisHasPathMapping.ts"] + } + } +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { foo } from "foo"; + +foo`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportPaths_withLeadingDotSlash_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportPaths_withLeadingDotSlash_test.go new file mode 100644 index 0000000000..35d33b1f8d --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportPaths_withLeadingDotSlash_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportPaths_withLeadingDotSlash(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /a.ts +[|foo|] +// @Filename: /thisHasPathMapping.ts +export function foo() {}; +// @Filename: /tsconfig.json +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "foo": ["././thisHasPathMapping"] + } + } +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { foo } from "foo"; + +foo`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportPaths_withParentRelativePath_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportPaths_withParentRelativePath_test.go new file mode 100644 index 0000000000..f52909c872 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportPaths_withParentRelativePath_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportPaths_withParentRelativePath(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /src/a.ts +[|foo|] +// @Filename: /thisHasPathMapping.ts +export function foo() {}; +// @Filename: /tsconfig.json +{ + "compilerOptions": { + "baseUrl": "src", + "paths": { + "foo": ["..\\thisHasPathMapping"] + } + } +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { foo } from "foo"; + +foo`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportRootDirs0_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportRootDirs0_test.go new file mode 100644 index 0000000000..c192ad7931 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportRootDirs0_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportRootDirs0(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: a/f1.ts +[|foo/*0*/();|] +// @Filename: b/c/f2.ts +export function foo() {}; +// @Filename: tsconfig.json +{ + "compilerOptions": { + "rootDirs": [ + "a", + "b/c" + ] + } +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { foo } from "./f2"; + +foo();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportRootDirs1_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportRootDirs1_test.go new file mode 100644 index 0000000000..b122ccd697 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportRootDirs1_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportRootDirs1(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: a/f1.ts +[|foo/*0*/();|] +// @Filename: a/b/index.ts +export function foo() {}; +// @Filename: tsconfig.json +{ + "compilerOptions": { + "rootDirs": [ + "a" + ] + } +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { foo } from "./b"; + +foo();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportTypeRoots0_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportTypeRoots0_test.go new file mode 100644 index 0000000000..aad50b5007 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportTypeRoots0_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportTypeRoots0(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: a/f1.ts +[|foo/*0*/();|] +// @Filename: types/random/index.ts +export function foo() {}; +// @Filename: tsconfig.json +{ + "compilerOptions": { + "typeRoots": [ + "./types" + ] + } +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { foo } from "../types/random"; + +foo();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixNewImportTypeRoots1_test.go b/internal/fourslash/tests/gen/importNameCodeFixNewImportTypeRoots1_test.go new file mode 100644 index 0000000000..c92b250137 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixNewImportTypeRoots1_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixNewImportTypeRoots1(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: a/f1.ts +[|foo/*0*/();|] +// @Filename: types/random/index.ts +export function foo() {}; +// @Filename: tsconfig.json +{ + "compilerOptions": { + "baseUrl": ".", + "typeRoots": [ + "./types" + ] + } +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { foo } from "types/random"; + +foo();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixOptionalImport0_test.go b/internal/fourslash/tests/gen/importNameCodeFixOptionalImport0_test.go new file mode 100644 index 0000000000..38ad76631d --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixOptionalImport0_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixOptionalImport0(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: a/f1.ts +[|import * as ns from "./foo"; +foo/*0*/();|] +// @Filename: a/foo/bar.ts +export function foo() {}; +// @Filename: a/foo.ts +export { foo } from "./foo/bar";` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import * as ns from "./foo"; +ns.foo();`, + `import * as ns from "./foo"; +import { foo } from "./foo"; +foo();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixOptionalImport1_test.go b/internal/fourslash/tests/gen/importNameCodeFixOptionalImport1_test.go new file mode 100644 index 0000000000..a086546674 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixOptionalImport1_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixOptionalImport1(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: a/f1.ts +[|foo/*0*/();|] +// @Filename: a/node_modules/bar/index.ts +export function foo() {}; +// @Filename: a/foo.ts +export { foo } from "bar";` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { foo } from "bar"; + +foo();`, + `import { foo } from "./foo"; + +foo();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixShebang_test.go b/internal/fourslash/tests/gen/importNameCodeFixShebang_test.go new file mode 100644 index 0000000000..d5f6f4d062 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixShebang_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixShebang(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /a.ts +export const foo = 0; +// @Filename: /b.ts +[|#!/usr/bin/env node +foo/**/|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/a.ts") + f.GoToFile(t, "/b.ts") + f.VerifyImportFixAtPosition(t, []string{ + `#!/usr/bin/env node + +import { foo } from "./a"; + +foo`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixUMDGlobal0_test.go b/internal/fourslash/tests/gen/importNameCodeFixUMDGlobal0_test.go new file mode 100644 index 0000000000..117fb890cb --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixUMDGlobal0_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixUMDGlobal0(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @AllowSyntheticDefaultImports: false +// @Module: es2015 +// @Filename: a/f1.ts +[|export function test() { }; +bar1/*0*/.bar;|] +// @Filename: a/foo.d.ts +export declare function bar(): number; +export as namespace bar1; ` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import * as bar1 from "./foo"; + +export function test() { }; +bar1.bar;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixUMDGlobal1_test.go b/internal/fourslash/tests/gen/importNameCodeFixUMDGlobal1_test.go new file mode 100644 index 0000000000..b52bd9657b --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixUMDGlobal1_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixUMDGlobal1(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @AllowSyntheticDefaultImports: false +// @Module: esnext +// @Filename: a/f1.ts +[|import { bar } from "./foo"; + +export function test() { }; +bar1/*0*/.bar();|] +// @Filename: a/foo.d.ts +export declare function bar(): number; +export as namespace bar1; ` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import * as bar1 from "./foo"; +import { bar } from "./foo"; + +export function test() { }; +bar1.bar();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixUMDGlobalJavaScript_test.go b/internal/fourslash/tests/gen/importNameCodeFixUMDGlobalJavaScript_test.go new file mode 100644 index 0000000000..7fadc60437 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixUMDGlobalJavaScript_test.go @@ -0,0 +1,31 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixUMDGlobalJavaScript(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @AllowSyntheticDefaultImports: false +// @Module: commonjs +// @CheckJs: true +// @AllowJs: true +// @Filename: a/f1.js +[|export function test() { }; +bar1/*0*/.bar;|] +// @Filename: a/foo.d.ts +export declare function bar(): number; +export as namespace bar1; ` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import * as bar1 from "./foo"; + +export function test() { }; +bar1.bar;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixUMDGlobalReact0_test.go b/internal/fourslash/tests/gen/importNameCodeFixUMDGlobalReact0_test.go new file mode 100644 index 0000000000..97a304325c --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixUMDGlobalReact0_test.go @@ -0,0 +1,50 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixUMDGlobalReact0(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @jsx: react +// @allowSyntheticDefaultImports: false +// @module: es2015 +// @moduleResolution: bundler +// @Filename: /node_modules/@types/react/index.d.ts +export = React; +export as namespace React; +declare namespace React { + export class Component { render(): JSX.Element | null; } +} +declare global { + namespace JSX { + interface Element {} + } +} +// @Filename: /a.tsx +[|import { Component } from "react"; +export class MyMap extends Component { } +;|] +// @Filename: /b.tsx +[|import { Component } from "react"; +<>;|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/a.tsx") + f.VerifyImportFixAtPosition(t, []string{ + `import * as React from "react"; +import { Component } from "react"; +export class MyMap extends Component { } +;`, + }, nil /*preferences*/) + f.GoToFile(t, "/b.tsx") + f.VerifyImportFixAtPosition(t, []string{ + `import * as React from "react"; +import { Component } from "react"; +<>;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixUMDGlobalReact1_test.go b/internal/fourslash/tests/gen/importNameCodeFixUMDGlobalReact1_test.go new file mode 100644 index 0000000000..a4772340ee --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixUMDGlobalReact1_test.go @@ -0,0 +1,41 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixUMDGlobalReact1(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @jsx: react +// @allowSyntheticDefaultImports: false +// @module: es2015 +// @moduleResolution: bundler +// @Filename: /node_modules/@types/react/index.d.ts +export = React; +export as namespace React; +declare namespace React { + export class Component { render(): JSX.Element | null; } +} +declare global { + namespace JSX { + interface Element {} + } +} +// @Filename: /a.tsx +[|import { Component } from "react"; +export class MyMap extends Component { } +;|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/a.tsx") + f.VerifyImportFixAtPosition(t, []string{ + `import * as React from "react"; +import { Component } from "react"; +export class MyMap extends Component { } +;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFixUMDGlobalReact2_test.go b/internal/fourslash/tests/gen/importNameCodeFixUMDGlobalReact2_test.go new file mode 100644 index 0000000000..4302412c8d --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFixUMDGlobalReact2_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFixUMDGlobalReact2(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @jsx: react +// @jsxFactory: factory +// @Filename: /factory.ts +export function factory() { return {}; } +declare global { + namespace JSX { + interface Element {} + } +} +// @Filename: /a.tsx +[|
|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/a.tsx") + f.VerifyImportFixAtPosition(t, []string{ + `import { factory } from "./factory"; + +
`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_HeaderComment1_test.go b/internal/fourslash/tests/gen/importNameCodeFix_HeaderComment1_test.go new file mode 100644 index 0000000000..7bc55cb9ae --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_HeaderComment1_test.go @@ -0,0 +1,36 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_HeaderComment1(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /a.ts +export const foo = 0; +// @Filename: /b.ts +export const bar = 0; +// @Filename: /c.ts +/*-------------------- + * Copyright Header + *--------------------*/ + +import { bar } from "./b"; +foo;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/c.ts") + f.VerifyImportFixAtPosition(t, []string{ + `/*-------------------- + * Copyright Header + *--------------------*/ + +import { foo } from "./a"; +import { bar } from "./b"; +foo;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_HeaderComment2_test.go b/internal/fourslash/tests/gen/importNameCodeFix_HeaderComment2_test.go new file mode 100644 index 0000000000..36b33f05fe --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_HeaderComment2_test.go @@ -0,0 +1,42 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_HeaderComment2(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /a.ts +export const foo = 0; +// @Filename: /b.ts +export const bar = 0; +// @Filename: /c.ts +/*-------------------- + * Copyright Header + *--------------------*/ + +const afterHeader = 1; + +// non-header comment +import { bar } from "./b"; +foo;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/c.ts") + f.VerifyImportFixAtPosition(t, []string{ + `/*-------------------- + * Copyright Header + *--------------------*/ + +const afterHeader = 1; + +import { foo } from "./a"; +// non-header comment +import { bar } from "./b"; +foo;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_avoidRelativeNodeModules_test.go b/internal/fourslash/tests/gen/importNameCodeFix_avoidRelativeNodeModules_test.go new file mode 100644 index 0000000000..4b0885f69d --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_avoidRelativeNodeModules_test.go @@ -0,0 +1,34 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_avoidRelativeNodeModules(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /a/index.d.ts +// @Symlink: /b/node_modules/a/index.d.ts +// @Symlink: /c/node_modules/a/index.d.ts +export const a: number; +// @Filename: /b/index.ts +// @Symlink: /c/node_modules/b/index.d.ts +import { a } from 'a' +export const b: number; +// @Filename: /c/a_user.ts +import { a } from "a"; +// @Filename: /c/foo.ts +[|import { b } from "b"; +a;|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/c/foo.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import { a } from "a"; +import { b } from "b"; +a;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_commonjs_allowSynthetic_test.go b/internal/fourslash/tests/gen/importNameCodeFix_commonjs_allowSynthetic_test.go new file mode 100644 index 0000000000..bf093cc95d --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_commonjs_allowSynthetic_test.go @@ -0,0 +1,35 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_commonjs_allowSynthetic(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @module: esnext +// @moduleResolution: bundler +// @allowJs: true +// @checkJs: true +// @allowSyntheticDefaultImports: true +// @Filename: /test_module.js +const MY_EXPORTS = {} +module.exports = MY_EXPORTS; +// @Filename: /index.js +const newVar = { + any: MY_EXPORTS/**/, +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `const MY_EXPORTS = require("./test_module"); + +const newVar = { + any: MY_EXPORTS, +}`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_defaultExport_test.go b/internal/fourslash/tests/gen/importNameCodeFix_defaultExport_test.go new file mode 100644 index 0000000000..5c1f1252e3 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_defaultExport_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_defaultExport(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @module: esnext +// @allowJs: true +// @checkJs: true +// @Filename: /a.js +class C {} +export default C; +// @Filename: /b.js +[|C;|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/b.js") + f.VerifyImportFixAtPosition(t, []string{ + `import C from "./a"; + +C;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_dollar_test.go b/internal/fourslash/tests/gen/importNameCodeFix_dollar_test.go new file mode 100644 index 0000000000..0b52076265 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_dollar_test.go @@ -0,0 +1,27 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_dollar(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @module: esnext +// @moduleResolution: bundler +// @Filename: /node_modules/qwik/index.d.ts +export declare const $: any; +// @Filename: /index.ts +import {} from "qwik"; +$/**/` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { $ } from "qwik"; +$`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_fileWithNoTrailingNewline_test.go b/internal/fourslash/tests/gen/importNameCodeFix_fileWithNoTrailingNewline_test.go new file mode 100644 index 0000000000..819c94f71c --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_fileWithNoTrailingNewline_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_fileWithNoTrailingNewline(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /a.ts +export const foo = 0; +// @Filename: /b.ts +export const bar = 0; +// @Filename: /c.ts +foo; +import { bar } from "./b";` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/c.ts") + f.VerifyImportFixAtPosition(t, []string{ + `foo; +import { foo } from "./a"; +import { bar } from "./b";`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_fromPathMapping_test.go b/internal/fourslash/tests/gen/importNameCodeFix_fromPathMapping_test.go new file mode 100644 index 0000000000..dc71d0e7eb --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_fromPathMapping_test.go @@ -0,0 +1,34 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_fromPathMapping(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /a.ts +export const foo = 0; +// @Filename: /x/y.ts +foo; +// @Filename: /tsconfig.json +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@root/*": ["*"], + } + } +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/x/y.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import { foo } from "@root/a"; + +foo;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_getCanonicalFileName_test.go b/internal/fourslash/tests/gen/importNameCodeFix_getCanonicalFileName_test.go new file mode 100644 index 0000000000..22ec6d259f --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_getCanonicalFileName_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_getCanonicalFileName(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /howNow/node_modules/brownCow/index.d.ts +export const foo: number; +// @Filename: /howNow/a.ts +foo;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/howNow/a.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import { foo } from "brownCow"; + +foo;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_importType1_test.go b/internal/fourslash/tests/gen/importNameCodeFix_importType1_test.go new file mode 100644 index 0000000000..058e1a75f8 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_importType1_test.go @@ -0,0 +1,37 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_importType1(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @verbatimModuleSyntax: true +// @module: es2015 +// @Filename: /exports.ts +export default someValue = 0; +export function Component() {} +export interface ComponentProps {} +// @Filename: /a.ts +import { Component } from "./exports.js"; +interface MoreProps extends /*a*/ComponentProps {} +// @Filename: /b.ts +import someValue from "./exports.js"; +interface MoreProps extends /*b*/ComponentProps {}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "a") + f.VerifyImportFixAtPosition(t, []string{ + `import { Component, type ComponentProps } from "./exports.js"; +interface MoreProps extends ComponentProps {}`, + }, nil /*preferences*/) + f.GoToMarker(t, "b") + f.VerifyImportFixAtPosition(t, []string{ + `import someValue, { type ComponentProps } from "./exports.js"; +interface MoreProps extends ComponentProps {}`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_importType2_test.go b/internal/fourslash/tests/gen/importNameCodeFix_importType2_test.go new file mode 100644 index 0000000000..1a09b188c4 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_importType2_test.go @@ -0,0 +1,57 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_importType2(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @verbatimModuleSyntax: true +// @module: es2015 +// @Filename: /exports1.ts +export default interface SomeType {} +export interface OtherType {} +export interface OtherOtherType {} +export const someValue = 0; +// @Filename: /a.ts +import type SomeType from "./exports1.js"; +someValue/*a*/ +// @Filename: /b.ts +import { someValue } from "./exports1.js"; +const b: SomeType/*b*/ = someValue; +// @Filename: /c.ts +import type SomeType from "./exports1.js"; +const x: OtherType/*c*/ +// @Filename: /d.ts +import type { OtherType } from "./exports1.js"; +const x: OtherOtherType/*d*/` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "a") + f.VerifyImportFixAtPosition(t, []string{ + `import type SomeType from "./exports1.js"; +import { someValue } from "./exports1.js"; +someValue`, + }, nil /*preferences*/) + f.GoToMarker(t, "b") + f.VerifyImportFixAtPosition(t, []string{ + `import type SomeType from "./exports1.js"; +import { someValue } from "./exports1.js"; +const b: SomeType = someValue;`, + }, nil /*preferences*/) + f.GoToMarker(t, "c") + f.VerifyImportFixAtPosition(t, []string{ + `import type { OtherType } from "./exports1.js"; +import type SomeType from "./exports1.js"; +const x: OtherType`, + }, nil /*preferences*/) + f.GoToMarker(t, "d") + f.VerifyImportFixAtPosition(t, []string{ + `import type { OtherOtherType, OtherType } from "./exports1.js"; +const x: OtherOtherType`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_importType3_test.go b/internal/fourslash/tests/gen/importNameCodeFix_importType3_test.go new file mode 100644 index 0000000000..abb40aa078 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_importType3_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_importType3(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @verbatimModuleSyntax: true +// @module: es2015 +// @Filename: /exports.ts +class SomeClass {} +export type { SomeClass }; +// @Filename: /a.ts +import {} from "./exports.js"; +function takeSomeClass(c: SomeClass/**/)` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { type SomeClass } from "./exports.js"; +function takeSomeClass(c: SomeClass)`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_importType4_test.go b/internal/fourslash/tests/gen/importNameCodeFix_importType4_test.go new file mode 100644 index 0000000000..3fadc0f617 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_importType4_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_importType4(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @preserveValueImports: true +// @isolatedModules: true +// @module: es2015 +// @Filename: /exports.ts +export interface SomeInterface {} +export class SomePig {} +// @Filename: /a.ts +import type { SomeInterface } from "./exports.js"; +new SomePig/**/` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { SomePig, type SomeInterface } from "./exports.js"; +new SomePig`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_importType5_test.go b/internal/fourslash/tests/gen/importNameCodeFix_importType5_test.go new file mode 100644 index 0000000000..e35bdb4cb7 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_importType5_test.go @@ -0,0 +1,27 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_importType5(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @module: es2015 +// @Filename: /exports.ts +export interface SomeInterface {} +export class SomePig {} +// @Filename: /a.ts +import type { SomeInterface, SomePig } from "./exports.js"; +new SomePig/**/` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { SomeInterface, SomePig } from "./exports.js"; +new SomePig`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_importType6_test.go b/internal/fourslash/tests/gen/importNameCodeFix_importType6_test.go new file mode 100644 index 0000000000..775fc58e70 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_importType6_test.go @@ -0,0 +1,30 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_importType6(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @module: es2015 +// @esModuleInterop: true +// @jsx: react +// @Filename: /types.d.ts +declare module "react" { var React: any; export = React; export as namespace React; } +// @Filename: /a.tsx +import type React from "react"; +function Component() {} +()` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import React from "react"; +function Component() {} +()`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_importType7_test.go b/internal/fourslash/tests/gen/importNameCodeFix_importType7_test.go new file mode 100644 index 0000000000..3f5d77e92a --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_importType7_test.go @@ -0,0 +1,54 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_importType7(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @module: es2015 +// @Filename: /exports.ts +export interface SomeInterface {} +export class SomePig {} +// @Filename: /a.ts +import { + type SomeInterface, + type SomePig, +} from "./exports.js"; +new SomePig/**/` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { + SomePig, + type SomeInterface, +} from "./exports.js"; +new SomePig`, + }, nil /*preferences*/) + f.VerifyImportFixAtPosition(t, []string{ + `import { + SomePig, + type SomeInterface, +} from "./exports.js"; +new SomePig`, + }, nil /*preferences*/) + f.VerifyImportFixAtPosition(t, []string{ + `import { + type SomeInterface, + SomePig, +} from "./exports.js"; +new SomePig`, + }, nil /*preferences*/) + f.VerifyImportFixAtPosition(t, []string{ + `import { + type SomeInterface, + SomePig, +} from "./exports.js"; +new SomePig`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_importType8_test.go b/internal/fourslash/tests/gen/importNameCodeFix_importType8_test.go new file mode 100644 index 0000000000..f239ddfd14 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_importType8_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_importType8(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @module: es2015 +// @verbatimModuleSyntax: true +// @Filename: /exports.ts +export interface SomeInterface {} +export class SomePig {} +// @Filename: /a.ts +import type { SomeInterface, SomePig } from "./exports.js"; +new SomePig/**/` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { SomePig, type SomeInterface } from "./exports.js"; +new SomePig`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_importType_test.go b/internal/fourslash/tests/gen/importNameCodeFix_importType_test.go new file mode 100644 index 0000000000..a0db33f11e --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_importType_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_importType(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @allowJs: true +// @checkJs: true +// @Filename: /a.js +export {}; +/** @typedef {number} T */ +// @Filename: /b.js +/** @type {T} */ +const x = 0;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/b.js") + f.VerifyImportFixAtPosition(t, []string{ + `/** @type {import("./a").T} */ +const x = 0;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_jsCJSvsESM1_test.go b/internal/fourslash/tests/gen/importNameCodeFix_jsCJSvsESM1_test.go new file mode 100644 index 0000000000..3d5735dd2a --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_jsCJSvsESM1_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_jsCJSvsESM1(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @allowJs: true +// @checkJs: true +// @Filename: types/dep.d.ts +export declare class Dep {} +// @Filename: index.js +Dep/**/ +// @Filename: util.js +import fs from 'fs';` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { Dep } from "./types/dep"; + +Dep`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_jsCJSvsESM2_test.go b/internal/fourslash/tests/gen/importNameCodeFix_jsCJSvsESM2_test.go new file mode 100644 index 0000000000..be73839c63 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_jsCJSvsESM2_test.go @@ -0,0 +1,31 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_jsCJSvsESM2(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @allowJs: true +// @checkJs: true +// @Filename: types/dep.d.ts +export declare class Dep {} +// @Filename: index.js +Dep/**/ +// @Filename: util1.ts +import fs from 'fs'; +// @Filename: util2.js +const fs = require('fs');` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `const { Dep } = require("./types/dep"); + +Dep`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_jsCJSvsESM3_test.go b/internal/fourslash/tests/gen/importNameCodeFix_jsCJSvsESM3_test.go new file mode 100644 index 0000000000..7006d6b90a --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_jsCJSvsESM3_test.go @@ -0,0 +1,34 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_jsCJSvsESM3(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @allowJs: true +// @checkJs: true +// @Filename: types/dep.d.ts +export declare class Dep {} +// @Filename: index.js +import fs from 'fs'; +const path = require('path'); + +Dep/**/ +// @Filename: util2.js +export {};` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import fs from 'fs'; +import { Dep } from './types/dep'; +const path = require('path'); + +Dep`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_jsx1_test.go b/internal/fourslash/tests/gen/importNameCodeFix_jsx1_test.go new file mode 100644 index 0000000000..655a57f416 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_jsx1_test.go @@ -0,0 +1,42 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_jsx1(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @jsx: react +// @Filename: /node_modules/react/index.d.ts +export const React: any; +// @Filename: /a.tsx +[||] +// @Filename: /Foo.tsx +export const Foo = 0; +// @Filename: /c.tsx +import { React } from "react"; +; +// @Filename: /d.tsx +import { Foo } from "./Foo"; +;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/a.tsx") + f.VerifyImportFixAtPosition(t, []string{}, nil /*preferences*/) + f.GoToFile(t, "/c.tsx") + f.VerifyImportFixAtPosition(t, []string{ + `import { React } from "react"; +import { Foo } from "./Foo"; +;`, + }, nil /*preferences*/) + f.GoToFile(t, "/d.tsx") + f.VerifyImportFixAtPosition(t, []string{ + `import { React } from "react"; +import { Foo } from "./Foo"; +;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_jsxOpeningTagImportDefault_test.go b/internal/fourslash/tests/gen/importNameCodeFix_jsxOpeningTagImportDefault_test.go new file mode 100644 index 0000000000..91aeb29425 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_jsxOpeningTagImportDefault_test.go @@ -0,0 +1,31 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_jsxOpeningTagImportDefault(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @module: commonjs +// @jsx: react-jsx +// @Filename: /component.tsx +export default function (props: any) {} +// @Filename: /index.tsx +export function Index() { + return ; +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import Component from "./component"; + +export function Index() { + return ; +}`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_jsxReact17_test.go b/internal/fourslash/tests/gen/importNameCodeFix_jsxReact17_test.go new file mode 100644 index 0000000000..6dbc973a45 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_jsxReact17_test.go @@ -0,0 +1,41 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_jsxReact17(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @jsx: preserve +// @module: commonjs +// @Filename: /node_modules/@types/react/index.d.ts +declare namespace React { + function createElement(): any; +} +export = React; +export as namespace React; + +declare global { + namespace JSX { + interface IntrinsicElements {} + interface IntrinsicAttributes {} + } +} +// @Filename: /component.tsx +import "react"; +export declare function Component(): any; +// @Filename: /index.tsx +();` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { Component } from "./component"; + +();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_order2_test.go b/internal/fourslash/tests/gen/importNameCodeFix_order2_test.go new file mode 100644 index 0000000000..ff7d259f51 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_order2_test.go @@ -0,0 +1,54 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_order2(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /a.ts +export const _aB: number; +export const _Ab: number; +export const aB: number; +export const Ab: number; +// @Filename: /b.ts +[|import { + _aB, + _Ab, + Ab, +} from "./a"; +aB;|] +// @Filename: /c.ts +[|import { + _aB, + _Ab, + Ab, +} from "./a"; +aB;|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/b.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import { + _aB, + _Ab, + Ab, + aB, +} from "./a"; +aB;`, + }, nil /*preferences*/) + f.GoToFile(t, "/c.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import { + _aB, + _Ab, + aB, + Ab, +} from "./a"; +aB;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_order_test.go b/internal/fourslash/tests/gen/importNameCodeFix_order_test.go new file mode 100644 index 0000000000..e24829a31b --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_order_test.go @@ -0,0 +1,31 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_order(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /a.ts +export const foo: number; +// @Filename: /b.ts +export const foo: number; +export const bar: number; +// @Filename: /c.ts +[|import { bar } from "./b"; +foo;|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/c.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import { bar, foo } from "./b"; +foo;`, + `import { foo } from "./a"; +import { bar } from "./b"; +foo;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_pathsWithoutBaseUrl1_test.go b/internal/fourslash/tests/gen/importNameCodeFix_pathsWithoutBaseUrl1_test.go new file mode 100644 index 0000000000..85aa10e76b --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_pathsWithoutBaseUrl1_test.go @@ -0,0 +1,34 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_pathsWithoutBaseUrl1(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: tsconfig.json +{ + "compilerOptions": { + "module": "commonjs", + "paths": { + "@app/*": ["./lib/*"] + } + } +} +// @Filename: index.ts +utils/**/ +// @Filename: lib/utils.ts +export const utils = {};` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { utils } from "@app/utils"; + +utils`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_pathsWithoutBaseUrl2_test.go b/internal/fourslash/tests/gen/importNameCodeFix_pathsWithoutBaseUrl2_test.go new file mode 100644 index 0000000000..ccda7d8e28 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_pathsWithoutBaseUrl2_test.go @@ -0,0 +1,34 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_pathsWithoutBaseUrl2(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /packages/test-package-1/tsconfig.json +{ + "compilerOptions": { + "module": "commonjs", + "paths": { + "test-package-2/*": ["../test-package-2/src/*"] + } + } +} +// @Filename: /packages/test-package-1/src/common/logging.ts +export class Logger {}; +// @Filename: /packages/test-package-1/src/something/index.ts +Logger/**/` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { Logger } from "../common/logging"; + +Logger`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_pnpm1_test.go b/internal/fourslash/tests/gen/importNameCodeFix_pnpm1_test.go new file mode 100644 index 0000000000..2d91a8fd41 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_pnpm1_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_pnpm1(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /home/src/workspaces/project/tsconfig.json +{ "compilerOptions": { "module": "commonjs" } } +// @Filename: /home/src/workspaces/project/node_modules/.pnpm/@types+react@17.0.7/node_modules/@types/react/index.d.ts +export declare function Component(): void; +// @Filename: /home/src/workspaces/project/index.ts +Component/**/ +// @link: /home/src/workspaces/project/node_modules/.pnpm/@types+react@17.0.7/node_modules/@types/react -> /home/src/workspaces/project/node_modules/@types/react` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { Component } from "react"; + +Component`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_preferBaseUrl_test.go b/internal/fourslash/tests/gen/importNameCodeFix_preferBaseUrl_test.go new file mode 100644 index 0000000000..06168a5bfc --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_preferBaseUrl_test.go @@ -0,0 +1,27 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_preferBaseUrl(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /tsconfig.json +{ "compilerOptions": { "baseUrl": "./src" } } +// @Filename: /src/d0/d1/d2/file.ts +foo/**/; +// @Filename: /src/d0/a.ts +export const foo = 0;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/src/d0/d1/d2/file.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import { foo } from "d0/a"; + +foo;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_quoteStyle_test.go b/internal/fourslash/tests/gen/importNameCodeFix_quoteStyle_test.go new file mode 100644 index 0000000000..ee57b2659d --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_quoteStyle_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/ls/lsutil" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_quoteStyle(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /a.ts +export const foo: number; +// @Filename: /b.ts +[|foo;|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/b.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import { foo } from './a'; + +foo;`, + }, &lsutil.UserPreferences{QuotePreference: lsutil.QuotePreference("single")}) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_reExportDefault_test.go b/internal/fourslash/tests/gen/importNameCodeFix_reExportDefault_test.go new file mode 100644 index 0000000000..8a99b77559 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_reExportDefault_test.go @@ -0,0 +1,54 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_reExportDefault(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /user.ts +foo; +// @Filename: /user2.ts +unnamed; +// @Filename: /user3.ts +reExportUnnamed; +// @Filename: /reExportNamed.ts +export { default } from "./named"; +// @Filename: /reExportUnnamed.ts +export { default } from "./unnamed"; +// @Filename: /named.ts +function foo() {} +export default foo; +// @Filename: /unnamed.ts +export default 0;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/user.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import foo from "./named"; + +foo;`, + `import foo from "./reExportNamed"; + +foo;`, + }, nil /*preferences*/) + f.GoToFile(t, "/user2.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import unnamed from "./unnamed"; + +unnamed;`, + `import unnamed from "./reExportUnnamed"; + +unnamed;`, + }, nil /*preferences*/) + f.GoToFile(t, "/user3.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import reExportUnnamed from "./reExportUnnamed"; + +reExportUnnamed;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_reExport_test.go b/internal/fourslash/tests/gen/importNameCodeFix_reExport_test.go new file mode 100644 index 0000000000..f895975f15 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_reExport_test.go @@ -0,0 +1,30 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_reExport(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /a.ts +export default function foo(): void {} +// @Filename: /b.ts +export { default } from "./a"; +// @Filename: /user.ts +[|foo;|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/user.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import foo from "./a"; + +foo;`, + `import foo from "./b"; + +foo;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_shorthandPropertyAssignment1_test.go b/internal/fourslash/tests/gen/importNameCodeFix_shorthandPropertyAssignment1_test.go new file mode 100644 index 0000000000..e828e39af6 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_shorthandPropertyAssignment1_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_shorthandPropertyAssignment1(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /a.ts +export const a = 1; +// @Filename: /b.ts +const b = { /**/a };` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/b.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import { a } from "./a"; + +const b = { a };`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_shorthandPropertyAssignment2_test.go b/internal/fourslash/tests/gen/importNameCodeFix_shorthandPropertyAssignment2_test.go new file mode 100644 index 0000000000..0020bc1031 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_shorthandPropertyAssignment2_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_shorthandPropertyAssignment2(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /a.ts +const a = 1; +export default a; +// @Filename: /b.ts +const b = { /**/a };` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/b.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import a from "./a"; + +const b = { a };`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_sortByDistance_test.go b/internal/fourslash/tests/gen/importNameCodeFix_sortByDistance_test.go new file mode 100644 index 0000000000..f03212e595 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_sortByDistance_test.go @@ -0,0 +1,41 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_sortByDistance(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @module: commonjs +// @Filename: /src/admin/utils/db/db.ts +export const db = {}; +// @Filename: /src/admin/utils/db/index.ts +export * from "./db"; +// @Filename: /src/client/helpers/db.ts +export const db = {}; +// @Filename: /src/client/db.ts +export const db = {}; +// @Filename: /src/client/foo.ts +db/**/` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { db } from "./db"; + +db`, + `import { db } from "./helpers/db"; + +db`, + `import { db } from "../admin/utils/db"; + +db`, + `import { db } from "../admin/utils/db/db"; + +db`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_symlink_own_package_2_test.go b/internal/fourslash/tests/gen/importNameCodeFix_symlink_own_package_2_test.go new file mode 100644 index 0000000000..0381a6302c --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_symlink_own_package_2_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_symlink_own_package_2(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /packages/a/test.ts +// @Symlink: /node_modules/a/test.ts +x; +// @Filename: /packages/a/utils.ts +// @Symlink: /node_modules/a/utils.ts +import {} from "a/utils"; +export const x = 0;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/packages/a/test.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import { x } from "./utils"; + +x;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_symlink_own_package_test.go b/internal/fourslash/tests/gen/importNameCodeFix_symlink_own_package_test.go new file mode 100644 index 0000000000..1e4a28c2da --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_symlink_own_package_test.go @@ -0,0 +1,31 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_symlink_own_package(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /packages/b/b0.ts +// @Symlink: /node_modules/b/b0.ts +x; +// @Filename: /packages/b/b1.ts +// @Symlink: /node_modules/b/b1.ts +import { a } from "a"; +export const x = 0; +// @Filename: /packages/a/index.d.ts +// @Symlink: /node_modules/a/index.d.ts +export const a: number;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/packages/b/b0.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import { x } from "./b1"; + +x;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_symlink_test.go b/internal/fourslash/tests/gen/importNameCodeFix_symlink_test.go new file mode 100644 index 0000000000..ca04ad52c6 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_symlink_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_symlink(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @moduleResolution: bundler +// @noLib: true +// @Filename: /node_modules/real/index.d.ts +// @Symlink: /node_modules/link/index.d.ts +export const foo: number; +// @Filename: /a.ts +import { foo } from "link"; +// @Filename: /b.ts +[|foo;|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/b.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import { foo } from "link"; + +foo;`, + `import { foo } from "real"; + +foo;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_trailingComma_test.go b/internal/fourslash/tests/gen/importNameCodeFix_trailingComma_test.go new file mode 100644 index 0000000000..07f720b6e5 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_trailingComma_test.go @@ -0,0 +1,35 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_trailingComma(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: index.ts +import { + T2, + T1, +} from "./types"; + +const x: T3/**/ +// @Filename: types.ts +export type T1 = 0; +export type T2 = 0; +export type T3 = 0;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyImportFixAtPosition(t, []string{ + `import { + T2, + T1, + T3, +} from "./types"; + +const x: T3`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_tripleSlashOrdering_test.go b/internal/fourslash/tests/gen/importNameCodeFix_tripleSlashOrdering_test.go new file mode 100644 index 0000000000..d03ba3b561 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_tripleSlashOrdering_test.go @@ -0,0 +1,119 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_tripleSlashOrdering(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /tsconfig.json +{ + "compilerOptions": { + "skipDefaultLibCheck": false + } +} +// @Filename: /a.ts +export const x = 0; +// @Filename: /b.ts +// some comment + +/// + +const y = x + 1; +// @Filename: /c.ts +// some comment + +/// + +const y = x + 1; +// @Filename: /d.ts +// some comment + +/// + +const y = x + 1; +// @Filename: /e.ts +// some comment + +/// + +const y = x + 1; +// @Filename: /f.ts +// some comment + +/// + +const y = x + 1; +// @Filename: /g.ts +// some comment + +/// + +const y = x + 1;` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/b.ts") + f.VerifyImportFixAtPosition(t, []string{ + `// some comment + +/// + +import { x } from "./a"; + +const y = x + 1;`, + }, nil /*preferences*/) + f.GoToFile(t, "/c.ts") + f.VerifyImportFixAtPosition(t, []string{ + `// some comment + +/// + +import { x } from "./a"; + +const y = x + 1;`, + }, nil /*preferences*/) + f.GoToFile(t, "/d.ts") + f.VerifyImportFixAtPosition(t, []string{ + `// some comment + +/// + +import { x } from "./a"; + +const y = x + 1;`, + }, nil /*preferences*/) + f.GoToFile(t, "/e.ts") + f.VerifyImportFixAtPosition(t, []string{ + `// some comment + +/// + +import { x } from "./a"; + +const y = x + 1;`, + }, nil /*preferences*/) + f.GoToFile(t, "/f.ts") + f.VerifyImportFixAtPosition(t, []string{ + `// some comment + +/// + +import { x } from "./a"; + +const y = x + 1;`, + }, nil /*preferences*/) + f.GoToFile(t, "/g.ts") + f.VerifyImportFixAtPosition(t, []string{ + `// some comment + +/// + +import { x } from "./a"; + +const y = x + 1;`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_typeOnly_test.go b/internal/fourslash/tests/gen/importNameCodeFix_typeOnly_test.go new file mode 100644 index 0000000000..c81ef37ec3 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_typeOnly_test.go @@ -0,0 +1,27 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_typeOnly(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @module: esnext +// @verbatimModuleSyntax: true +// @Filename: types.ts +export class A {} +// @Filename: index.ts +const a: /**/A` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import type { A } from "./types"; + +const a: A`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_typeUsedAsValue_test.go b/internal/fourslash/tests/gen/importNameCodeFix_typeUsedAsValue_test.go new file mode 100644 index 0000000000..c44a172826 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_typeUsedAsValue_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_typeUsedAsValue(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /a.ts +export class ReadonlyArray {} +// @Filename: /b.ts +[|new ReadonlyArray();|]` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/b.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import { ReadonlyArray } from "./a"; + +new ReadonlyArray();`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importNameCodeFix_withJson_test.go b/internal/fourslash/tests/gen/importNameCodeFix_withJson_test.go new file mode 100644 index 0000000000..3af50482e0 --- /dev/null +++ b/internal/fourslash/tests/gen/importNameCodeFix_withJson_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportNameCodeFix_withJson(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /a.ts +export const a = 'a'; +// @Filename: /b.ts +import "./anything.json"; + +a/**/` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToFile(t, "/b.ts") + f.VerifyImportFixAtPosition(t, []string{ + `import { a } from "./a"; +import "./anything.json"; + +a`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/importTypeCompletions5_test.go b/internal/fourslash/tests/gen/importTypeCompletions5_test.go index 543d69aa3f..a05887ff74 100644 --- a/internal/fourslash/tests/gen/importTypeCompletions5_test.go +++ b/internal/fourslash/tests/gen/importTypeCompletions5_test.go @@ -12,7 +12,7 @@ import ( func TestImportTypeCompletions5(t *testing.T) { t.Parallel() - t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `// @allowSyntheticDefaultImports: false // @esModuleInterop: false diff --git a/internal/fourslash/tests/gen/javascriptModules21_test.go b/internal/fourslash/tests/gen/javascriptModules21_test.go index 9e73e2b5d2..0f3c99ff0d 100644 --- a/internal/fourslash/tests/gen/javascriptModules21_test.go +++ b/internal/fourslash/tests/gen/javascriptModules21_test.go @@ -12,7 +12,7 @@ import ( func TestJavascriptModules21(t *testing.T) { t.Parallel() - t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `// @allowJs: true // @module: system diff --git a/internal/fourslash/tests/gen/moduleNodeNextAutoImport1_test.go b/internal/fourslash/tests/gen/moduleNodeNextAutoImport1_test.go new file mode 100644 index 0000000000..6434da678c --- /dev/null +++ b/internal/fourslash/tests/gen/moduleNodeNextAutoImport1_test.go @@ -0,0 +1,31 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestModuleNodeNextAutoImport1(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /tsconfig.json +{ "compilerOptions": { "module": "nodenext" } } +// @Filename: /package.json +{ "type": "module" } +// @Filename: /mobx.d.ts +export declare function autorun(): void; +// @Filename: /index.ts +autorun/**/ +// @Filename: /utils.ts +import "./mobx.js";` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { autorun } from "./mobx.js"; + +autorun`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/moduleNodeNextAutoImport2_test.go b/internal/fourslash/tests/gen/moduleNodeNextAutoImport2_test.go new file mode 100644 index 0000000000..0803a88deb --- /dev/null +++ b/internal/fourslash/tests/gen/moduleNodeNextAutoImport2_test.go @@ -0,0 +1,31 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestModuleNodeNextAutoImport2(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /tsconfig.json +{ "compilerOptions": { "module": "nodenext" } } +// @Filename: /package.json +{ "type": "module" } +// @Filename: /mobx.d.cts +export declare function autorun(): void; +// @Filename: /index.ts +autorun/**/ +// @Filename: /utils.ts +import "./mobx.cjs";` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { autorun } from "./mobx.cjs"; + +autorun`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/moduleNodeNextAutoImport3_test.go b/internal/fourslash/tests/gen/moduleNodeNextAutoImport3_test.go new file mode 100644 index 0000000000..79a3a1c638 --- /dev/null +++ b/internal/fourslash/tests/gen/moduleNodeNextAutoImport3_test.go @@ -0,0 +1,31 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestModuleNodeNextAutoImport3(t *testing.T) { + t.Parallel() + t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /tsconfig.json +{ "compilerOptions": { "module": "nodenext" } } +// @Filename: /package.json +{ "type": "module" } +// @Filename: /mobx.d.mts +export declare function autorun(): void; +// @Filename: /index.ts +autorun/**/ +// @Filename: /utils.ts +import "./mobx.mjs";` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.GoToMarker(t, "") + f.VerifyImportFixAtPosition(t, []string{ + `import { autorun } from "./mobx.mjs"; + +autorun`, + }, nil /*preferences*/) +} diff --git a/internal/fourslash/tests/gen/tsxCompletion14_test.go b/internal/fourslash/tests/gen/tsxCompletion14_test.go index 364cf42fc3..bf1f8cb687 100644 --- a/internal/fourslash/tests/gen/tsxCompletion14_test.go +++ b/internal/fourslash/tests/gen/tsxCompletion14_test.go @@ -10,7 +10,7 @@ import ( func TestTsxCompletion14(t *testing.T) { t.Parallel() - t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `//@module: commonjs //@jsx: preserve diff --git a/internal/fourslash/tests/gen/tsxCompletion15_test.go b/internal/fourslash/tests/gen/tsxCompletion15_test.go index 90478f63fd..63de791a39 100644 --- a/internal/fourslash/tests/gen/tsxCompletion15_test.go +++ b/internal/fourslash/tests/gen/tsxCompletion15_test.go @@ -10,7 +10,7 @@ import ( func TestTsxCompletion15(t *testing.T) { t.Parallel() - t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `//@module: commonjs //@jsx: preserve diff --git a/internal/ls/autoimportfixes.go b/internal/ls/autoimportfixes.go index 82a7ddaced..e05dec1efe 100644 --- a/internal/ls/autoimportfixes.go +++ b/internal/ls/autoimportfixes.go @@ -116,23 +116,33 @@ func (ls *LanguageService) doAddExistingFix( // ); // if len(existingSpecifiers) > 0 && isSorted != core.TSFalse { - // The sorting preference computed earlier may or may not have validated that these particular - // import specifiers are sorted. If they aren't, `getImportSpecifierInsertionIndex` will return - // nonsense. So if there are existing specifiers, even if we know the sorting preference, we - // need to ensure that the existing specifiers are sorted according to the preference in order - // to do a sorted insertion. - // if we're promoting the clause from type-only, we need to transform the existing imports before attempting to insert the new named imports - // transformedExistingSpecifiers := existingSpecifiers - // if promoteFromTypeOnly && existingSpecifiers { - // transformedExistingSpecifiers = ct.NodeFactory.updateNamedImports( - // importClause.NamedBindings.AsNamedImports(), - // core.SameMap(existingSpecifiers, func(e *ast.ImportSpecifier) *ast.ImportSpecifier { - // return ct.NodeFactory.updateImportSpecifier(e, /*isTypeOnly*/ true, e.propertyName, e.name) - // }), - // ).elements - // } + // The sorting preference computed earlier may or may not have validated that these particular + // import specifiers are sorted. If they aren't, `getImportSpecifierInsertionIndex` will return + // nonsense. So if there are existing specifiers, even if we know the sorting preference, we + // need to ensure that the existing specifiers are sorted according to the preference in order + // to do a sorted insertion. + + // If we're promoting the clause from type-only, we need to transform the existing imports + // before attempting to insert the new named imports (for comparison purposes only) + specsToCompareAgainst := existingSpecifiers + if promoteFromTypeOnly && len(existingSpecifiers) > 0 { + specsToCompareAgainst = core.Map(existingSpecifiers, func(e *ast.Node) *ast.Node { + spec := e.AsImportSpecifier() + var propertyName *ast.Node + if spec.PropertyName != nil { + propertyName = spec.PropertyName + } + syntheticSpec := ct.NodeFactory.NewImportSpecifier( + true, // isTypeOnly + propertyName, + spec.Name(), + ) + return syntheticSpec + }) + } + for _, spec := range newSpecifiers { - insertionIndex := organizeimports.GetImportSpecifierInsertionIndex(existingSpecifiers, spec, specifierComparer) + insertionIndex := organizeimports.GetImportSpecifierInsertionIndex(specsToCompareAgainst, spec, specifierComparer) ct.InsertImportSpecifierAtIndex(sourceFile, spec, importClause.NamedBindings, insertionIndex) } } else if len(existingSpecifiers) > 0 && isSorted.IsTrue() { @@ -168,24 +178,36 @@ func (ls *LanguageService) doAddExistingFix( } if promoteFromTypeOnly { - // !!! promote type-only imports not implemented + // Delete the 'type' keyword from the import clause + typeKeyword := getTypeKeywordOfTypeOnlyImport(importClause, sourceFile) + ct.Delete(sourceFile, typeKeyword) - // ct.delete(sourceFile, getTypeKeywordOfTypeOnlyImport(clause, sourceFile)); - // if (existingSpecifiers) { - // // We used to convert existing specifiers to type-only only if compiler options indicated that - // // would be meaningful (see the `importNameElisionDisabled` utility function), but user - // // feedback indicated a preference for preserving the type-onlyness of existing specifiers - // // regardless of whether it would make a difference in emit. - // for _, specifier := range existingSpecifiers { - // ct.insertModifierBefore(sourceFile, SyntaxKind.TypeKeyword, specifier); - // } - // } + // Add 'type' modifier to existing specifiers (not newly added ones) + // We preserve the type-onlyness of existing specifiers regardless of whether + // it would make a difference in emit (user preference). + if len(existingSpecifiers) > 0 { + for _, specifier := range existingSpecifiers { + if !specifier.AsImportSpecifier().IsTypeOnly { + ct.InsertModifierBefore(sourceFile, ast.KindTypeKeyword, specifier) + } + } + } } default: panic("Unsupported clause kind: " + clause.Kind.String() + "for doAddExistingFix") } } +func getTypeKeywordOfTypeOnlyImport(importClause *ast.ImportClause, sourceFile *ast.SourceFile) *ast.Node { + debug.Assert(importClause.IsTypeOnly(), "import clause must be type-only") + // The first child of a type-only import clause is the 'type' keyword + // import type { foo } from './bar' + // ^^^^ + typeKeyword := astnav.FindChildOfKind(importClause.AsNode(), ast.KindTypeKeyword, sourceFile) + debug.Assert(typeKeyword != nil, "type-only import clause should have a type keyword") + return typeKeyword +} + func addElementToBindingPattern(ct *change.Tracker, sourceFile *ast.SourceFile, bindingPattern *ast.Node, name string, propertyName *string) { element := newBindingElementFromNameAndPropertyName(ct, name, propertyName) if len(bindingPattern.Elements()) > 0 { @@ -287,7 +309,8 @@ func (ls *LanguageService) getNewImports( topLevelTypeOnly := (defaultImport == nil || needsTypeOnly(defaultImport.addAsTypeOnly)) && core.Every(namedImports, func(i *Import) bool { return needsTypeOnly(i.addAsTypeOnly) }) || (compilerOptions.VerbatimModuleSyntax.IsTrue() || ls.UserPreferences().PreferTypeOnlyAutoImports) && - defaultImport != nil && defaultImport.addAsTypeOnly != AddAsTypeOnlyNotAllowed && !core.Some(namedImports, func(i *Import) bool { return i.addAsTypeOnly == AddAsTypeOnlyNotAllowed }) + (defaultImport == nil || defaultImport.addAsTypeOnly != AddAsTypeOnlyNotAllowed) && + !core.Some(namedImports, func(i *Import) bool { return i.addAsTypeOnly == AddAsTypeOnlyNotAllowed }) var defaultImportNode *ast.Node if defaultImport != nil { diff --git a/internal/ls/autoimports.go b/internal/ls/autoimports.go index 7642b31e17..6a44f28299 100644 --- a/internal/ls/autoimports.go +++ b/internal/ls/autoimports.go @@ -356,13 +356,13 @@ func (l *LanguageService) getImportCompletionAction( sourceFile *ast.SourceFile, position int, exportMapKey ExportInfoMapKey, - symbolName string, // !!! needs *string ? + symbolName string, isJsxTagName bool, // formatContext *formattingContext, ) (string, codeAction) { var exportInfos []*SymbolExportInfo // `exportMapKey` should be in the `itemData` of each auto-import completion entry and sent in resolving completion entry requests - exportInfos = l.getExportInfos(ctx, ch, sourceFile, exportMapKey) + exportInfos = l.getExportInfoMap(ctx, ch, sourceFile, exportMapKey) if len(exportInfos) == 0 { panic("Some exportInfo should match the specified exportMapKey") } @@ -482,7 +482,7 @@ func fileContainsPackageImport(sourceFile *ast.SourceFile, packageName string) b } func isImportableSymbol(symbol *ast.Symbol, ch *checker.Checker) bool { - return !ch.IsUndefinedSymbol(symbol) && !ch.IsUnknownSymbol(symbol) && !checker.IsKnownSymbol(symbol) // !!! && !checker.IsPrivateIdentifierSymbol(symbol); + return !ch.IsUndefinedSymbol(symbol) && !ch.IsUnknownSymbol(symbol) && !checker.IsKnownSymbol(symbol) && !checker.IsPrivateIdentifierSymbol(symbol) } func getDefaultLikeExportInfo(moduleSymbol *ast.Symbol, ch *checker.Checker) *ExportInfo { @@ -536,7 +536,7 @@ func (l *LanguageService) getImportFixForSymbol( if isValidTypeOnlySite == nil { isValidTypeOnlySite = ptrTo(ast.IsValidTypeOnlyAliasUseSite(astnav.GetTokenAtPosition(sourceFile, position))) } - useRequire := getShouldUseRequire(sourceFile, l.GetProgram()) + useRequire := shouldUseRequire(sourceFile, l.GetProgram()) packageJsonImportFilter := l.createPackageJsonImportFilter(sourceFile) _, fixes := l.getImportFixes(ch, exportInfos, ptrTo(l.converters.PositionToLineAndCharacter(sourceFile, core.TextPos(position))), isValidTypeOnlySite, &useRequire, sourceFile, false /* fromCacheOnly */) return l.getBestFix(fixes, sourceFile, packageJsonImportFilter.allowsImportingSpecifier) @@ -581,26 +581,25 @@ func (l *LanguageService) compareModuleSpecifiers( allowsImportingSpecifier func(specifier string) bool, toPath func(fileName string) tspath.Path, ) int { - if a.kind == ImportFixKindUseNamespace || b.kind == ImportFixKindUseNamespace { - return 0 - } - if comparison := core.CompareBooleans( - b.moduleSpecifierKind != modulespecifiers.ResultKindNodeModules || allowsImportingSpecifier(b.moduleSpecifier), - a.moduleSpecifierKind != modulespecifiers.ResultKindNodeModules || allowsImportingSpecifier(a.moduleSpecifier), - ); comparison != 0 { - return comparison - } - if comparison := compareModuleSpecifierRelativity(a, b, l.UserPreferences()); comparison != 0 { - return comparison - } - if comparison := compareNodeCoreModuleSpecifiers(a.moduleSpecifier, b.moduleSpecifier, importingFile, l.GetProgram()); comparison != 0 { - return comparison - } - if comparison := core.CompareBooleans(isFixPossiblyReExportingImportingFile(a, importingFile.Path(), toPath), isFixPossiblyReExportingImportingFile(b, importingFile.Path(), toPath)); comparison != 0 { - return comparison - } - if comparison := tspath.CompareNumberOfDirectorySeparators(a.moduleSpecifier, b.moduleSpecifier); comparison != 0 { - return comparison + if a.kind != ImportFixKindUseNamespace && b.kind != ImportFixKindUseNamespace { + if comparison := core.CompareBooleans( + b.moduleSpecifierKind != modulespecifiers.ResultKindNodeModules || allowsImportingSpecifier(b.moduleSpecifier), + a.moduleSpecifierKind != modulespecifiers.ResultKindNodeModules || allowsImportingSpecifier(a.moduleSpecifier), + ); comparison != 0 { + return comparison + } + if comparison := compareModuleSpecifierRelativity(a, b, l.UserPreferences()); comparison != 0 { + return comparison + } + if comparison := compareNodeCoreModuleSpecifiers(a.moduleSpecifier, b.moduleSpecifier, importingFile, l.GetProgram()); comparison != 0 { + return comparison + } + if comparison := core.CompareBooleans(isFixPossiblyReExportingImportingFile(a, importingFile.Path(), toPath), isFixPossiblyReExportingImportingFile(b, importingFile.Path(), toPath)); comparison != 0 { + return comparison + } + if comparison := tspath.CompareNumberOfDirectorySeparators(a.moduleSpecifier, b.moduleSpecifier); comparison != 0 { + return comparison + } } return 0 } @@ -911,29 +910,9 @@ func tryUseExistingNamespaceImport(existingImports []*FixAddToExistingImportInfo if existingImport.importKind != ImportKindNamed { continue } - var namespacePrefix string - declaration := existingImport.declaration - switch declaration.Kind { - case ast.KindVariableDeclaration, ast.KindImportEqualsDeclaration: - name := declaration.Name() - if declaration.Kind == ast.KindVariableDeclaration && (name == nil || name.Kind != ast.KindIdentifier) { - continue - } - namespacePrefix = name.Text() - case ast.KindJSDocImportTag, ast.KindImportDeclaration: - importClause := ast.GetImportClauseOfDeclaration(declaration) - if importClause == nil || importClause.NamedBindings == nil || importClause.NamedBindings.Kind != ast.KindNamespaceImport { - continue - } - namespacePrefix = importClause.NamedBindings.Name().Text() - default: - debug.AssertNever(declaration) - } - if namespacePrefix == "" { - continue - } - moduleSpecifier := checker.TryGetModuleSpecifierFromDeclaration(declaration) - if moduleSpecifier != nil && moduleSpecifier.Text() != "" { + namespacePrefix := getNamespaceLikeImportText(existingImport.declaration) + moduleSpecifier := checker.TryGetModuleSpecifierFromDeclaration(existingImport.declaration) + if namespacePrefix != "" && moduleSpecifier != nil && moduleSpecifier.Text() != "" { return getUseNamespaceImport( moduleSpecifier.Text(), modulespecifiers.ResultKindNone, @@ -945,6 +924,28 @@ func tryUseExistingNamespaceImport(existingImports []*FixAddToExistingImportInfo return nil } +func getNamespaceLikeImportText(declaration *ast.Statement) string { + switch declaration.Kind { + case ast.KindVariableDeclaration: + name := declaration.Name() + if name != nil && name.Kind == ast.KindIdentifier { + return name.Text() + } + return "" + case ast.KindImportEqualsDeclaration: + return declaration.Name().Text() + case ast.KindJSDocImportTag, ast.KindImportDeclaration: + importClause := declaration.ImportClause() + if importClause != nil && importClause.AsImportClause().NamedBindings != nil && importClause.AsImportClause().NamedBindings.Kind == ast.KindNamespaceImport { + return importClause.AsImportClause().NamedBindings.Name().Text() + } + return "" + default: + debug.AssertNever(declaration) + return "" + } +} + func tryAddToExistingImport(existingImports []*FixAddToExistingImportInfo, isValidTypeOnlyUseSite *bool, ch *checker.Checker, compilerOptions *core.CompilerOptions) *ImportFix { var best *ImportFix @@ -990,11 +991,11 @@ func (info *FixAddToExistingImportInfo) getAddToExistingImportFix(isValidTypeOnl return nil } - importClause := ast.GetImportClauseOfDeclaration(info.declaration) + importClause := info.declaration.ImportClause() if importClause == nil || !ast.IsStringLiteralLike(info.declaration.ModuleSpecifier()) { return nil } - namedBindings := importClause.NamedBindings + namedBindings := importClause.AsImportClause().NamedBindings // A type-only import may not have both a default and named imports, so the only way a name can // be added to an existing type-only import is adding a named import to existing named bindings. if importClause.IsTypeOnly() && !(info.importKind == ImportKindNamed && namedBindings != nil) { @@ -1161,7 +1162,7 @@ func getAddAsTypeOnly( return AddAsTypeOnlyAllowed } -func getShouldUseRequire( +func shouldUseRequire( sourceFile *ast.SourceFile, // !!! | FutureSourceFile program *compiler.Program, ) bool { @@ -1460,19 +1461,33 @@ func (l *LanguageService) codeActionForFixWorker( switch fix.kind { case ImportFixKindUseNamespace: addNamespaceQualifier(changeTracker, sourceFile, fix.qualification()) - return diagnostics.FormatMessage(diagnostics.Change_0_to_1, symbolName, `${fix.namespacePrefix}.${symbolName}`) + return diagnostics.FormatMessage(diagnostics.Change_0_to_1, symbolName, fmt.Sprintf("%s.%s", *fix.namespacePrefix, symbolName)) case ImportFixKindJsdocTypeImport: - // !!! not implemented - // changeTracker.addImportType(changeTracker, sourceFile, fix, quotePreference); - // return diagnostics.FormatMessage(diagnostics.Change_0_to_1, symbolName, getImportTypePrefix(fix.moduleSpecifier, quotePreference) + symbolName); + if fix.usagePosition == nil { + return nil + } + quotePreference := getQuotePreference(sourceFile, l.UserPreferences()) + quoteChar := "\"" + if quotePreference == quotePreferenceSingle { + quoteChar = "'" + } + importTypePrefix := fmt.Sprintf("import(%s%s%s).", quoteChar, fix.moduleSpecifier, quoteChar) + changeTracker.InsertText(sourceFile, *fix.usagePosition, importTypePrefix) + return diagnostics.FormatMessage(diagnostics.Change_0_to_1, symbolName, importTypePrefix+symbolName) case ImportFixKindAddToExisting: + var defaultImport *Import + var namedImports []*Import + if fix.importKind == ImportKindDefault { + defaultImport = &Import{name: symbolName, addAsTypeOnly: fix.addAsTypeOnly} + } else if fix.importKind == ImportKindNamed { + namedImports = []*Import{{name: symbolName, addAsTypeOnly: fix.addAsTypeOnly, propertyName: fix.propertyName}} + } l.doAddExistingFix( changeTracker, sourceFile, fix.importClauseOrBindingPattern, - core.IfElse(fix.importKind == ImportKindDefault, &Import{name: symbolName, addAsTypeOnly: fix.addAsTypeOnly}, nil), - core.IfElse(fix.importKind == ImportKindNamed, []*Import{{name: symbolName, addAsTypeOnly: fix.addAsTypeOnly}}, nil), - // nil /*removeExistingImportSpecifiers*/, + defaultImport, + namedImports, ) moduleSpecifierWithoutQuotes := stringutil.StripQuotes(fix.moduleSpecifier) if includeSymbolNameInDescription { @@ -1481,9 +1496,14 @@ func (l *LanguageService) codeActionForFixWorker( return diagnostics.FormatMessage(diagnostics.Update_import_from_0, moduleSpecifierWithoutQuotes) case ImportFixKindAddNew: var declarations []*ast.Statement - defaultImport := core.IfElse(fix.importKind == ImportKindDefault, &Import{name: symbolName, addAsTypeOnly: fix.addAsTypeOnly}, nil) - namedImports := core.IfElse(fix.importKind == ImportKindNamed, []*Import{{name: symbolName, addAsTypeOnly: fix.addAsTypeOnly}}, nil) + var defaultImport *Import + var namedImports []*Import var namespaceLikeImport *Import + if fix.importKind == ImportKindDefault { + defaultImport = &Import{name: symbolName, addAsTypeOnly: fix.addAsTypeOnly} + } else if fix.importKind == ImportKindNamed { + namedImports = []*Import{{name: symbolName, addAsTypeOnly: fix.addAsTypeOnly, propertyName: fix.propertyName}} + } qualification := fix.qualification() if fix.importKind == ImportKindNamespace || fix.importKind == ImportKindCommonJS { namespaceLikeImport = &Import{kind: fix.importKind, addAsTypeOnly: fix.addAsTypeOnly, name: symbolName} @@ -1512,16 +1532,16 @@ func (l *LanguageService) codeActionForFixWorker( } return diagnostics.FormatMessage(diagnostics.Add_import_from_0, fix.moduleSpecifier) case ImportFixKindPromoteTypeOnly: - // !!! type only - // promotedDeclaration := promoteFromTypeOnly(changes, fix.typeOnlyAliasDeclaration, program, sourceFile, preferences); - // if promotedDeclaration.Kind == ast.KindImportSpecifier { - // return diagnostics.FormatMessage(diagnostics.Remove_type_from_import_of_0_from_1, symbolName, getModuleSpecifierText(promotedDeclaration.parent.parent)) - // } - // return diagnostics.FormatMessage(diagnostics.Remove_type_from_import_declaration_from_0, getModuleSpecifierText(promotedDeclaration)); + promotedDeclaration := promoteFromTypeOnly(changeTracker, fix.typeOnlyAliasDeclaration, l.GetProgram(), sourceFile, l) + if promotedDeclaration.Kind == ast.KindImportSpecifier { + moduleSpec := getModuleSpecifierText(promotedDeclaration.Parent.Parent) + return diagnostics.FormatMessage(diagnostics.Remove_type_from_import_of_0_from_1, symbolName, moduleSpec) + } + moduleSpec := getModuleSpecifierText(promotedDeclaration) + return diagnostics.FormatMessage(diagnostics.Remove_type_from_import_declaration_from_0, moduleSpec) default: panic(fmt.Sprintf(`Unexpected fix kind %v`, fix.kind)) } - return nil } func getNewRequires( @@ -1608,7 +1628,7 @@ func createConstEqualsRequireDeclaration(changeTracker *change.Tracker, name *as ) } -func getModuleSpecifierText(promotedDeclaration *ast.ImportDeclaration) string { +func getModuleSpecifierText(promotedDeclaration *ast.Node) string { if promotedDeclaration.Kind == ast.KindImportEqualsDeclaration { importEqualsDeclaration := promotedDeclaration.AsImportEqualsDeclaration() if ast.IsExternalModuleReference(importEqualsDeclaration.ModuleReference) { diff --git a/internal/ls/autoimportsexportinfo.go b/internal/ls/autoimportsexportinfo.go index 9b1b66703c..1d3c0ee771 100644 --- a/internal/ls/autoimportsexportinfo.go +++ b/internal/ls/autoimportsexportinfo.go @@ -11,7 +11,7 @@ import ( "github.com/microsoft/typescript-go/internal/stringutil" ) -func (l *LanguageService) getExportInfos( +func (l *LanguageService) getExportInfoMap( ctx context.Context, ch *checker.Checker, importingFile *ast.SourceFile, diff --git a/internal/ls/change/delete.go b/internal/ls/change/delete.go new file mode 100644 index 0000000000..a001cdbcdc --- /dev/null +++ b/internal/ls/change/delete.go @@ -0,0 +1,270 @@ +package change + +import ( + "slices" + + "github.com/microsoft/typescript-go/internal/ast" + "github.com/microsoft/typescript-go/internal/astnav" + "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/debug" + "github.com/microsoft/typescript-go/internal/format" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/scanner" + "github.com/microsoft/typescript-go/internal/stringutil" +) + +// deleteDeclaration deletes a node with smart handling for different node types. +// This handles special cases like import specifiers in lists, parameters, etc. +func deleteDeclaration(t *Tracker, deletedNodesInLists map[*ast.Node]bool, sourceFile *ast.SourceFile, node *ast.Node) { + switch node.Kind { + case ast.KindParameter: + oldFunction := node.Parent + if oldFunction.Kind == ast.KindArrowFunction && + len(oldFunction.AsArrowFunction().Parameters.Nodes) == 1 && + astnav.FindChildOfKind(oldFunction, ast.KindOpenParenToken, sourceFile) == nil { + // Lambdas with exactly one parameter are special because, after removal, there + // must be an empty parameter list (i.e. `()`) and this won't necessarily be the + // case if the parameter is simply removed (e.g. in `x => 1`). + t.ReplaceRangeWithText(sourceFile, t.getAdjustedRange(sourceFile, node, node, LeadingTriviaOptionIncludeAll, TrailingTriviaOptionInclude), "()") + } else { + deleteNodeInList(t, deletedNodesInLists, sourceFile, node) + } + + case ast.KindImportDeclaration, ast.KindImportEqualsDeclaration: + imports := sourceFile.Imports() + isFirstImport := len(imports) > 0 && node == imports[0].Parent || + node == core.Find(sourceFile.Statements.Nodes, func(s *ast.Node) bool { return ast.IsAnyImportSyntax(s) }) + // For first import, leave header comment in place, otherwise only delete JSDoc comments + leadingTrivia := LeadingTriviaOptionStartLine + if isFirstImport { + leadingTrivia = LeadingTriviaOptionExclude + } else if hasJSDocNodes(node) { + leadingTrivia = LeadingTriviaOptionJSDoc + } + deleteNode(t, sourceFile, node, leadingTrivia, TrailingTriviaOptionInclude) + + case ast.KindBindingElement: + pattern := node.Parent + preserveComma := pattern.Kind == ast.KindArrayBindingPattern && + node != pattern.AsBindingPattern().Elements.Nodes[len(pattern.AsBindingPattern().Elements.Nodes)-1] + if preserveComma { + deleteNode(t, sourceFile, node, LeadingTriviaOptionIncludeAll, TrailingTriviaOptionExclude) + } else { + deleteNodeInList(t, deletedNodesInLists, sourceFile, node) + } + + case ast.KindVariableDeclaration: + deleteVariableDeclaration(t, deletedNodesInLists, sourceFile, node) + + case ast.KindTypeParameter: + deleteNodeInList(t, deletedNodesInLists, sourceFile, node) + + case ast.KindImportSpecifier: + namedImports := node.Parent + if len(namedImports.AsNamedImports().Elements.Nodes) == 1 { + deleteImportBinding(t, sourceFile, namedImports) + } else { + deleteNodeInList(t, deletedNodesInLists, sourceFile, node) + } + + case ast.KindNamespaceImport: + deleteImportBinding(t, sourceFile, node) + + case ast.KindSemicolonToken: + deleteNode(t, sourceFile, node, LeadingTriviaOptionIncludeAll, TrailingTriviaOptionExclude) + + case ast.KindTypeKeyword: + // For type keyword in import clauses, we need to delete the keyword and any trailing space + // The trailing space is part of the next token's leading trivia, so we include it + deleteNode(t, sourceFile, node, LeadingTriviaOptionExclude, TrailingTriviaOptionInclude) + + case ast.KindFunctionKeyword: + deleteNode(t, sourceFile, node, LeadingTriviaOptionExclude, TrailingTriviaOptionInclude) + + case ast.KindClassDeclaration, ast.KindFunctionDeclaration: + leadingTrivia := LeadingTriviaOptionStartLine + if hasJSDocNodes(node) { + leadingTrivia = LeadingTriviaOptionJSDoc + } + deleteNode(t, sourceFile, node, leadingTrivia, TrailingTriviaOptionInclude) + + default: + if node.Parent == nil { + // a misbehaving client can reach here with the SourceFile node + deleteNode(t, sourceFile, node, LeadingTriviaOptionIncludeAll, TrailingTriviaOptionInclude) + } else if node.Parent.Kind == ast.KindImportClause && node.Parent.AsImportClause().Name() == node { + deleteDefaultImport(t, sourceFile, node.Parent) + } else if node.Parent.Kind == ast.KindCallExpression && slices.Contains(node.Parent.AsCallExpression().Arguments.Nodes, node) { + deleteNodeInList(t, deletedNodesInLists, sourceFile, node) + } else { + deleteNode(t, sourceFile, node, LeadingTriviaOptionIncludeAll, TrailingTriviaOptionInclude) + } + } +} + +func deleteDefaultImport(t *Tracker, sourceFile *ast.SourceFile, importClause *ast.Node) { + clause := importClause.AsImportClause() + if clause.NamedBindings == nil { + // Delete the whole import + deleteNode(t, sourceFile, importClause.Parent, LeadingTriviaOptionIncludeAll, TrailingTriviaOptionInclude) + } else { + // import |d,| * as ns from './file' + name := clause.Name() + start := astnav.GetStartOfNode(name, sourceFile, false) + nextToken := astnav.GetTokenAtPosition(sourceFile, name.End()) + if nextToken != nil && nextToken.Kind == ast.KindCommaToken { + // shift first non-whitespace position after comma to the start position of the node + end := scanner.SkipTriviaEx(sourceFile.Text(), nextToken.End(), &scanner.SkipTriviaOptions{StopAfterLineBreak: false, StopAtComments: true}) + startPos := t.converters.PositionToLineAndCharacter(sourceFile, core.TextPos(start)) + endPos := t.converters.PositionToLineAndCharacter(sourceFile, core.TextPos(end)) + t.ReplaceRangeWithText(sourceFile, lsproto.Range{Start: startPos, End: endPos}, "") + } else { + deleteNode(t, sourceFile, name, LeadingTriviaOptionIncludeAll, TrailingTriviaOptionInclude) + } + } +} + +func deleteImportBinding(t *Tracker, sourceFile *ast.SourceFile, node *ast.Node) { + importClause := node.Parent.AsImportClause() + if importClause.Name() != nil { + // Delete named imports while preserving the default import + // import d|, * as ns| from './file' + // import d|, { a }| from './file' + previousToken := astnav.GetTokenAtPosition(sourceFile, node.Pos()-1) + debug.Assert(previousToken != nil, "previousToken should not be nil") + startPos := t.converters.PositionToLineAndCharacter(sourceFile, core.TextPos(astnav.GetStartOfNode(previousToken, sourceFile, false))) + endPos := t.converters.PositionToLineAndCharacter(sourceFile, core.TextPos(node.End())) + t.ReplaceRangeWithText(sourceFile, lsproto.Range{Start: startPos, End: endPos}, "") + } else { + // Delete the entire import declaration + // |import * as ns from './file'| + // |import { a } from './file'| + importDecl := ast.FindAncestorKind(node, ast.KindImportDeclaration) + debug.Assert(importDecl != nil, "importDecl should not be nil") + deleteNode(t, sourceFile, importDecl, LeadingTriviaOptionIncludeAll, TrailingTriviaOptionInclude) + } +} + +func deleteVariableDeclaration(t *Tracker, deletedNodesInLists map[*ast.Node]bool, sourceFile *ast.SourceFile, node *ast.Node) { + parent := node.Parent + + if parent.Kind == ast.KindCatchClause { + // TODO: There's currently no unused diagnostic for this, could be a suggestion + openParen := astnav.FindChildOfKind(parent, ast.KindOpenParenToken, sourceFile) + closeParen := astnav.FindChildOfKind(parent, ast.KindCloseParenToken, sourceFile) + debug.Assert(openParen != nil && closeParen != nil, "catch clause should have parens") + t.DeleteNodeRange(sourceFile, openParen, closeParen, LeadingTriviaOptionIncludeAll, TrailingTriviaOptionInclude) + return + } + + if len(parent.AsVariableDeclarationList().Declarations.Nodes) != 1 { + deleteNodeInList(t, deletedNodesInLists, sourceFile, node) + return + } + + gp := parent.Parent + switch gp.Kind { + case ast.KindForOfStatement, ast.KindForInStatement: + t.ReplaceNode(sourceFile, node, t.NodeFactory.NewObjectLiteralExpression(t.NodeFactory.NewNodeList([]*ast.Node{}), false), nil) + + case ast.KindForStatement: + deleteNode(t, sourceFile, parent, LeadingTriviaOptionIncludeAll, TrailingTriviaOptionInclude) + + case ast.KindVariableStatement: + leadingTrivia := LeadingTriviaOptionStartLine + if hasJSDocNodes(gp) { + leadingTrivia = LeadingTriviaOptionJSDoc + } + deleteNode(t, sourceFile, gp, leadingTrivia, TrailingTriviaOptionInclude) + + default: + debug.Fail("Unexpected grandparent kind: " + gp.Kind.String()) + } +} + +// deleteNode deletes a node with the specified trivia options. +// Warning: This deletes comments too. +func deleteNode(t *Tracker, sourceFile *ast.SourceFile, node *ast.Node, leadingTrivia LeadingTriviaOption, trailingTrivia TrailingTriviaOption) { + startPosition := t.getAdjustedStartPosition(sourceFile, node, leadingTrivia, false) + endPosition := t.getAdjustedEndPosition(sourceFile, node, trailingTrivia) + startPos := t.converters.PositionToLineAndCharacter(sourceFile, core.TextPos(startPosition)) + endPos := t.converters.PositionToLineAndCharacter(sourceFile, core.TextPos(endPosition)) + t.ReplaceRangeWithText(sourceFile, lsproto.Range{Start: startPos, End: endPos}, "") +} + +func deleteNodeInList(t *Tracker, deletedNodesInLists map[*ast.Node]bool, sourceFile *ast.SourceFile, node *ast.Node) { + containingList := format.GetContainingList(node, sourceFile) + debug.Assert(containingList != nil, "containingList should not be nil") + index := slices.Index(containingList.Nodes, node) + debug.Assert(index != -1, "node should be in containing list") + + if len(containingList.Nodes) == 1 { + deleteNode(t, sourceFile, node, LeadingTriviaOptionIncludeAll, TrailingTriviaOptionInclude) + return + } + + // Note: We will only delete a comma *after* a node. This will leave a trailing comma if we delete the last node. + // That's handled in the end by finishTrailingCommaAfterDeletingNodesInList. + debug.Assert(!deletedNodesInLists[node], "Deleting a node twice") + deletedNodesInLists[node] = true + + startPos := t.startPositionToDeleteNodeInList(sourceFile, node) + var endPos int + if index == len(containingList.Nodes)-1 { + endPos = t.getAdjustedEndPosition(sourceFile, node, TrailingTriviaOptionInclude) + } else { + prevNode := (*ast.Node)(nil) + if index > 0 { + prevNode = containingList.Nodes[index-1] + } + endPos = t.endPositionToDeleteNodeInList(sourceFile, node, prevNode, containingList.Nodes[index+1]) + } + + startLSPos := t.converters.PositionToLineAndCharacter(sourceFile, core.TextPos(startPos)) + endLSPos := t.converters.PositionToLineAndCharacter(sourceFile, core.TextPos(endPos)) + t.ReplaceRangeWithText(sourceFile, lsproto.Range{Start: startLSPos, End: endLSPos}, "") +} + +// startPositionToDeleteNodeInList finds the first non-whitespace position in the leading trivia of the node +func (t *Tracker) startPositionToDeleteNodeInList(sourceFile *ast.SourceFile, node *ast.Node) int { + start := t.getAdjustedStartPosition(sourceFile, node, LeadingTriviaOptionIncludeAll, false) + return scanner.SkipTriviaEx(sourceFile.Text(), start, &scanner.SkipTriviaOptions{StopAfterLineBreak: false, StopAtComments: true}) +} + +func (t *Tracker) endPositionToDeleteNodeInList(sourceFile *ast.SourceFile, node *ast.Node, prevNode *ast.Node, nextNode *ast.Node) int { + end := t.startPositionToDeleteNodeInList(sourceFile, nextNode) + if prevNode == nil || positionsAreOnSameLine(t.getAdjustedEndPosition(sourceFile, node, TrailingTriviaOptionInclude), end, sourceFile) { + return end + } + token := astnav.FindPrecedingToken(sourceFile, astnav.GetStartOfNode(nextNode, sourceFile, false)) + if isSeparator(node, token) { + prevToken := astnav.FindPrecedingToken(sourceFile, astnav.GetStartOfNode(node, sourceFile, false)) + if isSeparator(prevNode, prevToken) { + pos := scanner.SkipTriviaEx(sourceFile.Text(), token.End(), &scanner.SkipTriviaOptions{StopAfterLineBreak: true, StopAtComments: true}) + if positionsAreOnSameLine(astnav.GetStartOfNode(prevToken, sourceFile, false), astnav.GetStartOfNode(token, sourceFile, false), sourceFile) { + if pos > 0 && stringutil.IsLineBreak(rune(sourceFile.Text()[pos-1])) { + return pos - 1 + } + return pos + } + if stringutil.IsLineBreak(rune(sourceFile.Text()[pos])) { + return pos + } + } + } + return end +} + +func positionsAreOnSameLine(pos1, pos2 int, sourceFile *ast.SourceFile) bool { + return format.GetLineStartPositionForPosition(pos1, sourceFile) == format.GetLineStartPositionForPosition(pos2, sourceFile) +} + +// hasJSDocNodes checks if a node has JSDoc comments +func hasJSDocNodes(node *ast.Node) bool { + if node == nil { + return false + } + // nil is ok for JSDoc - it will return empty slice if not available + jsdocs := node.JSDoc(nil) + return len(jsdocs) > 0 +} diff --git a/internal/ls/change/tracker.go b/internal/ls/change/tracker.go index 5d88f40a7b..28c5c65205 100644 --- a/internal/ls/change/tracker.go +++ b/internal/ls/change/tracker.go @@ -29,28 +29,28 @@ type NodeOptions struct { // Text of inserted node will be formatted with this delta, otherwise delta will be inferred from the new node kind delta *int - leadingTriviaOption - trailingTriviaOption + LeadingTriviaOption + TrailingTriviaOption joiner string } -type leadingTriviaOption int +type LeadingTriviaOption int const ( - leadingTriviaOptionNone leadingTriviaOption = 0 - leadingTriviaOptionExclude leadingTriviaOption = 1 - leadingTriviaOptionIncludeAll leadingTriviaOption = 2 - leadingTriviaOptionJSDoc leadingTriviaOption = 3 - leadingTriviaOptionStartLine leadingTriviaOption = 4 + LeadingTriviaOptionNone LeadingTriviaOption = 0 + LeadingTriviaOptionExclude LeadingTriviaOption = 1 + LeadingTriviaOptionIncludeAll LeadingTriviaOption = 2 + LeadingTriviaOptionJSDoc LeadingTriviaOption = 3 + LeadingTriviaOptionStartLine LeadingTriviaOption = 4 ) -type trailingTriviaOption int +type TrailingTriviaOption int const ( - trailingTriviaOptionNone trailingTriviaOption = 0 - trailingTriviaOptionExclude trailingTriviaOption = 1 - trailingTriviaOptionExcludeWhitespace trailingTriviaOption = 2 - trailingTriviaOptionInclude trailingTriviaOption = 3 + TrailingTriviaOptionNone TrailingTriviaOption = 0 + TrailingTriviaOptionExclude TrailingTriviaOption = 1 + TrailingTriviaOptionExcludeWhitespace TrailingTriviaOption = 2 + TrailingTriviaOptionInclude TrailingTriviaOption = 3 ) type trackerEditKind int @@ -82,13 +82,19 @@ type Tracker struct { *printer.EmitContext *ast.NodeFactory - changes *collections.MultiMap[*ast.SourceFile, *trackerEdit] + changes *collections.MultiMap[*ast.SourceFile, *trackerEdit] + deletedNodes []deletedNode // created during call to getChanges writer *printer.ChangeTrackerWriter // printer } +type deletedNode struct { + sourceFile *ast.SourceFile + node *ast.Node +} + func NewTracker(ctx context.Context, compilerOptions *core.CompilerOptions, formatOptions *format.FormatCodeSettings, converters *lsconv.Converters) *Tracker { emitContext := printer.NewEmitContext() newLine := compilerOptions.NewLine.GetNewLineCharacter() @@ -104,10 +110,10 @@ func NewTracker(ctx context.Context, compilerOptions *core.CompilerOptions, form } } -// !!! address strada note -// - Note: after calling this, the TextChanges object must be discarded! +// GetChanges returns the accumulated text edits. +// Note: after calling this, the Tracker object must be discarded! func (t *Tracker) GetChanges() map[string][]*lsproto.TextEdit { - // !!! finishDeleteDeclarations + t.finishDeleteDeclarations() // !!! finishClassesWithNodesInsertedAtStart changes := t.getTextChangesFromChanges() // !!! changes for new files @@ -118,11 +124,11 @@ func (t *Tracker) ReplaceNode(sourceFile *ast.SourceFile, oldNode *ast.Node, new if options == nil { // defaults to `useNonAdjustedPositions` options = &NodeOptions{ - leadingTriviaOption: leadingTriviaOptionExclude, - trailingTriviaOption: trailingTriviaOptionExclude, + LeadingTriviaOption: LeadingTriviaOptionExclude, + TrailingTriviaOption: TrailingTriviaOptionExclude, } } - t.ReplaceRange(sourceFile, t.getAdjustedRange(sourceFile, oldNode, oldNode, options.leadingTriviaOption, options.trailingTriviaOption), newNode, *options) + t.ReplaceRange(sourceFile, t.getAdjustedRange(sourceFile, oldNode, oldNode, options.LeadingTriviaOption, options.TrailingTriviaOption), newNode, *options) } func (t *Tracker) ReplaceRange(sourceFile *ast.SourceFile, lsprotoRange lsproto.Range, newNode *ast.Node, options NodeOptions) { @@ -166,7 +172,87 @@ func (t *Tracker) InsertNodesAfter(sourceFile *ast.SourceFile, after *ast.Node, } func (t *Tracker) InsertNodeBefore(sourceFile *ast.SourceFile, before *ast.Node, newNode *ast.Node, blankLineBetween bool) { - t.InsertNodeAt(sourceFile, core.TextPos(t.getAdjustedStartPosition(sourceFile, before, leadingTriviaOptionNone, false)), newNode, t.getOptionsForInsertNodeBefore(before, newNode, blankLineBetween)) + t.InsertNodeAt(sourceFile, core.TextPos(t.getAdjustedStartPosition(sourceFile, before, LeadingTriviaOptionNone, false)), newNode, t.getOptionsForInsertNodeBefore(before, newNode, blankLineBetween)) +} + +// InsertModifierBefore inserts a modifier token (like 'type') before a node with a trailing space. +func (t *Tracker) InsertModifierBefore(sourceFile *ast.SourceFile, modifier ast.Kind, before *ast.Node) { + pos := astnav.GetStartOfNode(before, sourceFile, false) + token := sourceFile.GetOrCreateToken(modifier, pos, pos, before.Parent) + t.InsertNodeAt(sourceFile, core.TextPos(pos), token, NodeOptions{Suffix: " "}) +} + +// Delete queues a node for deletion with smart handling of list items, imports, etc. +// The actual deletion happens in finishDeleteDeclarations during GetChanges. +func (t *Tracker) Delete(sourceFile *ast.SourceFile, node *ast.Node) { + t.deletedNodes = append(t.deletedNodes, deletedNode{sourceFile: sourceFile, node: node}) +} + +// DeleteRange deletes a text range from the source file. +func (t *Tracker) DeleteRange(sourceFile *ast.SourceFile, textRange core.TextRange) { + lspRange := t.converters.ToLSPRange(sourceFile, textRange) + t.ReplaceRangeWithText(sourceFile, lspRange, "") +} + +// DeleteNode deletes a node immediately with specified trivia options. +// Stop! Consider using Delete instead, which has logic for deleting nodes from delimited lists. +func (t *Tracker) DeleteNode(sourceFile *ast.SourceFile, node *ast.Node, leadingTrivia LeadingTriviaOption, trailingTrivia TrailingTriviaOption) { + rng := t.getAdjustedRange(sourceFile, node, node, leadingTrivia, trailingTrivia) + t.ReplaceRangeWithText(sourceFile, rng, "") +} + +// DeleteNodeRange deletes a range of nodes with specified trivia options. +func (t *Tracker) DeleteNodeRange(sourceFile *ast.SourceFile, startNode *ast.Node, endNode *ast.Node, leadingTrivia LeadingTriviaOption, trailingTrivia TrailingTriviaOption) { + startPosition := t.getAdjustedStartPosition(sourceFile, startNode, leadingTrivia, false) + endPosition := t.getAdjustedEndPosition(sourceFile, endNode, trailingTrivia) + startPos := t.converters.PositionToLineAndCharacter(sourceFile, core.TextPos(startPosition)) + endPos := t.converters.PositionToLineAndCharacter(sourceFile, core.TextPos(endPosition)) + t.ReplaceRangeWithText(sourceFile, lsproto.Range{Start: startPos, End: endPos}, "") +} + +// finishDeleteDeclarations processes all queued deletions with smart handling for lists and trailing commas. +func (t *Tracker) finishDeleteDeclarations() { + deletedNodesInLists := make(map[*ast.Node]bool) + + for _, deleted := range t.deletedNodes { + // Skip if this node is contained within another deleted node + isContained := false + for _, other := range t.deletedNodes { + if other.sourceFile == deleted.sourceFile && other.node != deleted.node && + rangeContainsRangeExclusive(other.node, deleted.node) { + isContained = true + break + } + } + if isContained { + continue + } + + deleteDeclaration(t, deletedNodesInLists, deleted.sourceFile, deleted.node) + } + + // Handle trailing commas for last elements in lists + for node := range deletedNodesInLists { + sourceFile := ast.GetSourceFileOfNode(node) + list := format.GetContainingList(node, sourceFile) + if list == nil || node != list.Nodes[len(list.Nodes)-1] { + continue + } + + lastNonDeletedIndex := -1 + for i := len(list.Nodes) - 2; i >= 0; i-- { + if !deletedNodesInLists[list.Nodes[i]] { + lastNonDeletedIndex = i + break + } + } + + if lastNonDeletedIndex != -1 { + startPos := t.converters.PositionToLineAndCharacter(sourceFile, core.TextPos(list.Nodes[lastNonDeletedIndex].End())) + endPos := t.converters.PositionToLineAndCharacter(sourceFile, core.TextPos(t.startPositionToDeleteNodeInList(sourceFile, list.Nodes[lastNonDeletedIndex+1]))) + t.ReplaceRangeWithText(sourceFile, lsproto.Range{Start: startPos, End: endPos}, "") + } + } } func (t *Tracker) endPosForInsertNodeAfter(sourceFile *ast.SourceFile, after *ast.Node, newNode *ast.Node) core.TextPos { @@ -180,7 +266,7 @@ func (t *Tracker) endPosForInsertNodeAfter(sourceFile *ast.SourceFile, after *as NodeOptions{}, ) } - return core.TextPos(t.getAdjustedEndPosition(sourceFile, after, trailingTriviaOptionNone)) + return core.TextPos(t.getAdjustedEndPosition(sourceFile, after, TrailingTriviaOptionNone)) } /** @@ -287,7 +373,11 @@ func (t *Tracker) InsertImportSpecifierAtIndex(sourceFile *ast.SourceFile, newSp namedImportsNode := namedImports.AsNamedImports() elements := namedImportsNode.Elements.Nodes - if index > 0 && len(elements) > index { + if index >= len(elements) { + // Insert at the end (after the last element) + t.InsertNodeInListAfter(sourceFile, elements[len(elements)-1], newSpecifier, elements) + } else if index > 0 { + // Insert after the element at index-1 t.InsertNodeInListAfter(sourceFile, elements[index-1], newSpecifier, elements) } else { // Insert before the first element @@ -387,6 +477,10 @@ func ptrTo[T any](v T) *T { return &v } +func rangeContainsRangeExclusive(outer *ast.Node, inner *ast.Node) bool { + return outer.Pos() < inner.Pos() && inner.End() < outer.End() +} + func isSeparator(node *ast.Node, candidate *ast.Node) bool { return candidate != nil && node.Parent != nil && (candidate.Kind == ast.KindCommaToken || (candidate.Kind == ast.KindSemicolonToken && node.Parent.Kind == ast.KindObjectLiteralExpression)) } diff --git a/internal/ls/change/trackerimpl.go b/internal/ls/change/trackerimpl.go index 92b1babd2e..d39c5422c8 100644 --- a/internal/ls/change/trackerimpl.go +++ b/internal/ls/change/trackerimpl.go @@ -172,7 +172,7 @@ func (t *Tracker) getNonformattedText(node *ast.Node, sourceFile *ast.SourceFile } // method on the changeTracker because use of converters -func (t *Tracker) getAdjustedRange(sourceFile *ast.SourceFile, startNode *ast.Node, endNode *ast.Node, leadingOption leadingTriviaOption, trailingOption trailingTriviaOption) lsproto.Range { +func (t *Tracker) getAdjustedRange(sourceFile *ast.SourceFile, startNode *ast.Node, endNode *ast.Node, leadingOption LeadingTriviaOption, trailingOption TrailingTriviaOption) lsproto.Range { return t.converters.ToLSPRange( sourceFile, core.NewTextRange( @@ -183,8 +183,8 @@ func (t *Tracker) getAdjustedRange(sourceFile *ast.SourceFile, startNode *ast.No } // method on the changeTracker because use of converters -func (t *Tracker) getAdjustedStartPosition(sourceFile *ast.SourceFile, node *ast.Node, leadingOption leadingTriviaOption, hasTrailingComment bool) int { - if leadingOption == leadingTriviaOptionJSDoc { +func (t *Tracker) getAdjustedStartPosition(sourceFile *ast.SourceFile, node *ast.Node, leadingOption LeadingTriviaOption, hasTrailingComment bool) int { + if leadingOption == LeadingTriviaOptionJSDoc { if JSDocComments := parser.GetJSDocCommentRanges(t.NodeFactory, nil, node, sourceFile.Text()); len(JSDocComments) > 0 { return format.GetLineStartPositionForPosition(JSDocComments[0].Pos(), sourceFile) } @@ -194,9 +194,9 @@ func (t *Tracker) getAdjustedStartPosition(sourceFile *ast.SourceFile, node *ast startOfLinePos := format.GetLineStartPositionForPosition(start, sourceFile) switch leadingOption { - case leadingTriviaOptionExclude: + case LeadingTriviaOptionExclude: return start - case leadingTriviaOptionStartLine: + case LeadingTriviaOptionStartLine: if node.Loc.ContainsInclusive(startOfLinePos) { return startOfLinePos } @@ -218,7 +218,7 @@ func (t *Tracker) getAdjustedStartPosition(sourceFile *ast.SourceFile, node *ast // fullstart // when b is replaced - we usually want to keep the leading trvia // when b is deleted - we delete it - if leadingOption == leadingTriviaOptionIncludeAll { + if leadingOption == LeadingTriviaOptionIncludeAll { return fullStart } return start @@ -248,8 +248,8 @@ func (t *Tracker) getAdjustedStartPosition(sourceFile *ast.SourceFile, node *ast // method on the changeTracker because of converters // Return the end position of a multiline comment of it is on another line; otherwise returns `undefined`; -func (t *Tracker) getEndPositionOfMultilineTrailingComment(sourceFile *ast.SourceFile, node *ast.Node, trailingOpt trailingTriviaOption) int { - if trailingOpt == trailingTriviaOptionInclude { +func (t *Tracker) getEndPositionOfMultilineTrailingComment(sourceFile *ast.SourceFile, node *ast.Node, trailingOpt TrailingTriviaOption) int { + if trailingOpt == TrailingTriviaOptionInclude { // If the trailing comment is a multiline comment that extends to the next lines, // return the end of the comment and track it for the next nodes to adjust. lineStarts := sourceFile.ECMALineMap() @@ -274,11 +274,11 @@ func (t *Tracker) getEndPositionOfMultilineTrailingComment(sourceFile *ast.Sourc } // method on the changeTracker because of converters -func (t *Tracker) getAdjustedEndPosition(sourceFile *ast.SourceFile, node *ast.Node, trailingTriviaOption trailingTriviaOption) int { - if trailingTriviaOption == trailingTriviaOptionExclude { +func (t *Tracker) getAdjustedEndPosition(sourceFile *ast.SourceFile, node *ast.Node, TrailingTriviaOption TrailingTriviaOption) int { + if TrailingTriviaOption == TrailingTriviaOptionExclude { return node.End() } - if trailingTriviaOption == trailingTriviaOptionExcludeWhitespace { + if TrailingTriviaOption == TrailingTriviaOptionExcludeWhitespace { if comments := slices.AppendSeq( slices.Collect(scanner.GetTrailingCommentRanges(t.NodeFactory, sourceFile.Text(), node.End())), scanner.GetLeadingCommentRanges(t.NodeFactory, sourceFile.Text(), node.End()), @@ -290,13 +290,13 @@ func (t *Tracker) getAdjustedEndPosition(sourceFile *ast.SourceFile, node *ast.N return node.End() } - if multilineEndPosition := t.getEndPositionOfMultilineTrailingComment(sourceFile, node, trailingTriviaOption); multilineEndPosition != 0 { + if multilineEndPosition := t.getEndPositionOfMultilineTrailingComment(sourceFile, node, TrailingTriviaOption); multilineEndPosition != 0 { return multilineEndPosition } newEnd := scanner.SkipTriviaEx(sourceFile.Text(), node.End(), &scanner.SkipTriviaOptions{StopAfterLineBreak: true}) - if newEnd != node.End() && (trailingTriviaOption == trailingTriviaOptionInclude || stringutil.IsLineBreak(rune(sourceFile.Text()[newEnd-1]))) { + if newEnd != node.End() && (TrailingTriviaOption == TrailingTriviaOptionInclude || stringutil.IsLineBreak(rune(sourceFile.Text()[newEnd-1]))) { return newEnd } return node.End() diff --git a/internal/ls/codeactions.go b/internal/ls/codeactions.go new file mode 100644 index 0000000000..4cf6ed27dc --- /dev/null +++ b/internal/ls/codeactions.go @@ -0,0 +1,126 @@ +package ls + +import ( + "context" + "slices" + + "github.com/microsoft/typescript-go/internal/ast" + "github.com/microsoft/typescript-go/internal/checker" + "github.com/microsoft/typescript-go/internal/compiler" + "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" +) + +// CodeFixProvider represents a provider for a specific type of code fix +type CodeFixProvider struct { + ErrorCodes []int32 + GetCodeActions func(ctx context.Context, fixContext *CodeFixContext) []CodeAction + FixIds []string + GetAllCodeActions func(ctx context.Context, fixContext *CodeFixContext) *CombinedCodeActions +} + +// CodeFixContext contains the context needed to generate code fixes +type CodeFixContext struct { + SourceFile *ast.SourceFile + Span core.TextRange + ErrorCode int32 + Program *compiler.Program + Checker *checker.Checker + LS *LanguageService + Diagnostic *lsproto.Diagnostic + Params *lsproto.CodeActionParams +} + +// CodeAction represents a single code action fix +type CodeAction struct { + Description string + Changes []*lsproto.TextEdit +} + +// CombinedCodeActions represents combined code actions for fix-all scenarios +type CombinedCodeActions struct { + Description string + Changes []*lsproto.TextEdit +} + +// codeFixProviders is the list of all registered code fix providers +var codeFixProviders = []*CodeFixProvider{ + ImportFixProvider, + // Add more code fix providers here as they are implemented +} + +// ProvideCodeActions returns code actions for the given range and context +func (l *LanguageService) ProvideCodeActions(ctx context.Context, params *lsproto.CodeActionParams) (lsproto.CodeActionResponse, error) { + program, file := l.getProgramAndFile(params.TextDocument.Uri) + + // Get the type checker + ch, done := program.GetTypeCheckerForFile(ctx, file) + if done != nil { + defer done() + } + + var actions []lsproto.CommandOrCodeAction + + // Process diagnostics in the context to generate quick fixes + if params.Context != nil && params.Context.Diagnostics != nil { + for _, diag := range params.Context.Diagnostics { + if diag.Code.Integer == nil { + continue + } + + errorCode := *diag.Code.Integer + + // Check all code fix providers + for _, provider := range codeFixProviders { + if !containsErrorCode(provider.ErrorCodes, errorCode) { + continue + } + + // Create context for the provider + position := l.converters.LineAndCharacterToPosition(file, diag.Range.Start) + endPosition := l.converters.LineAndCharacterToPosition(file, diag.Range.End) + fixContext := &CodeFixContext{ + SourceFile: file, + Span: core.NewTextRange(int(position), int(endPosition)), + ErrorCode: errorCode, + Program: program, + Checker: ch, + LS: l, + Diagnostic: diag, + Params: params, + } + + // Get code actions from the provider + providerActions := provider.GetCodeActions(ctx, fixContext) + for _, action := range providerActions { + actions = append(actions, convertToLSPCodeAction(&action, diag, params.TextDocument.Uri)) + } + } + } + } + + return lsproto.CommandOrCodeActionArrayOrNull{CommandOrCodeActionArray: &actions}, nil +} + +// containsErrorCode checks if the error code is in the list +func containsErrorCode(codes []int32, code int32) bool { + return slices.Contains(codes, code) +} + +// convertToLSPCodeAction converts an internal CodeAction to an LSP CodeAction +func convertToLSPCodeAction(action *CodeAction, diag *lsproto.Diagnostic, uri lsproto.DocumentUri) lsproto.CommandOrCodeAction { + kind := lsproto.CodeActionKindQuickFix + changes := map[lsproto.DocumentUri][]*lsproto.TextEdit{ + uri: action.Changes, + } + diagnostics := []*lsproto.Diagnostic{diag} + + return lsproto.CommandOrCodeAction{ + CodeAction: &lsproto.CodeAction{ + Title: action.Description, + Kind: &kind, + Edit: &lsproto.WorkspaceEdit{Changes: &changes}, + Diagnostics: &diagnostics, + }, + } +} diff --git a/internal/ls/codeactions_importfixes.go b/internal/ls/codeactions_importfixes.go new file mode 100644 index 0000000000..23449bd365 --- /dev/null +++ b/internal/ls/codeactions_importfixes.go @@ -0,0 +1,640 @@ +package ls + +import ( + "cmp" + "context" + "fmt" + "slices" + "strings" + + "github.com/microsoft/typescript-go/internal/ast" + "github.com/microsoft/typescript-go/internal/astnav" + "github.com/microsoft/typescript-go/internal/checker" + "github.com/microsoft/typescript-go/internal/collections" + "github.com/microsoft/typescript-go/internal/compiler" + "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/diagnostics" + "github.com/microsoft/typescript-go/internal/ls/change" + "github.com/microsoft/typescript-go/internal/ls/lsutil" + "github.com/microsoft/typescript-go/internal/ls/organizeimports" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/outputpaths" + "github.com/microsoft/typescript-go/internal/scanner" + "github.com/microsoft/typescript-go/internal/tspath" +) + +var importFixErrorCodes = []int32{ + diagnostics.Cannot_find_name_0.Code(), + diagnostics.Cannot_find_name_0_Did_you_mean_1.Code(), + diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0.Code(), + diagnostics.Cannot_find_name_0_Did_you_mean_the_static_member_1_0.Code(), + diagnostics.Cannot_find_namespace_0.Code(), + diagnostics.X_0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead.Code(), + diagnostics.X_0_only_refers_to_a_type_but_is_being_used_as_a_value_here.Code(), + diagnostics.No_value_exists_in_scope_for_the_shorthand_property_0_Either_declare_one_or_provide_an_initializer.Code(), + diagnostics.X_0_cannot_be_used_as_a_value_because_it_was_imported_using_import_type.Code(), + diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_save_dev_types_Slashjquery.Code(), + diagnostics.Cannot_find_name_0_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_1_or_later.Code(), + diagnostics.Cannot_find_name_0_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_include_dom.Code(), + diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_a_test_runner_Try_npm_i_save_dev_types_Slashjest_or_npm_i_save_dev_types_Slashmocha_and_then_add_jest_or_mocha_to_the_types_field_in_your_tsconfig.Code(), + diagnostics.Cannot_find_name_0_Did_you_mean_to_write_this_in_an_async_function.Code(), + diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_save_dev_types_Slashjquery_and_then_add_jquery_to_the_types_field_in_your_tsconfig.Code(), + diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_a_test_runner_Try_npm_i_save_dev_types_Slashjest_or_npm_i_save_dev_types_Slashmocha.Code(), + diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_node_Try_npm_i_save_dev_types_Slashnode.Code(), + diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_node_Try_npm_i_save_dev_types_Slashnode_and_then_add_node_to_the_types_field_in_your_tsconfig.Code(), + diagnostics.Cannot_find_namespace_0_Did_you_mean_1.Code(), + diagnostics.Cannot_extend_an_interface_0_Did_you_mean_implements.Code(), + diagnostics.This_JSX_tag_requires_0_to_be_in_scope_but_it_could_not_be_found.Code(), +} + +const ( + importFixID = "fixMissingImport" +) + +// ImportFixProvider is the CodeFixProvider for import-related fixes +var ImportFixProvider = &CodeFixProvider{ + ErrorCodes: importFixErrorCodes, + GetCodeActions: getImportCodeActions, + FixIds: []string{importFixID}, +} + +type fixInfo struct { + fix *ImportFix + symbolName string + errorIdentifierText string + isJsxNamespaceFix bool +} + +func getImportCodeActions(ctx context.Context, fixContext *CodeFixContext) []CodeAction { + info := getFixInfos(ctx, fixContext, fixContext.ErrorCode, fixContext.Span.Pos(), true /* useAutoImportProvider */) + if len(info) == 0 { + return nil + } + + var actions []CodeAction + for _, fixInfo := range info { + tracker := change.NewTracker(ctx, fixContext.Program.Options(), fixContext.LS.FormatOptions(), fixContext.LS.converters) + msg := fixContext.LS.codeActionForFixWorker( + tracker, + fixContext.SourceFile, + fixInfo.symbolName, + fixInfo.fix, + fixInfo.symbolName != fixInfo.errorIdentifierText, + ) + + if msg != nil { + // Convert changes to LSP edits + changes := tracker.GetChanges() + var edits []*lsproto.TextEdit + for _, fileChanges := range changes { + edits = append(edits, fileChanges...) + } + + actions = append(actions, CodeAction{ + Description: msg.Message(), + Changes: edits, + }) + } + } + return actions +} + +func getFixInfos(ctx context.Context, fixContext *CodeFixContext, errorCode int32, pos int, useAutoImportProvider bool) []*fixInfo { + symbolToken := astnav.GetTokenAtPosition(fixContext.SourceFile, pos) + + var info []*fixInfo + + if errorCode == diagnostics.X_0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead.Code() { + info = getFixesInfoForUMDImport(ctx, fixContext, symbolToken) + } else if !ast.IsIdentifier(symbolToken) { + return nil + } else if errorCode == diagnostics.X_0_cannot_be_used_as_a_value_because_it_was_imported_using_import_type.Code() { + // Handle type-only import promotion + ch, done := fixContext.Program.GetTypeChecker(ctx) + defer done() + compilerOptions := fixContext.Program.Options() + symbolNames := getSymbolNamesToImport(fixContext.SourceFile, ch, symbolToken, compilerOptions) + if len(symbolNames) != 1 { + panic("Expected exactly one symbol name for type-only import promotion") + } + symbolName := symbolNames[0] + fix := getTypeOnlyPromotionFix(ctx, fixContext.SourceFile, symbolToken, symbolName, fixContext.Program) + if fix != nil { + return []*fixInfo{{fix: fix, symbolName: symbolName, errorIdentifierText: symbolToken.Text()}} + } + return nil + } else { + info = getFixesInfoForNonUMDImport(ctx, fixContext, symbolToken, useAutoImportProvider) + } + + // Sort fixes by preference + return sortFixInfo(info, fixContext) +} + +func getFixesInfoForUMDImport(ctx context.Context, fixContext *CodeFixContext, token *ast.Node) []*fixInfo { + ch, done := fixContext.Program.GetTypeChecker(ctx) + defer done() + + umdSymbol := getUmdSymbol(token, ch) + if umdSymbol == nil { + return nil + } + + symbol := ch.GetAliasedSymbol(umdSymbol) + symbolName := umdSymbol.Name + exportInfo := []*SymbolExportInfo{{ + symbol: umdSymbol, + moduleSymbol: symbol, + moduleFileName: "", + exportKind: ExportKindUMD, + targetFlags: symbol.Flags, + isFromPackageJson: false, + }} + + useRequire := shouldUseRequire(fixContext.SourceFile, fixContext.Program) + // `usagePosition` is undefined because `token` may not actually be a usage of the symbol we're importing. + // For example, we might need to import `React` in order to use an arbitrary JSX tag. We could send a position + // for other UMD imports, but `usagePosition` is currently only used to insert a namespace qualification + // before a named import, like converting `writeFile` to `fs.writeFile` (whether `fs` is already imported or + // not), and this function will only be called for UMD symbols, which are necessarily an `export =`, not a + // named export. + _, fixes := fixContext.LS.getImportFixes( + ch, + exportInfo, + nil, // usagePosition undefined for UMD + ptrTo(false), + &useRequire, + fixContext.SourceFile, + false, // fromCacheOnly + ) + + var result []*fixInfo + for _, fix := range fixes { + errorIdentifierText := "" + if ast.IsIdentifier(token) { + errorIdentifierText = token.Text() + } + result = append(result, &fixInfo{ + fix: fix, + symbolName: symbolName, + errorIdentifierText: errorIdentifierText, + }) + } + return result +} + +func getUmdSymbol(token *ast.Node, ch *checker.Checker) *ast.Symbol { + // try the identifier to see if it is the umd symbol + var umdSymbol *ast.Symbol + if ast.IsIdentifier(token) { + umdSymbol = ch.GetResolvedSymbol(token) + } + if isUMDExportSymbol(umdSymbol) { + return umdSymbol + } + + // The error wasn't for the symbolAtLocation, it was for the JSX tag itself, which needs access to e.g. `React`. + parent := token.Parent + if (ast.IsJsxOpeningLikeElement(parent) && parent.TagName() == token) || + ast.IsJsxOpeningFragment(parent) { + var location *ast.Node + if ast.IsJsxOpeningLikeElement(parent) { + location = token + } else { + location = parent + } + jsxNamespace := ch.GetJsxNamespace(parent) + parentSymbol := ch.ResolveName(jsxNamespace, location, ast.SymbolFlagsValue, false /* excludeGlobals */) + if isUMDExportSymbol(parentSymbol) { + return parentSymbol + } + } + return nil +} + +func isUMDExportSymbol(symbol *ast.Symbol) bool { + return symbol != nil && len(symbol.Declarations) > 0 && + symbol.Declarations[0] != nil && + ast.IsNamespaceExportDeclaration(symbol.Declarations[0]) +} + +func getFixesInfoForNonUMDImport(ctx context.Context, fixContext *CodeFixContext, symbolToken *ast.Node, useAutoImportProvider bool) []*fixInfo { + ch, done := fixContext.Program.GetTypeChecker(ctx) + defer done() + compilerOptions := fixContext.Program.Options() + + symbolNames := getSymbolNamesToImport(fixContext.SourceFile, ch, symbolToken, compilerOptions) + var allInfo []*fixInfo + + for _, symbolName := range symbolNames { + // "default" is a keyword and not a legal identifier for the import + if symbolName == "default" { + continue + } + + isValidTypeOnlyUseSite := ast.IsValidTypeOnlyAliasUseSite(symbolToken) + useRequire := shouldUseRequire(fixContext.SourceFile, fixContext.Program) + exportInfosMap := getExportInfos( + ctx, + symbolName, + ast.IsJsxTagName(symbolToken), + getMeaningFromLocation(symbolToken), + fixContext.SourceFile, + fixContext.Program, + fixContext.LS, + ) + + // Flatten all export infos from the map into a single slice + var allExportInfos []*SymbolExportInfo + for exportInfoList := range exportInfosMap.Values() { + allExportInfos = append(allExportInfos, exportInfoList...) + } + + // Sort by moduleFileName to ensure deterministic iteration order + // TODO: This might not work 100% of the time; need to revisit this + slices.SortStableFunc(allExportInfos, func(a, b *SymbolExportInfo) int { + return strings.Compare(a.moduleFileName, b.moduleFileName) + }) + + if len(allExportInfos) > 0 { + usagePos := scanner.GetTokenPosOfNode(symbolToken, fixContext.SourceFile, false) + lspPos := fixContext.LS.converters.PositionToLineAndCharacter(fixContext.SourceFile, core.TextPos(usagePos)) + _, fixes := fixContext.LS.getImportFixes( + ch, + allExportInfos, + &lspPos, + &isValidTypeOnlyUseSite, + &useRequire, + fixContext.SourceFile, + false, // fromCacheOnly + ) + + for _, fix := range fixes { + allInfo = append(allInfo, &fixInfo{ + fix: fix, + symbolName: symbolName, + errorIdentifierText: symbolToken.Text(), + isJsxNamespaceFix: symbolName != symbolToken.Text(), + }) + } + } + } + + return allInfo +} + +func getTypeOnlyPromotionFix(ctx context.Context, sourceFile *ast.SourceFile, symbolToken *ast.Node, symbolName string, program *compiler.Program) *ImportFix { + ch, done := program.GetTypeChecker(ctx) + defer done() + + // Get the symbol at the token location + symbol := ch.ResolveName(symbolName, symbolToken, ast.SymbolFlagsValue, true /* excludeGlobals */) + if symbol == nil { + return nil + } + + // Get the type-only alias declaration + typeOnlyAliasDeclaration := ch.GetTypeOnlyAliasDeclaration(symbol) + if typeOnlyAliasDeclaration == nil || ast.GetSourceFileOfNode(typeOnlyAliasDeclaration) != sourceFile { + return nil + } + + return &ImportFix{ + kind: ImportFixKindPromoteTypeOnly, + typeOnlyAliasDeclaration: typeOnlyAliasDeclaration, + } +} + +func getSymbolNamesToImport(sourceFile *ast.SourceFile, ch *checker.Checker, symbolToken *ast.Node, compilerOptions *core.CompilerOptions) []string { + parent := symbolToken.Parent + if (ast.IsJsxOpeningLikeElement(parent) || ast.IsJsxClosingElement(parent)) && + parent.TagName() == symbolToken && + jsxModeNeedsExplicitImport(compilerOptions.Jsx) { + jsxNamespace := ch.GetJsxNamespace(sourceFile.AsNode()) + if needsJsxNamespaceFix(jsxNamespace, symbolToken, ch) { + needsComponentNameFix := !scanner.IsIntrinsicJsxName(symbolToken.Text()) && + ch.ResolveName(symbolToken.Text(), symbolToken, ast.SymbolFlagsValue, false /* excludeGlobals */) == nil + if needsComponentNameFix { + return []string{symbolToken.Text(), jsxNamespace} + } + return []string{jsxNamespace} + } + } + return []string{symbolToken.Text()} +} + +func needsJsxNamespaceFix(jsxNamespace string, symbolToken *ast.Node, ch *checker.Checker) bool { + if scanner.IsIntrinsicJsxName(symbolToken.Text()) { + return true + } + namespaceSymbol := ch.ResolveName(jsxNamespace, symbolToken, ast.SymbolFlagsValue, true /* excludeGlobals */) + if namespaceSymbol == nil { + return true + } + // Check if all declarations are type-only + if slices.ContainsFunc(namespaceSymbol.Declarations, ast.IsTypeOnlyImportOrExportDeclaration) { + return (namespaceSymbol.Flags & ast.SymbolFlagsValue) == 0 + } + return false +} + +func jsxModeNeedsExplicitImport(jsx core.JsxEmit) bool { + return jsx == core.JsxEmitReact || jsx == core.JsxEmitReactNative +} + +func getExportInfos( + ctx context.Context, + symbolName string, + isJsxTagName bool, + currentTokenMeaning ast.SemanticMeaning, + fromFile *ast.SourceFile, + program *compiler.Program, + ls *LanguageService, +) *collections.MultiMap[ast.SymbolId, *SymbolExportInfo] { + // For each original symbol, keep all re-exports of that symbol together + // Maps symbol id to info for modules providing that symbol (original export + re-exports) + originalSymbolToExportInfos := &collections.MultiMap[ast.SymbolId, *SymbolExportInfo]{} + + ch, done := program.GetTypeChecker(ctx) + defer done() + + packageJsonFilter := ls.createPackageJsonImportFilter(fromFile) + + // Helper to add a symbol to the results map + addSymbol := func(moduleSymbol *ast.Symbol, toFile *ast.SourceFile, exportedSymbol *ast.Symbol, exportKind ExportKind, isFromPackageJson bool) { + if !ls.isImportable(fromFile, toFile, moduleSymbol, packageJsonFilter) { + return + } + + // Get unique ID for the exported symbol + symbolID := ast.GetSymbolId(exportedSymbol) + + moduleFileName := "" + if toFile != nil { + moduleFileName = toFile.FileName() + } + + originalSymbolToExportInfos.Add(symbolID, &SymbolExportInfo{ + symbol: exportedSymbol, + moduleSymbol: moduleSymbol, + moduleFileName: moduleFileName, + exportKind: exportKind, + targetFlags: ch.SkipAlias(exportedSymbol).Flags, + isFromPackageJson: isFromPackageJson, + }) + } + + // Iterate through all external modules + forEachExternalModuleToImportFrom( + ch, + program, + func(moduleSymbol *ast.Symbol, sourceFile *ast.SourceFile, checker *checker.Checker, isFromPackageJson bool) { + // Check for cancellation + if ctx.Err() != nil { + return + } + + compilerOptions := program.Options() + + // Check default export + defaultInfo := getDefaultLikeExportInfo(moduleSymbol, checker) + if defaultInfo != nil && + symbolFlagsHaveMeaning(checker.GetSymbolFlags(defaultInfo.exportingModuleSymbol), currentTokenMeaning) && + forEachNameOfDefaultExport(defaultInfo.exportingModuleSymbol, checker, compilerOptions.GetEmitScriptTarget(), func(name, capitalizedName string) string { + actualName := name + if isJsxTagName && capitalizedName != "" { + actualName = capitalizedName + } + if actualName == symbolName { + return actualName + } + return "" + }) != "" { + addSymbol(moduleSymbol, sourceFile, defaultInfo.exportingModuleSymbol, defaultInfo.exportKind, isFromPackageJson) + } + // Check for named export with identical name + exportSymbol := checker.TryGetMemberInModuleExportsAndProperties(symbolName, moduleSymbol) + if exportSymbol != nil && symbolFlagsHaveMeaning(checker.GetSymbolFlags(exportSymbol), currentTokenMeaning) { + addSymbol(moduleSymbol, sourceFile, exportSymbol, ExportKindNamed, isFromPackageJson) + } + }, + ) + + return originalSymbolToExportInfos +} + +func sortFixInfo(fixes []*fixInfo, fixContext *CodeFixContext) []*fixInfo { + if len(fixes) == 0 { + return fixes + } + + // Create a copy to avoid modifying the original + sorted := make([]*fixInfo, len(fixes)) + copy(sorted, fixes) + + // Create package.json filter for import filtering + packageJsonFilter := fixContext.LS.createPackageJsonImportFilter(fixContext.SourceFile) + + // Sort by: + // 1. JSX namespace fixes last + // 2. Fix kind (UseNamespace and AddToExisting preferred) + // 3. Module specifier comparison + slices.SortFunc(sorted, func(a, b *fixInfo) int { + // JSX namespace fixes should come last + if cmp := core.CompareBooleans(a.isJsxNamespaceFix, b.isJsxNamespaceFix); cmp != 0 { + return cmp + } + + // Compare fix kinds (lower is better) + if cmp := cmp.Compare(int(a.fix.kind), int(b.fix.kind)); cmp != 0 { + return cmp + } + + // Compare module specifiers + return fixContext.LS.compareModuleSpecifiers( + a.fix, + b.fix, + fixContext.SourceFile, + packageJsonFilter.allowsImportingSpecifier, + func(fileName string) tspath.Path { return tspath.Path(fileName) }, + ) + }) + + return sorted +} + +func promoteFromTypeOnly( + changes *change.Tracker, + aliasDeclaration *ast.Declaration, + program *compiler.Program, + sourceFile *ast.SourceFile, + ls *LanguageService, +) *ast.Declaration { + compilerOptions := program.Options() + // See comment in `doAddExistingFix` on constant with the same name. + convertExistingToTypeOnly := compilerOptions.VerbatimModuleSyntax + + switch aliasDeclaration.Kind { + case ast.KindImportSpecifier: + spec := aliasDeclaration.AsImportSpecifier() + if spec.IsTypeOnly { + if spec.Parent != nil && spec.Parent.Kind == ast.KindNamedImports { + // TypeScript creates a new specifier with isTypeOnly=false, computes insertion index, + // and if different from current position, deletes and re-inserts at new position. + // For now, we just delete the range from the first token (type keyword) to the property name or name. + firstToken := lsutil.GetFirstToken(aliasDeclaration, sourceFile) + typeKeywordPos := scanner.GetTokenPosOfNode(firstToken, sourceFile, false) + var targetNode *ast.DeclarationName + if spec.PropertyName != nil { + targetNode = spec.PropertyName + } else { + targetNode = spec.Name() + } + targetPos := scanner.GetTokenPosOfNode(targetNode.AsNode(), sourceFile, false) + changes.DeleteRange(sourceFile, core.NewTextRange(typeKeywordPos, targetPos)) + } + return aliasDeclaration + } else { + // The parent import clause is type-only + if spec.Parent == nil || spec.Parent.Kind != ast.KindNamedImports { + panic("ImportSpecifier parent must be NamedImports") + } + if spec.Parent.Parent == nil || spec.Parent.Parent.Kind != ast.KindImportClause { + panic("NamedImports parent must be ImportClause") + } + promoteImportClause(changes, spec.Parent.Parent.AsImportClause(), program, sourceFile, ls, convertExistingToTypeOnly, aliasDeclaration) + return spec.Parent.Parent + } + + case ast.KindImportClause: + promoteImportClause(changes, aliasDeclaration.AsImportClause(), program, sourceFile, ls, convertExistingToTypeOnly, aliasDeclaration) + return aliasDeclaration + + case ast.KindNamespaceImport: + // Promote the parent import clause + if aliasDeclaration.Parent == nil || aliasDeclaration.Parent.Kind != ast.KindImportClause { + panic("NamespaceImport parent must be ImportClause") + } + promoteImportClause(changes, aliasDeclaration.Parent.AsImportClause(), program, sourceFile, ls, convertExistingToTypeOnly, aliasDeclaration) + return aliasDeclaration.Parent + + case ast.KindImportEqualsDeclaration: + // Remove the 'type' keyword (which is the second token: 'import' 'type' name '=' ...) + importEqDecl := aliasDeclaration.AsImportEqualsDeclaration() + // The type keyword is after 'import' and before the name + scan := scanner.GetScannerForSourceFile(sourceFile, importEqDecl.Pos()) + // Skip 'import' keyword to get to 'type' + scan.Scan() + deleteTypeKeyword(changes, sourceFile, scan.TokenStart()) + return aliasDeclaration + default: + panic(fmt.Sprintf("Unexpected alias declaration kind: %v", aliasDeclaration.Kind)) + } +} + +// promoteImportClause removes the type keyword from an import clause +func promoteImportClause( + changes *change.Tracker, + importClause *ast.ImportClause, + program *compiler.Program, + sourceFile *ast.SourceFile, + ls *LanguageService, + convertExistingToTypeOnly core.Tristate, + aliasDeclaration *ast.Declaration, +) { + // Delete the 'type' keyword + if importClause.PhaseModifier == ast.KindTypeKeyword { + deleteTypeKeyword(changes, sourceFile, importClause.Pos()) + } + + // Handle .ts extension conversion to .js if necessary + compilerOptions := program.Options() + if compilerOptions.AllowImportingTsExtensions.IsFalse() { + moduleSpecifier := checker.TryGetModuleSpecifierFromDeclaration(importClause.Parent) + if moduleSpecifier != nil { + resolvedModule := program.GetResolvedModuleFromModuleSpecifier(sourceFile, moduleSpecifier) + if resolvedModule != nil && resolvedModule.ResolvedUsingTsExtension { + moduleText := moduleSpecifier.AsStringLiteral().Text + changedExtension := tspath.ChangeExtension( + moduleText, + outputpaths.GetOutputExtension(moduleText, compilerOptions.Jsx), + ) + // Replace the module specifier with the new extension + newStringLiteral := changes.NewStringLiteral(changedExtension) + changes.ReplaceNode(sourceFile, moduleSpecifier, newStringLiteral, nil) + } + } + } + + // Handle verbatimModuleSyntax conversion + // If convertExistingToTypeOnly is true, we need to add 'type' to other specifiers + // in the same import declaration + if convertExistingToTypeOnly.IsTrue() { + namedImports := importClause.NamedBindings + if namedImports != nil && namedImports.Kind == ast.KindNamedImports { + namedImportsData := namedImports.AsNamedImports() + if len(namedImportsData.Elements.Nodes) > 1 { + // Check if the list is sorted and if we need to reorder + _, isSorted := organizeimports.GetNamedImportSpecifierComparerWithDetection( + importClause.Parent, + sourceFile, + ls.UserPreferences(), + ) + + // If the alias declaration is an ImportSpecifier and the list is sorted, + // move it to index 0 (since it will be the only non-type-only import) + if isSorted.IsFalse() == false && // isSorted !== false + aliasDeclaration != nil && + aliasDeclaration.Kind == ast.KindImportSpecifier { + // Find the index of the alias declaration + aliasIndex := -1 + for i, element := range namedImportsData.Elements.Nodes { + if element == aliasDeclaration { + aliasIndex = i + break + } + } + // If not already at index 0, move it there + if aliasIndex > 0 { + // Delete the specifier from its current position + changes.Delete(sourceFile, aliasDeclaration) + // Insert it at index 0 + changes.InsertImportSpecifierAtIndex(sourceFile, aliasDeclaration, namedImports, 0) + } + } + + // Add 'type' keyword to all other import specifiers that aren't already type-only + for _, element := range namedImportsData.Elements.Nodes { + spec := element.AsImportSpecifier() + // Skip the specifier being promoted (if aliasDeclaration is an ImportSpecifier) + if aliasDeclaration != nil && aliasDeclaration.Kind == ast.KindImportSpecifier { + if element == aliasDeclaration { + continue + } + } + // Skip if already type-only + if !spec.IsTypeOnly { + changes.InsertModifierBefore(sourceFile, ast.KindTypeKeyword, element) + } + } + } + } + } +} + +// deleteTypeKeyword deletes the 'type' keyword token starting at the given position, +// including any trailing whitespace. +func deleteTypeKeyword(changes *change.Tracker, sourceFile *ast.SourceFile, startPos int) { + scan := scanner.GetScannerForSourceFile(sourceFile, startPos) + if scan.Token() != ast.KindTypeKeyword { + return + } + typeStart := scan.TokenStart() + typeEnd := scan.TokenEnd() + // Skip trailing whitespace + text := sourceFile.Text() + for typeEnd < len(text) && (text[typeEnd] == ' ' || text[typeEnd] == '\t') { + typeEnd++ + } + changes.DeleteRange(sourceFile, core.NewTextRange(typeStart, typeEnd)) +} diff --git a/internal/ls/utilities.go b/internal/ls/utilities.go index 465761bc56..480bf0ebc2 100644 --- a/internal/ls/utilities.go +++ b/internal/ls/utilities.go @@ -1176,6 +1176,22 @@ func getAdjustedLocationForExportDeclaration(node *ast.ExportDeclaration, forRen return nil } +func symbolFlagsHaveMeaning(flags ast.SymbolFlags, meaning ast.SemanticMeaning) bool { + if meaning == ast.SemanticMeaningAll { + return true + } + if meaning&ast.SemanticMeaningValue != 0 { + return flags&ast.SymbolFlagsValue != 0 + } + if meaning&ast.SemanticMeaningType != 0 { + return flags&ast.SymbolFlagsType != 0 + } + if meaning&ast.SemanticMeaningNamespace != 0 { + return flags&ast.SymbolFlagsNamespace != 0 + } + return false +} + func getMeaningFromLocation(node *ast.Node) ast.SemanticMeaning { // todo: check if this function needs to be changed for jsdoc updates node = getAdjustedLocation(node, false /*forRename*/, nil) diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 0c98f83ecd..29037f6c74 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -491,6 +491,7 @@ var handlers = sync.OnceValue(func() handlerMap { registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentDocumentHighlightInfo, (*Server).handleDocumentHighlight) registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentSelectionRangeInfo, (*Server).handleSelectionRange) registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentInlayHintInfo, (*Server).handleInlayHint) + registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentCodeActionInfo, (*Server).handleCodeAction) registerRequestHandler(handlers, lsproto.WorkspaceSymbolInfo, (*Server).handleWorkspaceSymbol) registerRequestHandler(handlers, lsproto.CompletionItemResolveInfo, (*Server).handleCompletionItemResolve) @@ -676,6 +677,13 @@ func (s *Server) handleInitialize(ctx context.Context, params *lsproto.Initializ InlayHintProvider: &lsproto.BooleanOrInlayHintOptionsOrInlayHintRegistrationOptions{ Boolean: ptrTo(true), }, + CodeActionProvider: &lsproto.BooleanOrCodeActionOptions{ + CodeActionOptions: &lsproto.CodeActionOptions{ + CodeActionKinds: &[]lsproto.CodeActionKind{ + lsproto.CodeActionKindQuickFix, + }, + }, + }, }, } @@ -919,6 +927,10 @@ func (s *Server) handleSelectionRange(ctx context.Context, ls *ls.LanguageServic return ls.ProvideSelectionRanges(ctx, params) } +func (s *Server) handleCodeAction(ctx context.Context, ls *ls.LanguageService, params *lsproto.CodeActionParams) (lsproto.CodeActionResponse, error) { + return ls.ProvideCodeActions(ctx, params) +} + func (s *Server) Log(msg ...any) { fmt.Fprintln(s.stderr, msg...) } diff --git a/internal/testrunner/test_case_parser.go b/internal/testrunner/test_case_parser.go index 62fa71de79..e8fbe2de3b 100644 --- a/internal/testrunner/test_case_parser.go +++ b/internal/testrunner/test_case_parser.go @@ -107,6 +107,13 @@ func makeUnitsFromTest(code string, fileName string) testCaseContent { } } +type ParseTestFilesOptions struct { + // If true, allows test content to appear before the first @Filename directive. + // In this case, an implicit first file is created using the fileName parameter. + // This matches the behavior of the TypeScript fourslash test harness. + AllowImplicitFirstFile bool +} + // Given a test file containing // @FileName and // @symlink directives, // return an array of named units of code to be added to an existing compiler instance, // along with a map of symlinks and the current directory. @@ -114,6 +121,15 @@ func ParseTestFilesAndSymlinks[T any]( code string, fileName string, parseFile func(filename string, content string, fileOptions map[string]string) (T, error), +) (units []T, symlinks map[string]string, currentDir string, globalOptions map[string]string, e error) { + return ParseTestFilesAndSymlinksWithOptions(code, fileName, parseFile, ParseTestFilesOptions{}) +} + +func ParseTestFilesAndSymlinksWithOptions[T any]( + code string, + fileName string, + parseFile func(filename string, content string, fileOptions map[string]string) (T, error), + options ParseTestFilesOptions, ) (units []T, symlinks map[string]string, currentDir string, globalOptions map[string]string, e error) { // List of all the subfiles we've parsed out var testUnits []T @@ -123,6 +139,11 @@ func ParseTestFilesAndSymlinks[T any]( // Stuff related to the subfile we're parsing var currentFileContent strings.Builder var currentFileName string + if options.AllowImplicitFirstFile { + // For fourslash tests, initialize currentFileName to the fileName parameter + // so content before the first @Filename directive goes into an implicit first file + currentFileName = fileName + } var currentDirectory string var parseError error currentFileOptions := make(map[string]string) @@ -158,13 +179,16 @@ func ParseTestFilesAndSymlinks[T any]( // New metadata statement after having collected some code to go with the previous metadata if currentFileName != "" { - // Store result file - newTestFile, e := parseFile(currentFileName, currentFileContent.String(), currentFileOptions) - if e != nil { - parseError = e - break + // Store result file - always save for regular tests, but skip empty implicit first file for fourslash + shouldSaveFile := currentFileContent.Len() != 0 || !options.AllowImplicitFirstFile + if shouldSaveFile { + newTestFile, e := parseFile(currentFileName, currentFileContent.String(), currentFileOptions) + if e != nil { + parseError = e + break + } + testUnits = append(testUnits, newTestFile) } - testUnits = append(testUnits, newTestFile) // Reset local data currentFileContent.Reset() @@ -172,11 +196,27 @@ func ParseTestFilesAndSymlinks[T any]( currentFileOptions = make(map[string]string) } else { // First metadata marker in the file - currentFileName = strings.TrimSpace(testMetaData[2]) - if currentFileContent.Len() != 0 && scanner.SkipTrivia(currentFileContent.String(), 0) != currentFileContent.Len() { + hasContentBeforeFirstFilename := currentFileContent.Len() != 0 && scanner.SkipTrivia(currentFileContent.String(), 0) != currentFileContent.Len() + if hasContentBeforeFirstFilename && !options.AllowImplicitFirstFile { panic("Non-comment test content appears before the first '// @Filename' directive") } + + // If we have content before the first @Filename and AllowImplicitFirstFile is true, + // we need to save it as an implicit first file before starting the new file + if hasContentBeforeFirstFilename && options.AllowImplicitFirstFile && currentFileName != "" { + // Store the implicit first file + newTestFile, e := parseFile(currentFileName, currentFileContent.String(), currentFileOptions) + if e != nil { + parseError = e + break + } + testUnits = append(testUnits, newTestFile) + } + + // Reset for the new file currentFileContent.Reset() + currentFileName = strings.TrimSpace(testMetaData[2]) + currentFileOptions = make(map[string]string) } } else { // Subfile content line