diff --git a/pkg/ast/parseoptions.go b/pkg/ast/parseoptions.go index 02700589d..6e66352a1 100644 --- a/pkg/ast/parseoptions.go +++ b/pkg/ast/parseoptions.go @@ -112,9 +112,6 @@ func isFileProbablyExternalModule(sourceFile *SourceFile) *Node { } func isAnExternalModuleIndicatorNode(node *Node) bool { - if node.Flags&NodeFlagsReparsed != 0 { - return false - } return HasSyntacticModifier(node, ModifierFlagsExport) || IsImportEqualsDeclaration(node) && IsExternalModuleReference(node.AsImportEqualsDeclaration().ModuleReference) || IsImportDeclaration(node) || IsExportAssignment(node) || IsExportDeclaration(node) diff --git a/pkg/ast/utilities.go b/pkg/ast/utilities.go index 7311ab29b..b842a0573 100644 --- a/pkg/ast/utilities.go +++ b/pkg/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/pkg/astnav/tokens.go b/pkg/astnav/tokens.go index ecc56762d..417494a12 100644 --- a/pkg/astnav/tokens.go +++ b/pkg/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/pkg/binder/binder.go b/pkg/binder/binder.go index bbcdf1850..1f8a6a820 100644 --- a/pkg/binder/binder.go +++ b/pkg/binder/binder.go @@ -377,7 +377,7 @@ func GetSymbolNameForPrivateIdentifier(containingClassSymbol *ast.Symbol, descri func (b *Binder) declareModuleMember(node *ast.Node, symbolFlags ast.SymbolFlags, symbolExcludes ast.SymbolFlags) *ast.Symbol { container := b.container - if node.Kind == ast.KindCommonJSExport { + if ast.IsCommonJSExport(node) { container = b.file.AsNode() } hasExportModifier := ast.GetCombinedModifierFlags(node)&ast.ModifierFlagsExport != 0 || ast.IsImplicitlyExportedJSTypeAlias(node) @@ -402,7 +402,7 @@ func (b *Binder) declareModuleMember(node *ast.Node, symbolFlags ast.SymbolFlags // during global merging in the checker. Why? The only case when ambient module is permitted inside another module is module augmentation // and this case is specially handled. Module augmentations should only be merged with original module definition // and should never be merged directly with other augmentation, and the latter case would be possible if automatic merge is allowed. - if !ast.IsAmbientModule(node) && (hasExportModifier || container.Flags&ast.NodeFlagsExportContext != 0) { + if !ast.IsAmbientModule(node) && (hasExportModifier || ast.IsCommonJSExport(node) || container.Flags&ast.NodeFlagsExportContext != 0) { if !ast.IsLocalsContainer(container) || (ast.HasSyntacticModifier(node, ast.ModifierFlagsDefault) && b.getDeclarationName(node) == ast.InternalSymbolNameMissing) || ast.IsCommonJSExport(node) { return b.declareSymbol(ast.GetExports(container.Symbol()), container.Symbol(), node, symbolFlags, symbolExcludes) // No local symbol for an unnamed default! diff --git a/pkg/checker/checker.go b/pkg/checker/checker.go index 8693d926a..ea9a1f059 100644 --- a/pkg/checker/checker.go +++ b/pkg/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/pkg/checker/exports.go b/pkg/checker/exports.go index d895648eb..eb9b8774b 100644 --- a/pkg/checker/exports.go +++ b/pkg/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/pkg/checker/grammarchecks.go b/pkg/checker/grammarchecks.go index 11d410f48..c09c22f67 100644 --- a/pkg/checker/grammarchecks.go +++ b/pkg/checker/grammarchecks.go @@ -798,8 +798,8 @@ func (c *Checker) checkGrammarArrowFunction(node *ast.Node, file *ast.SourceFile } equalsGreaterThanToken := arrowFunc.EqualsGreaterThanToken - startLine, _ := scanner.GetECMALineAndCharacterOfPosition(file, equalsGreaterThanToken.Pos()) - endLine, _ := scanner.GetECMALineAndCharacterOfPosition(file, equalsGreaterThanToken.End()) + startLine := scanner.GetECMALineOfPosition(file, equalsGreaterThanToken.Pos()) + endLine := scanner.GetECMALineOfPosition(file, equalsGreaterThanToken.End()) return startLine != endLine && c.grammarErrorOnNode(equalsGreaterThanToken, diagnostics.Line_terminator_not_permitted_before_arrow) } diff --git a/pkg/checker/utilities.go b/pkg/checker/utilities.go index 7bdf3626b..d7aa86b18 100644 --- a/pkg/checker/utilities.go +++ b/pkg/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/pkg/compiler/program.go b/pkg/compiler/program.go index 42e769935..b90af92a6 100644 --- a/pkg/compiler/program.go +++ b/pkg/compiler/program.go @@ -1082,7 +1082,7 @@ func (p *Program) getDiagnosticsWithPrecedingDirectives(sourceFile *ast.SourceFi // Build map of directives by line number directivesByLine := make(map[int]ast.CommentDirective) for _, directive := range sourceFile.CommentDirectives { - line, _ := scanner.GetECMALineAndCharacterOfPosition(sourceFile, directive.Loc.Pos()) + line := scanner.GetECMALineOfPosition(sourceFile, directive.Loc.Pos()) directivesByLine[line] = directive } lineStarts := scanner.GetECMALineStarts(sourceFile) diff --git a/pkg/diagnosticwriter/diagnosticwriter.go b/pkg/diagnosticwriter/diagnosticwriter.go index 3e2234e9c..ec09bc9da 100644 --- a/pkg/diagnosticwriter/diagnosticwriter.go +++ b/pkg/diagnosticwriter/diagnosticwriter.go @@ -90,7 +90,7 @@ func writeCodeSnippet(writer io.Writer, sourceFile *ast.SourceFile, start int, l lastLineChar++ // When length is zero, squiggle the character right after the start position. } - lastLineOfFile, _ := scanner.GetECMALineAndCharacterOfPosition(sourceFile, len(sourceFile.Text())) + lastLineOfFile := scanner.GetECMALineOfPosition(sourceFile, len(sourceFile.Text())) hasMoreThanFiveLines := lastLine-firstLine >= 4 gutterWidth := len(strconv.Itoa(lastLine + 1)) @@ -357,7 +357,7 @@ func prettyPathForFileError(file *ast.SourceFile, fileErrors []*ast.Diagnostic, if file == nil || len(fileErrors) == 0 { return "" } - line, _ := scanner.GetECMALineAndCharacterOfPosition(file, fileErrors[0].Loc().Pos()) + line := scanner.GetECMALineOfPosition(file, fileErrors[0].Loc().Pos()) fileName := file.FileName() if tspath.PathIsAbsolute(fileName) && tspath.PathIsAbsolute(formatOpts.CurrentDirectory) { fileName = tspath.ConvertToRelativePath(file.FileName(), formatOpts.ComparePathsOptions) diff --git a/pkg/execute/tsc.go b/pkg/execute/tsc.go index e03040d3d..f1470a8fb 100644 --- a/pkg/execute/tsc.go +++ b/pkg/execute/tsc.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/buke/typescript-go-internal/pkg/ast" + "github.com/buke/typescript-go-internal/pkg/collections" "github.com/buke/typescript-go-internal/pkg/compiler" "github.com/buke/typescript-go-internal/pkg/core" "github.com/buke/typescript-go-internal/pkg/diagnostics" @@ -113,7 +114,8 @@ func tscCompilation(sys tsc.System, commandLine *tsoptions.ParsedCommandLine, te } if commandLine.CompilerOptions().Init.IsTrue() { - return tsc.CommandLineResult{Status: tsc.ExitStatusNotImplemented} + tsc.WriteConfigFile(sys, reportDiagnostic, commandLine.Raw.(*collections.OrderedMap[string, any])) + return tsc.CommandLineResult{Status: tsc.ExitStatusSuccess} } if commandLine.CompilerOptions().Version.IsTrue() { diff --git a/pkg/execute/tsc/init.go b/pkg/execute/tsc/init.go new file mode 100644 index 000000000..836685424 --- /dev/null +++ b/pkg/execute/tsc/init.go @@ -0,0 +1,216 @@ +package tsc + +import ( + "fmt" + "reflect" + "slices" + "strings" + + "github.com/buke/typescript-go-internal/pkg/ast" + "github.com/buke/typescript-go-internal/pkg/collections" + "github.com/buke/typescript-go-internal/pkg/core" + "github.com/buke/typescript-go-internal/pkg/diagnostics" + "github.com/buke/typescript-go-internal/pkg/jsonutil" + "github.com/buke/typescript-go-internal/pkg/tsoptions" + "github.com/buke/typescript-go-internal/pkg/tspath" +) + +func WriteConfigFile(sys System, reportDiagnostic DiagnosticReporter, options *collections.OrderedMap[string, any]) { + getCurrentDirectory := sys.GetCurrentDirectory() + file := tspath.NormalizePath(tspath.CombinePaths(getCurrentDirectory, "tsconfig.json")) + if sys.FS().FileExists(file) { + reportDiagnostic(ast.NewCompilerDiagnostic(diagnostics.A_tsconfig_json_file_is_already_defined_at_Colon_0, file)) + } else { + _ = sys.FS().WriteFile(file, generateTSConfig(options), false) + output := []string{"\n"} + output = append(output, getHeader(sys, "Created a new tsconfig.json")...) + output = append(output, "You can learn more at https://aka.ms/tsconfig", "\n") + fmt.Fprint(sys.Writer(), strings.Join(output, "")) + } +} + +func generateTSConfig(options *collections.OrderedMap[string, any]) string { + const tab = " " + var result []string + + allSetOptions := make([]string, 0, options.Size()) + for k := range options.Keys() { + if k != "init" && k != "help" && k != "watch" { + allSetOptions = append(allSetOptions, k) + } + } + + // !!! locale getLocaleSpecificMessage + emitHeader := func(header *diagnostics.Message) { + result = append(result, tab+tab+"// "+header.Format()) + } + newline := func() { + result = append(result, "") + } + push := func(args ...string) { + result = append(result, args...) + } + + formatSingleValue := func(value any, enumMap *collections.OrderedMap[string, any]) string { + if enumMap != nil { + var found bool + for k, v := range enumMap.Entries() { + if value == v { + value = k + found = true + break + } + } + if !found { + panic(fmt.Sprintf("No matching value of %v", value)) + } + } + + b, err := jsonutil.MarshalIndent(value, "", "") + if err != nil { + panic(fmt.Sprintf("should not happen: %v", err)) + } + return string(b) + } + + formatValueOrArray := func(settingName string, value any) string { + var option *tsoptions.CommandLineOption + for _, decl := range tsoptions.OptionsDeclarations { + if decl.Name == settingName { + option = decl + } + } + if option == nil { + panic(`No option named ` + settingName) + } + + rval := reflect.ValueOf(value) + if rval.Kind() == reflect.Slice { + var enumMap *collections.OrderedMap[string, any] + if elemOption := option.Elements(); elemOption != nil { + enumMap = elemOption.EnumMap() + } + + var elems []string + for i := range rval.Len() { + elems = append(elems, formatSingleValue(rval.Index(i).Interface(), enumMap)) + } + return `[` + strings.Join(elems, ", ") + `]` + } else { + return formatSingleValue(value, option.EnumMap()) + } + } + + // commentedNever': Never comment this out + // commentedAlways': Always comment this out, even if it's on commandline + // commentedOptional': Comment out unless it's on commandline + type commented int + const ( + commentedNever commented = iota + commentedAlways + commentedOptional + ) + emitOption := func(setting string, defaultValue any, commented commented) { + if commented > 2 { + panic("should not happen: invalid `commented`, must be a bug.") + } + + existingOptionIndex := slices.Index(allSetOptions, setting) + if existingOptionIndex >= 0 { + allSetOptions = slices.Delete(allSetOptions, existingOptionIndex, existingOptionIndex+1) + } + + var comment bool + switch commented { + case commentedAlways: + comment = true + case commentedNever: + comment = false + default: + comment = !options.Has(setting) + } + + value, ok := options.Get(setting) + if !ok { + value = defaultValue + } + + if comment { + push(tab + tab + `// "` + setting + `": ` + formatValueOrArray(setting, value) + `,`) + } else { + push(tab + tab + `"` + setting + `": ` + formatValueOrArray(setting, value) + `,`) + } + } + + push("{") + // !!! locale getLocaleSpecificMessage + push(tab + `// ` + diagnostics.Visit_https_Colon_Slash_Slashaka_ms_Slashtsconfig_to_read_more_about_this_file.Format()) + push(tab + `"compilerOptions": {`) + + emitHeader(diagnostics.File_Layout) + emitOption("rootDir", "./src", commentedOptional) + emitOption("outDir", "./dist", commentedOptional) + + newline() + + emitHeader(diagnostics.Environment_Settings) + emitHeader(diagnostics.See_also_https_Colon_Slash_Slashaka_ms_Slashtsconfig_Slashmodule) + emitOption("module", core.ModuleKindNodeNext, commentedNever) + emitOption("target", core.ScriptTargetESNext, commentedNever) + emitOption("types", []any{}, commentedNever) + if lib, ok := options.Get("lib"); ok { + emitOption("lib", lib, commentedNever) + } + emitHeader(diagnostics.For_nodejs_Colon) + push(tab + tab + `// "lib": ["esnext"],`) + push(tab + tab + `// "types": ["node"],`) + emitHeader(diagnostics.X_and_npm_install_D_types_Slashnode) + + newline() + + emitHeader(diagnostics.Other_Outputs) + emitOption("sourceMap" /*defaultValue*/, true, commentedNever) + emitOption("declaration" /*defaultValue*/, true, commentedNever) + emitOption("declarationMap" /*defaultValue*/, true, commentedNever) + + newline() + + emitHeader(diagnostics.Stricter_Typechecking_Options) + emitOption("noUncheckedIndexedAccess" /*defaultValue*/, true, commentedNever) + emitOption("exactOptionalPropertyTypes" /*defaultValue*/, true, commentedNever) + + newline() + + emitHeader(diagnostics.Style_Options) + emitOption("noImplicitReturns" /*defaultValue*/, true, commentedOptional) + emitOption("noImplicitOverride" /*defaultValue*/, true, commentedOptional) + emitOption("noUnusedLocals" /*defaultValue*/, true, commentedOptional) + emitOption("noUnusedParameters" /*defaultValue*/, true, commentedOptional) + emitOption("noFallthroughCasesInSwitch" /*defaultValue*/, true, commentedOptional) + emitOption("noPropertyAccessFromIndexSignature" /*defaultValue*/, true, commentedOptional) + + newline() + + emitHeader(diagnostics.Recommended_Options) + emitOption("strict" /*defaultValue*/, true, commentedNever) + emitOption("jsx", core.JsxEmitReactJSX, commentedNever) + emitOption("verbatimModuleSyntax" /*defaultValue*/, true, commentedNever) + emitOption("isolatedModules" /*defaultValue*/, true, commentedNever) + emitOption("noUncheckedSideEffectImports" /*defaultValue*/, true, commentedNever) + emitOption("moduleDetection", core.ModuleDetectionKindForce, commentedNever) + emitOption("skipLibCheck" /*defaultValue*/, true, commentedNever) + + // Write any user-provided options we haven't already + if len(allSetOptions) > 0 { + newline() + for len(allSetOptions) > 0 { + emitOption(allSetOptions[0], options.GetOrZero(allSetOptions[0]), commentedNever) + } + } + + push(tab + "}") + push(`}`) + push(``) + + return strings.Join(result, "\n") +} diff --git a/pkg/execute/tsctests/tsc_test.go b/pkg/execute/tsctests/tsc_test.go index 716ee5d68..fe49d0b23 100644 --- a/pkg/execute/tsctests/tsc_test.go +++ b/pkg/execute/tsctests/tsc_test.go @@ -36,6 +36,60 @@ func TestTscCommandline(t *testing.T) { subScenario: "when build not first argument", commandLineArgs: []string{"--verbose", "--build"}, }, + { + subScenario: "Initialized TSConfig with files options", + commandLineArgs: []string{"--init", "file0.st", "file1.ts", "file2.ts"}, + }, + { + subScenario: "Initialized TSConfig with boolean value compiler options", + commandLineArgs: []string{"--init", "--noUnusedLocals"}, + }, + { + subScenario: "Initialized TSConfig with enum value compiler options", + commandLineArgs: []string{"--init", "--target", "es5", "--jsx", "react"}, + }, + { + subScenario: "Initialized TSConfig with list compiler options", + commandLineArgs: []string{"--init", "--types", "jquery,mocha"}, + }, + { + subScenario: "Initialized TSConfig with list compiler options with enum value", + commandLineArgs: []string{"--init", "--lib", "es5,es2015.core"}, + }, + { + subScenario: "Initialized TSConfig with incorrect compiler option", + commandLineArgs: []string{"--init", "--someNonExistOption"}, + }, + { + subScenario: "Initialized TSConfig with incorrect compiler option value", + commandLineArgs: []string{"--init", "--lib", "nonExistLib,es5,es2015.promise"}, + }, + { + subScenario: "Initialized TSConfig with advanced options", + commandLineArgs: []string{"--init", "--declaration", "--declarationDir", "lib", "--skipLibCheck", "--noErrorTruncation"}, + }, + { + subScenario: "Initialized TSConfig with --help", + commandLineArgs: []string{"--init", "--help"}, + }, + { + subScenario: "Initialized TSConfig with --watch", + commandLineArgs: []string{"--init", "--watch"}, + }, + { + subScenario: "Initialized TSConfig with tsconfig.json", + commandLineArgs: []string{"--init"}, + files: FileMap{ + "/home/src/workspaces/project/first.ts": `export const a = 1`, + "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` + { + "compilerOptions": { + "strict": true, + "noEmit": true + } + }`), + }, + }, { subScenario: "help", commandLineArgs: []string{"--help"}, diff --git a/pkg/format/api.go b/pkg/format/api.go index 3f76ff9a9..0741ffbb5 100644 --- a/pkg/format/api.go +++ b/pkg/format/api.go @@ -146,7 +146,7 @@ func FormatOnSemicolon(ctx context.Context, sourceFile *ast.SourceFile, position } func FormatOnEnter(ctx context.Context, sourceFile *ast.SourceFile, position int) []core.TextChange { - line, _ := scanner.GetECMALineAndCharacterOfPosition(sourceFile, position) + line := scanner.GetECMALineOfPosition(sourceFile, position) if line == 0 { return nil } diff --git a/pkg/format/indent.go b/pkg/format/indent.go index 99262f40a..6cc319c0f 100644 --- a/pkg/format/indent.go +++ b/pkg/format/indent.go @@ -68,7 +68,7 @@ func getIndentationForNodeWorker( // }) looking at the relationship between the list and *first* list item. var listIndentsChild bool if firstListChild != nil { - listLine, _ := getStartLineAndCharacterForNode(firstListChild, sourceFile) + listLine := getStartLineForNode(firstListChild, sourceFile) listIndentsChild = listLine > containingListOrParentStartLine } actualIndentation := getActualIndentationForListItem(current, sourceFile, options, listIndentsChild) @@ -134,7 +134,7 @@ func isArgumentAndStartLineOverlapsExpressionBeingCalled(parent *ast.Node, child return false } expressionOfCallExpressionEnd := parent.Expression().End() - expressionOfCallExpressionEndLine, _ := scanner.GetECMALineAndCharacterOfPosition(sourceFile, expressionOfCallExpressionEnd) + expressionOfCallExpressionEndLine := scanner.GetECMALineOfPosition(sourceFile, expressionOfCallExpressionEnd) return expressionOfCallExpressionEndLine == childStartLine } @@ -188,7 +188,7 @@ func deriveActualIndentationFromList(list *ast.NodeList, index int, sourceFile * continue } // skip list items that ends on the same line with the current list element - prevEndLine, _ := scanner.GetECMALineAndCharacterOfPosition(sourceFile, list.Nodes[i].End()) + prevEndLine := scanner.GetECMALineOfPosition(sourceFile, list.Nodes[i].End()) if prevEndLine != line { return findColumnForFirstNonWhitespaceCharacterInLine(line, char, sourceFile, options) } @@ -243,7 +243,7 @@ func childStartsOnTheSameLineWithElseInIfStatement(parent *ast.Node, child *ast. if parent.Kind == ast.KindIfStatement && parent.AsIfStatement().ElseStatement == child { elseKeyword := astnav.FindPrecedingToken(sourceFile, child.Pos()) debug.AssertIsDefined(elseKeyword) - elseKeywordStartLine, _ := getStartLineAndCharacterForNode(elseKeyword, sourceFile) + elseKeywordStartLine := getStartLineForNode(elseKeyword, sourceFile) return elseKeywordStartLine == childStartLine } return false @@ -253,6 +253,10 @@ func getStartLineAndCharacterForNode(n *ast.Node, sourceFile *ast.SourceFile) (l return scanner.GetECMALineAndCharacterOfPosition(sourceFile, scanner.GetTokenPosOfNode(n, sourceFile, false)) } +func getStartLineForNode(n *ast.Node, sourceFile *ast.SourceFile) int { + return scanner.GetECMALineOfPosition(sourceFile, scanner.GetTokenPosOfNode(n, sourceFile, false)) +} + func GetContainingList(node *ast.Node, sourceFile *ast.SourceFile) *ast.NodeList { if node.Parent == nil { return nil @@ -438,8 +442,8 @@ func NodeWillIndentChild(settings *FormatCodeSettings, parent *ast.Node, child * return rangeIsOnOneLine(child.Loc, sourceFile) } if parent.Kind == ast.KindBinaryExpression && sourceFile != nil && childKind == ast.KindJsxElement { - parentStartLine, _ := scanner.GetECMALineAndCharacterOfPosition(sourceFile, scanner.SkipTrivia(sourceFile.Text(), parent.Pos())) - childStartLine, _ := scanner.GetECMALineAndCharacterOfPosition(sourceFile, scanner.SkipTrivia(sourceFile.Text(), child.Pos())) + parentStartLine := scanner.GetECMALineOfPosition(sourceFile, scanner.SkipTrivia(sourceFile.Text(), parent.Pos())) + childStartLine := scanner.GetECMALineOfPosition(sourceFile, scanner.SkipTrivia(sourceFile.Text(), child.Pos())) return parentStartLine != childStartLine } if parent.Kind != ast.KindBinaryExpression { @@ -515,7 +519,7 @@ func NodeWillIndentChild(settings *FormatCodeSettings, parent *ast.Node, child * // branch beginning on the line that the whenTrue branch ends. func childIsUnindentedBranchOfConditionalExpression(parent *ast.Node, child *ast.Node, childStartLine int, sourceFile *ast.SourceFile) bool { if parent.Kind == ast.KindConditionalExpression && (child == parent.AsConditionalExpression().WhenTrue || child == parent.AsConditionalExpression().WhenFalse) { - conditionEndLine, _ := scanner.GetECMALineAndCharacterOfPosition(sourceFile, parent.AsConditionalExpression().Condition.End()) + conditionEndLine := scanner.GetECMALineOfPosition(sourceFile, parent.AsConditionalExpression().Condition.End()) if child == parent.AsConditionalExpression().WhenTrue { return childStartLine == conditionEndLine } else { @@ -526,8 +530,8 @@ func childIsUnindentedBranchOfConditionalExpression(parent *ast.Node, child *ast // ? 1 : ( L1: whenTrue indented because it's on a new line // 0 L2: indented two stops, one because whenTrue was indented // ); and one because of the parentheses spanning multiple lines - trueStartLine, _ := getStartLineAndCharacterForNode(parent.AsConditionalExpression().WhenTrue, sourceFile) - trueEndLine, _ := scanner.GetECMALineAndCharacterOfPosition(sourceFile, parent.AsConditionalExpression().WhenTrue.End()) + trueStartLine := getStartLineForNode(parent.AsConditionalExpression().WhenTrue, sourceFile) + trueEndLine := scanner.GetECMALineOfPosition(sourceFile, parent.AsConditionalExpression().WhenTrue.End()) return conditionEndLine == trueStartLine && trueEndLine == childStartLine } } @@ -549,7 +553,7 @@ func argumentStartsOnSameLineAsPreviousArgument(parent *ast.Node, child *ast.Nod } previousNode := parent.Arguments()[currentIndex-1] - lineOfPreviousNode, _ := scanner.GetECMALineAndCharacterOfPosition(sourceFile, previousNode.End()) + lineOfPreviousNode := scanner.GetECMALineOfPosition(sourceFile, previousNode.End()) if childStartLine == lineOfPreviousNode { return true } diff --git a/pkg/format/rulecontext.go b/pkg/format/rulecontext.go index f3cedf53e..33298af94 100644 --- a/pkg/format/rulecontext.go +++ b/pkg/format/rulecontext.go @@ -584,8 +584,8 @@ func isSemicolonDeletionContext(context *FormattingContext) bool { nextTokenStart = scanner.GetTokenPosOfNode(nextRealToken, context.SourceFile, false) } - startLine, _ := scanner.GetECMALineAndCharacterOfPosition(context.SourceFile, context.currentTokenSpan.Loc.Pos()) - endLine, _ := scanner.GetECMALineAndCharacterOfPosition(context.SourceFile, nextTokenStart) + startLine := scanner.GetECMALineOfPosition(context.SourceFile, context.currentTokenSpan.Loc.Pos()) + endLine := scanner.GetECMALineOfPosition(context.SourceFile, nextTokenStart) if startLine == endLine { return nextTokenKind == ast.KindCloseBraceToken || nextTokenKind == ast.KindEndOfFile } diff --git a/pkg/format/span.go b/pkg/format/span.go index de654cae2..340d9e59e 100644 --- a/pkg/format/span.go +++ b/pkg/format/span.go @@ -85,7 +85,7 @@ func getOwnOrInheritedDelta(n *ast.Node, options *FormatCodeSettings, sourceFile previousLine := -1 var child *ast.Node for n != nil { - line, _ := scanner.GetECMALineAndCharacterOfPosition(sourceFile, withTokenStart(n, sourceFile).Pos()) + line := scanner.GetECMALineOfPosition(sourceFile, withTokenStart(n, sourceFile).Pos()) if previousLine != -1 && line != previousLine { break } @@ -242,10 +242,10 @@ func (w *formatSpanWorker) execute(s *formattingScanner) []core.TextChange { w.formattingScanner.advance() if w.formattingScanner.isOnToken() { - startLine, _ := scanner.GetECMALineAndCharacterOfPosition(w.sourceFile, withTokenStart(w.enclosingNode, w.sourceFile).Pos()) + startLine := scanner.GetECMALineOfPosition(w.sourceFile, withTokenStart(w.enclosingNode, w.sourceFile).Pos()) undecoratedStartLine := startLine if ast.HasDecorators(w.enclosingNode) { - undecoratedStartLine, _ = scanner.GetECMALineAndCharacterOfPosition(w.sourceFile, getNonDecoratorTokenPosOfNode(w.enclosingNode, w.sourceFile)) + undecoratedStartLine = scanner.GetECMALineOfPosition(w.sourceFile, getNonDecoratorTokenPosOfNode(w.enclosingNode, w.sourceFile)) } w.processNode(w.enclosingNode, w.enclosingNode, startLine, undecoratedStartLine, w.initialIndentation, w.delta) @@ -305,7 +305,7 @@ func (w *formatSpanWorker) execute(s *formattingScanner) []core.TextChange { if parent == nil { parent = w.previousParent } - line, _ := scanner.GetECMALineAndCharacterOfPosition(w.sourceFile, tokenInfo.Loc.Pos()) + line := scanner.GetECMALineOfPosition(w.sourceFile, tokenInfo.Loc.Pos()) w.processPair( tokenInfo, line, @@ -343,11 +343,11 @@ func (w *formatSpanWorker) processChildNode( } childStartPos := scanner.GetTokenPosOfNode(child, w.sourceFile, false) - childStartLine, _ := scanner.GetECMALineAndCharacterOfPosition(w.sourceFile, childStartPos) + childStartLine := scanner.GetECMALineOfPosition(w.sourceFile, childStartPos) undecoratedChildStartLine := childStartLine if ast.HasDecorators(child) { - undecoratedChildStartLine, _ = scanner.GetECMALineAndCharacterOfPosition(w.sourceFile, getNonDecoratorTokenPosOfNode(child, w.sourceFile)) + undecoratedChildStartLine = scanner.GetECMALineOfPosition(w.sourceFile, getNonDecoratorTokenPosOfNode(child, w.sourceFile)) } // if child is a list item - try to get its indentation, only if parent is within the original range. @@ -457,7 +457,7 @@ func (w *formatSpanWorker) processChildNodes( break } else if tokenInfo.token.Kind == listStartToken { // consume list start token - startLine, _ = scanner.GetECMALineAndCharacterOfPosition(w.sourceFile, tokenInfo.token.Loc.Pos()) + startLine = scanner.GetECMALineOfPosition(w.sourceFile, tokenInfo.token.Loc.Pos()) w.consumeTokenAndAdvanceScanner(tokenInfo, parent, parentDynamicIndentation, parent, false) @@ -578,7 +578,7 @@ func (w *formatSpanWorker) tryComputeIndentationForListItem(startPos int, endPos return inheritedIndentation } } else { - startLine, _ := scanner.GetECMALineAndCharacterOfPosition(w.sourceFile, startPos) + startLine := scanner.GetECMALineOfPosition(w.sourceFile, startPos) startLinePosition := GetLineStartPositionForPosition(startPos, w.sourceFile) column := FindFirstNonWhitespaceColumn(startLinePosition, startPos, w.sourceFile, w.formattingContext.Options) if startLine != parentStartLine || startPos == column { @@ -747,7 +747,7 @@ func (w *formatSpanWorker) processRange(r TextRangeWithKind, rangeStartLine int, if !rangeHasError { if w.previousRange == NewTextRangeWithKind(0, 0, 0) { // trim whitespaces starting from the beginning of the span up to the current line - originalStartLine, _ := scanner.GetECMALineAndCharacterOfPosition(w.sourceFile, w.originalRange.Pos()) + originalStartLine := scanner.GetECMALineOfPosition(w.sourceFile, w.originalRange.Pos()) w.trimTrailingWhitespacesForLines(originalStartLine, rangeStartLine, NewTextRangeWithKind(0, 0, 0)) } else { lineAction = w.processPair(r, rangeStartLine, parent, w.previousRange, w.previousRangeStartLine, w.previousParent, contextNode, dynamicIndentation) @@ -797,8 +797,8 @@ func (w *formatSpanWorker) trimTrailingWhitespacesForRemainingRange(trivias []Te } func (w *formatSpanWorker) trimTrailingWitespacesForPositions(startPos int, endPos int, previousRange TextRangeWithKind) { - startLine, _ := scanner.GetECMALineAndCharacterOfPosition(w.sourceFile, startPos) - endLine, _ := scanner.GetECMALineAndCharacterOfPosition(w.sourceFile, endPos) + startLine := scanner.GetECMALineOfPosition(w.sourceFile, startPos) + endLine := scanner.GetECMALineOfPosition(w.sourceFile, endPos) w.trimTrailingWhitespacesForLines(startLine, endLine+1, previousRange) } @@ -909,8 +909,8 @@ func (w *formatSpanWorker) indentTriviaItems(trivia []TextRangeWithKind, comment func (w *formatSpanWorker) indentMultilineComment(commentRange core.TextRange, indentation int, firstLineIsIndented bool, indentFinalLine bool) { // split comment in lines - startLine, _ := scanner.GetECMALineAndCharacterOfPosition(w.sourceFile, commentRange.Pos()) - endLine, _ := scanner.GetECMALineAndCharacterOfPosition(w.sourceFile, commentRange.End()) + startLine := scanner.GetECMALineOfPosition(w.sourceFile, commentRange.Pos()) + endLine := scanner.GetECMALineOfPosition(w.sourceFile, commentRange.End()) if startLine == endLine { if !firstLineIsIndented { @@ -1033,7 +1033,7 @@ func (w *formatSpanWorker) consumeTokenAndAdvanceScanner(currentTokenInfo tokenI if lineAction == LineActionNone { // indent token only if end line of previous range does not match start line of the token if savePreviousRange != NewTextRangeWithKind(0, 0, 0) { - prevEndLine, _ := scanner.GetECMALineAndCharacterOfPosition(w.sourceFile, savePreviousRange.Loc.End()) + prevEndLine := scanner.GetECMALineOfPosition(w.sourceFile, savePreviousRange.Loc.End()) indentToken = lastTriviaWasNewLine && tokenStartLine != prevEndLine } } else { diff --git a/pkg/format/util.go b/pkg/format/util.go index 98e919eda..1fc2275bd 100644 --- a/pkg/format/util.go +++ b/pkg/format/util.go @@ -10,8 +10,8 @@ import ( ) func rangeIsOnOneLine(node core.TextRange, file *ast.SourceFile) bool { - startLine, _ := scanner.GetECMALineAndCharacterOfPosition(file, node.Pos()) - endLine, _ := scanner.GetECMALineAndCharacterOfPosition(file, node.End()) + startLine := scanner.GetECMALineOfPosition(file, node.Pos()) + endLine := scanner.GetECMALineOfPosition(file, node.End()) return startLine == endLine } @@ -77,7 +77,7 @@ func getCloseTokenForOpenToken(kind ast.Kind) ast.Kind { func GetLineStartPositionForPosition(position int, sourceFile *ast.SourceFile) int { lineStarts := scanner.GetECMALineStarts(sourceFile) - line, _ := scanner.GetECMALineAndCharacterOfPosition(sourceFile, position) + line := scanner.GetECMALineOfPosition(sourceFile, position) return int(lineStarts[line]) } diff --git a/pkg/fourslash/_scripts/convertFourslash.mts b/pkg/fourslash/_scripts/convertFourslash.mts index dedcc358b..53c52829f 100644 --- a/pkg/fourslash/_scripts/convertFourslash.mts +++ b/pkg/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/pkg/fourslash/_scripts/failingTests.txt b/pkg/fourslash/_scripts/failingTests.txt index 29254c11a..7e671cbed 100644 --- a/pkg/fourslash/_scripts/failingTests.txt +++ b/pkg/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/pkg/fourslash/baselineutil.go b/pkg/fourslash/baselineutil.go index 01d86b186..8735e4849 100644 --- a/pkg/fourslash/baselineutil.go +++ b/pkg/fourslash/baselineutil.go @@ -58,8 +58,13 @@ func getBaselineExtension(command string) string { } } -func getBaselineOptions(command string) baseline.Options { +func getBaselineOptions(command string, testPath string) baseline.Options { subfolder := "fourslash/" + normalizeCommandName(command) + if !isSubmoduleTest(testPath) { + return baseline.Options{ + Subfolder: subfolder, + } + } switch command { case "Smart Selection": return baseline.Options{ @@ -229,6 +234,10 @@ func getBaselineOptions(command string) baseline.Options { } } +func isSubmoduleTest(testPath string) bool { + return strings.Contains(testPath, "fourslash/tests/gen") || strings.Contains(testPath, "fourslash/tests/manual") +} + func normalizeCommandName(command string) string { words := strings.Fields(command) command = strings.Join(words, "") diff --git a/pkg/fourslash/fourslash.go b/pkg/fourslash/fourslash.go index b949ee431..02156b19f 100644 --- a/pkg/fourslash/fourslash.go +++ b/pkg/fourslash/fourslash.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "maps" + "runtime" "slices" "strings" "testing" @@ -147,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*/)) @@ -203,9 +227,10 @@ func NewFourslash(t *testing.T, capabilities *lsproto.ClientCapabilities, conten } f.activeFilename = f.testData.Files[0].fileName + _, testPath, _, _ := runtime.Caller(1) t.Cleanup(func() { inputWriter.Close() - f.verifyBaselines(t) + f.verifyBaselines(t, testPath) }) return f } @@ -1030,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, @@ -2243,9 +2403,9 @@ func (f *FourslashTest) getRangeText(r *RangeMarker) string { return script.content[r.Range.Pos():r.Range.End()] } -func (f *FourslashTest) verifyBaselines(t *testing.T) { +func (f *FourslashTest) verifyBaselines(t *testing.T, testPath string) { for command, content := range f.baselines { - baseline.Run(t, getBaselineFileName(t, command), content.String(), getBaselineOptions(command)) + baseline.Run(t, getBaselineFileName(t, command), content.String(), getBaselineOptions(command, testPath)) } } diff --git a/pkg/fourslash/test_parser.go b/pkg/fourslash/test_parser.go index 94b5ff51d..36ca7d3c8 100644 --- a/pkg/fourslash/test_parser.go +++ b/pkg/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/pkg/fourslash/tests/gen/autoImportCompletionExportEqualsWithDefault1_test.go b/pkg/fourslash/tests/gen/autoImportCompletionExportEqualsWithDefault1_test.go index cb40ef662..d2ee1849b 100644 --- a/pkg/fourslash/tests/gen/autoImportCompletionExportEqualsWithDefault1_test.go +++ b/pkg/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/pkg/fourslash/tests/gen/autoImportCrossProject_paths_sharedOutDir_test.go b/pkg/fourslash/tests/gen/autoImportCrossProject_paths_sharedOutDir_test.go new file mode 100644 index 000000000..f18c104ed --- /dev/null +++ b/pkg/fourslash/tests/gen/autoImportCrossProject_paths_sharedOutDir_test.go @@ -0,0 +1,50 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/autoImportCrossProject_paths_stripSrc_test.go b/pkg/fourslash/tests/gen/autoImportCrossProject_paths_stripSrc_test.go new file mode 100644 index 000000000..ae76652ad --- /dev/null +++ b/pkg/fourslash/tests/gen/autoImportCrossProject_paths_stripSrc_test.go @@ -0,0 +1,60 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/autoImportCrossProject_paths_toDist_test.go b/pkg/fourslash/tests/gen/autoImportCrossProject_paths_toDist_test.go new file mode 100644 index 000000000..f3860aaee --- /dev/null +++ b/pkg/fourslash/tests/gen/autoImportCrossProject_paths_toDist_test.go @@ -0,0 +1,60 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/autoImportCrossProject_paths_toSrc_test.go b/pkg/fourslash/tests/gen/autoImportCrossProject_paths_toSrc_test.go new file mode 100644 index 000000000..eec7262eb --- /dev/null +++ b/pkg/fourslash/tests/gen/autoImportCrossProject_paths_toSrc_test.go @@ -0,0 +1,60 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/autoImportCrossProject_symlinks_stripSrc_test.go b/pkg/fourslash/tests/gen/autoImportCrossProject_symlinks_stripSrc_test.go new file mode 100644 index 000000000..983a9e031 --- /dev/null +++ b/pkg/fourslash/tests/gen/autoImportCrossProject_symlinks_stripSrc_test.go @@ -0,0 +1,49 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/autoImportCrossProject_symlinks_toDist_test.go b/pkg/fourslash/tests/gen/autoImportCrossProject_symlinks_toDist_test.go new file mode 100644 index 000000000..cdd0c8cd5 --- /dev/null +++ b/pkg/fourslash/tests/gen/autoImportCrossProject_symlinks_toDist_test.go @@ -0,0 +1,49 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/autoImportCrossProject_symlinks_toSrc_test.go b/pkg/fourslash/tests/gen/autoImportCrossProject_symlinks_toSrc_test.go new file mode 100644 index 000000000..a2ea563a2 --- /dev/null +++ b/pkg/fourslash/tests/gen/autoImportCrossProject_symlinks_toSrc_test.go @@ -0,0 +1,46 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/autoImportJsDocImport1_test.go b/pkg/fourslash/tests/gen/autoImportJsDocImport1_test.go new file mode 100644 index 000000000..aa0b0b65b --- /dev/null +++ b/pkg/fourslash/tests/gen/autoImportJsDocImport1_test.go @@ -0,0 +1,50 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/autoImportNodeNextJSRequire_test.go b/pkg/fourslash/tests/gen/autoImportNodeNextJSRequire_test.go new file mode 100644 index 000000000..8ce7bc899 --- /dev/null +++ b/pkg/fourslash/tests/gen/autoImportNodeNextJSRequire_test.go @@ -0,0 +1,35 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/autoImportPackageJsonFilterExistingImport2_test.go b/pkg/fourslash/tests/gen/autoImportPackageJsonFilterExistingImport2_test.go new file mode 100644 index 000000000..9f9232e4c --- /dev/null +++ b/pkg/fourslash/tests/gen/autoImportPackageJsonFilterExistingImport2_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/autoImportPackageJsonFilterExistingImport3_test.go b/pkg/fourslash/tests/gen/autoImportPackageJsonFilterExistingImport3_test.go new file mode 100644 index 000000000..fba6124c2 --- /dev/null +++ b/pkg/fourslash/tests/gen/autoImportPackageJsonFilterExistingImport3_test.go @@ -0,0 +1,34 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/autoImportPnpm_test.go b/pkg/fourslash/tests/gen/autoImportPnpm_test.go new file mode 100644 index 000000000..b729af1a5 --- /dev/null +++ b/pkg/fourslash/tests/gen/autoImportPnpm_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/autoImportProvider4_test.go b/pkg/fourslash/tests/gen/autoImportProvider4_test.go new file mode 100644 index 000000000..baf123d73 --- /dev/null +++ b/pkg/fourslash/tests/gen/autoImportProvider4_test.go @@ -0,0 +1,34 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/autoImportProvider5_test.go b/pkg/fourslash/tests/gen/autoImportProvider5_test.go new file mode 100644 index 000000000..100e9a684 --- /dev/null +++ b/pkg/fourslash/tests/gen/autoImportProvider5_test.go @@ -0,0 +1,34 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/autoImportProvider_pnpm_test.go b/pkg/fourslash/tests/gen/autoImportProvider_pnpm_test.go new file mode 100644 index 000000000..1972ca3d6 --- /dev/null +++ b/pkg/fourslash/tests/gen/autoImportProvider_pnpm_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/autoImportSortCaseSensitivity1_test.go b/pkg/fourslash/tests/gen/autoImportSortCaseSensitivity1_test.go new file mode 100644 index 000000000..b014d7e2d --- /dev/null +++ b/pkg/fourslash/tests/gen/autoImportSortCaseSensitivity1_test.go @@ -0,0 +1,54 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/autoImportSymlinkCaseSensitive_test.go b/pkg/fourslash/tests/gen/autoImportSymlinkCaseSensitive_test.go new file mode 100644 index 000000000..e4f1311fd --- /dev/null +++ b/pkg/fourslash/tests/gen/autoImportSymlinkCaseSensitive_test.go @@ -0,0 +1,31 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/autoImportTypeImport1_test.go b/pkg/fourslash/tests/gen/autoImportTypeImport1_test.go new file mode 100644 index 000000000..3def8823c --- /dev/null +++ b/pkg/fourslash/tests/gen/autoImportTypeImport1_test.go @@ -0,0 +1,42 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/autoImportTypeImport2_test.go b/pkg/fourslash/tests/gen/autoImportTypeImport2_test.go new file mode 100644 index 000000000..dd3ba3380 --- /dev/null +++ b/pkg/fourslash/tests/gen/autoImportTypeImport2_test.go @@ -0,0 +1,42 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/autoImportTypeImport3_test.go b/pkg/fourslash/tests/gen/autoImportTypeImport3_test.go new file mode 100644 index 000000000..5be7fcaee --- /dev/null +++ b/pkg/fourslash/tests/gen/autoImportTypeImport3_test.go @@ -0,0 +1,42 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/autoImportTypeImport4_test.go b/pkg/fourslash/tests/gen/autoImportTypeImport4_test.go new file mode 100644 index 000000000..fc78942a7 --- /dev/null +++ b/pkg/fourslash/tests/gen/autoImportTypeImport4_test.go @@ -0,0 +1,123 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/autoImportTypeImport5_test.go b/pkg/fourslash/tests/gen/autoImportTypeImport5_test.go new file mode 100644 index 000000000..171224522 --- /dev/null +++ b/pkg/fourslash/tests/gen/autoImportTypeImport5_test.go @@ -0,0 +1,108 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/autoImportTypeOnlyPreferred2_test.go b/pkg/fourslash/tests/gen/autoImportTypeOnlyPreferred2_test.go new file mode 100644 index 000000000..5cd7060ba --- /dev/null +++ b/pkg/fourslash/tests/gen/autoImportTypeOnlyPreferred2_test.go @@ -0,0 +1,50 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/autoImportsNodeNext1_test.go b/pkg/fourslash/tests/gen/autoImportsNodeNext1_test.go new file mode 100644 index 000000000..d8bd526f3 --- /dev/null +++ b/pkg/fourslash/tests/gen/autoImportsNodeNext1_test.go @@ -0,0 +1,47 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + . "github.com/buke/typescript-go-internal/pkg/fourslash/tests/util" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/completionForStringLiteralNonrelativeImport7_test.go b/pkg/fourslash/tests/gen/completionForStringLiteralNonrelativeImport7_test.go index 279e68565..5d62dacae 100644 --- a/pkg/fourslash/tests/gen/completionForStringLiteralNonrelativeImport7_test.go +++ b/pkg/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/pkg/fourslash/tests/gen/completionsImport_default_symbolName_test.go b/pkg/fourslash/tests/gen/completionsImport_default_symbolName_test.go index 21e0b9584..1d59204d6 100644 --- a/pkg/fourslash/tests/gen/completionsImport_default_symbolName_test.go +++ b/pkg/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/pkg/fourslash/tests/gen/completionsImport_exportEquals_anonymous_test.go b/pkg/fourslash/tests/gen/completionsImport_exportEquals_anonymous_test.go index 94970e84b..536d1d532 100644 --- a/pkg/fourslash/tests/gen/completionsImport_exportEquals_anonymous_test.go +++ b/pkg/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/pkg/fourslash/tests/gen/completionsImport_exportEquals_test.go b/pkg/fourslash/tests/gen/completionsImport_exportEquals_test.go index 29a091ee5..413045a5c 100644 --- a/pkg/fourslash/tests/gen/completionsImport_exportEquals_test.go +++ b/pkg/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/pkg/fourslash/tests/gen/completionsImport_importType_test.go b/pkg/fourslash/tests/gen/completionsImport_importType_test.go index 06bb5b9ac..1f522a510 100644 --- a/pkg/fourslash/tests/gen/completionsImport_importType_test.go +++ b/pkg/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/pkg/fourslash/tests/gen/completionsImport_named_namespaceImportExists_test.go b/pkg/fourslash/tests/gen/completionsImport_named_namespaceImportExists_test.go index 49e90304e..2bc94a9a6 100644 --- a/pkg/fourslash/tests/gen/completionsImport_named_namespaceImportExists_test.go +++ b/pkg/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/pkg/fourslash/tests/gen/completionsImport_weirdDefaultSynthesis_test.go b/pkg/fourslash/tests/gen/completionsImport_weirdDefaultSynthesis_test.go index 48e6b7fd9..0ea11bc61 100644 --- a/pkg/fourslash/tests/gen/completionsImport_weirdDefaultSynthesis_test.go +++ b/pkg/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/pkg/fourslash/tests/gen/importFixWithMultipleModuleExportAssignment_test.go b/pkg/fourslash/tests/gen/importFixWithMultipleModuleExportAssignment_test.go new file mode 100644 index 000000000..573b9e8c0 --- /dev/null +++ b/pkg/fourslash/tests/gen/importFixWithMultipleModuleExportAssignment_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importFixesGlobalTypingsCache_test.go b/pkg/fourslash/tests/gen/importFixesGlobalTypingsCache_test.go new file mode 100644 index 000000000..70ad94006 --- /dev/null +++ b/pkg/fourslash/tests/gen/importFixesGlobalTypingsCache_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importFixes_quotePreferenceDouble_importHelpers_test.go b/pkg/fourslash/tests/gen/importFixes_quotePreferenceDouble_importHelpers_test.go new file mode 100644 index 000000000..1d2277559 --- /dev/null +++ b/pkg/fourslash/tests/gen/importFixes_quotePreferenceDouble_importHelpers_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importFixes_quotePreferenceSingle_importHelpers_test.go b/pkg/fourslash/tests/gen/importFixes_quotePreferenceSingle_importHelpers_test.go new file mode 100644 index 000000000..28387e183 --- /dev/null +++ b/pkg/fourslash/tests/gen/importFixes_quotePreferenceSingle_importHelpers_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixConvertTypeOnly1_test.go b/pkg/fourslash/tests/gen/importNameCodeFixConvertTypeOnly1_test.go new file mode 100644 index 000000000..41ef1dcb3 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixConvertTypeOnly1_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixDefaultExport1_test.go b/pkg/fourslash/tests/gen/importNameCodeFixDefaultExport1_test.go new file mode 100644 index 000000000..9e5c777a8 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixDefaultExport1_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixDefaultExport2_test.go b/pkg/fourslash/tests/gen/importNameCodeFixDefaultExport2_test.go new file mode 100644 index 000000000..bf07ce333 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixDefaultExport2_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixDefaultExport3_test.go b/pkg/fourslash/tests/gen/importNameCodeFixDefaultExport3_test.go new file mode 100644 index 000000000..012161c7e --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixDefaultExport3_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixDefaultExport4_test.go b/pkg/fourslash/tests/gen/importNameCodeFixDefaultExport4_test.go new file mode 100644 index 000000000..a42abeaf9 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixDefaultExport4_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixDefaultExport5_test.go b/pkg/fourslash/tests/gen/importNameCodeFixDefaultExport5_test.go new file mode 100644 index 000000000..7f76d909a --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixDefaultExport5_test.go @@ -0,0 +1,27 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixDefaultExport7_test.go b/pkg/fourslash/tests/gen/importNameCodeFixDefaultExport7_test.go new file mode 100644 index 000000000..81c964a65 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixDefaultExport7_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixDefaultExport_test.go b/pkg/fourslash/tests/gen/importNameCodeFixDefaultExport_test.go new file mode 100644 index 000000000..e01d028aa --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixDefaultExport_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixExistingImport0_test.go b/pkg/fourslash/tests/gen/importNameCodeFixExistingImport0_test.go new file mode 100644 index 000000000..2bfb3c96b --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixExistingImport0_test.go @@ -0,0 +1,23 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixExistingImport10_test.go b/pkg/fourslash/tests/gen/importNameCodeFixExistingImport10_test.go new file mode 100644 index 000000000..e16d7fe6c --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixExistingImport10_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixExistingImport11_test.go b/pkg/fourslash/tests/gen/importNameCodeFixExistingImport11_test.go new file mode 100644 index 000000000..7af3d6a92 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixExistingImport11_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixExistingImport12_test.go b/pkg/fourslash/tests/gen/importNameCodeFixExistingImport12_test.go new file mode 100644 index 000000000..ab992bd6a --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixExistingImport12_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixExistingImport1_test.go b/pkg/fourslash/tests/gen/importNameCodeFixExistingImport1_test.go new file mode 100644 index 000000000..8e098e122 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixExistingImport1_test.go @@ -0,0 +1,24 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixExistingImport2_test.go b/pkg/fourslash/tests/gen/importNameCodeFixExistingImport2_test.go new file mode 100644 index 000000000..6bb53accd --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixExistingImport2_test.go @@ -0,0 +1,30 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixExistingImport3_test.go b/pkg/fourslash/tests/gen/importNameCodeFixExistingImport3_test.go new file mode 100644 index 000000000..298adc394 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixExistingImport3_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixExistingImport4_test.go b/pkg/fourslash/tests/gen/importNameCodeFixExistingImport4_test.go new file mode 100644 index 000000000..41c31adcb --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixExistingImport4_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixExistingImport5_test.go b/pkg/fourslash/tests/gen/importNameCodeFixExistingImport5_test.go new file mode 100644 index 000000000..536ee6483 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixExistingImport5_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixExistingImport6_test.go b/pkg/fourslash/tests/gen/importNameCodeFixExistingImport6_test.go new file mode 100644 index 000000000..96b0b9ef2 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixExistingImport6_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixExistingImport7_test.go b/pkg/fourslash/tests/gen/importNameCodeFixExistingImport7_test.go new file mode 100644 index 000000000..88b93b5ab --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixExistingImport7_test.go @@ -0,0 +1,23 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixExistingImport8_test.go b/pkg/fourslash/tests/gen/importNameCodeFixExistingImport8_test.go new file mode 100644 index 000000000..9ab265855 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixExistingImport8_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixExistingImport9_test.go b/pkg/fourslash/tests/gen/importNameCodeFixExistingImport9_test.go new file mode 100644 index 000000000..45984887f --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixExistingImport9_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixExistingImportEquals0_test.go b/pkg/fourslash/tests/gen/importNameCodeFixExistingImportEquals0_test.go new file mode 100644 index 000000000..c45a70f38 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixExistingImportEquals0_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixExportAsDefaultExistingImport_test.go b/pkg/fourslash/tests/gen/importNameCodeFixExportAsDefaultExistingImport_test.go new file mode 100644 index 000000000..9084d859f --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixExportAsDefaultExistingImport_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixIndentedIdentifier_test.go b/pkg/fourslash/tests/gen/importNameCodeFixIndentedIdentifier_test.go new file mode 100644 index 000000000..30cf88cac --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixIndentedIdentifier_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports0_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports0_test.go new file mode 100644 index 000000000..ad92b7993 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports0_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports1_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports1_test.go new file mode 100644 index 000000000..49b9231d1 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports1_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports2_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports2_test.go new file mode 100644 index 000000000..007ce54af --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports2_test.go @@ -0,0 +1,30 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports3_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports3_test.go new file mode 100644 index 000000000..8a9a64cc7 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports3_test.go @@ -0,0 +1,30 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports4_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports4_test.go new file mode 100644 index 000000000..67a86b2d9 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports4_test.go @@ -0,0 +1,30 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports5_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports5_test.go new file mode 100644 index 000000000..eff1f5a79 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportAllowSyntheticDefaultImports5_test.go @@ -0,0 +1,30 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportAmbient0_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportAmbient0_test.go new file mode 100644 index 000000000..ec675eb9c --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportAmbient0_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportAmbient1_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportAmbient1_test.go new file mode 100644 index 000000000..9b3535358 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportAmbient1_test.go @@ -0,0 +1,38 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportAmbient2_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportAmbient2_test.go new file mode 100644 index 000000000..725bc6cd7 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportAmbient2_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportAmbient3_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportAmbient3_test.go new file mode 100644 index 000000000..352d8d98f --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportAmbient3_test.go @@ -0,0 +1,40 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportBaseUrl0_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportBaseUrl0_test.go new file mode 100644 index 000000000..125c1c31a --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportBaseUrl0_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportBaseUrl1_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportBaseUrl1_test.go new file mode 100644 index 000000000..21f319ec6 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportBaseUrl1_test.go @@ -0,0 +1,36 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportBaseUrl2_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportBaseUrl2_test.go new file mode 100644 index 000000000..84f1904b3 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportBaseUrl2_test.go @@ -0,0 +1,36 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportDefault0_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportDefault0_test.go new file mode 100644 index 000000000..175cfdcb4 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportDefault0_test.go @@ -0,0 +1,23 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportExportEqualsCommonJSInteropOn_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportExportEqualsCommonJSInteropOn_test.go new file mode 100644 index 000000000..5e8f9d663 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportExportEqualsCommonJSInteropOn_test.go @@ -0,0 +1,62 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportExportEqualsESNextInteropOff_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportExportEqualsESNextInteropOff_test.go new file mode 100644 index 000000000..e40c66a70 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportExportEqualsESNextInteropOff_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportExportEqualsESNextInteropOn_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportExportEqualsESNextInteropOn_test.go new file mode 100644 index 000000000..492f7e932 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportExportEqualsESNextInteropOn_test.go @@ -0,0 +1,30 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportFile0_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFile0_test.go new file mode 100644 index 000000000..35f438bc2 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFile0_test.go @@ -0,0 +1,24 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportFile1_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFile1_test.go new file mode 100644 index 000000000..430bd91bc --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFile1_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportFile2_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFile2_test.go new file mode 100644 index 000000000..1a50b5ed8 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFile2_test.go @@ -0,0 +1,24 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportFile3_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFile3_test.go new file mode 100644 index 000000000..ec74f7601 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFile3_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportFile4_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFile4_test.go new file mode 100644 index 000000000..7563ebeb0 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFile4_test.go @@ -0,0 +1,27 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportFileAllComments_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFileAllComments_test.go new file mode 100644 index 000000000..4df41186b --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFileAllComments_test.go @@ -0,0 +1,47 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportFileDetachedComments_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFileDetachedComments_test.go new file mode 100644 index 000000000..307cda1af --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFileDetachedComments_test.go @@ -0,0 +1,34 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyle0_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyle0_test.go new file mode 100644 index 000000000..806264e02 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyle0_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyle1_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyle1_test.go new file mode 100644 index 000000000..4e8b6883b --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyle1_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyle2_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyle2_test.go new file mode 100644 index 000000000..4582b81f0 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyle2_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyle3_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyle3_test.go new file mode 100644 index 000000000..1d5769ab7 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyle3_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyleMixed0_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyleMixed0_test.go new file mode 100644 index 000000000..1b6226356 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyleMixed0_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyleMixed1_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyleMixed1_test.go new file mode 100644 index 000000000..0fe207624 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFileQuoteStyleMixed1_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportFromAtTypesScopedPackage_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFromAtTypesScopedPackage_test.go new file mode 100644 index 000000000..ede7aa73b --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFromAtTypesScopedPackage_test.go @@ -0,0 +1,24 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportFromAtTypes_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFromAtTypes_test.go new file mode 100644 index 000000000..5190d3f6e --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportFromAtTypes_test.go @@ -0,0 +1,24 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportIndex_notForClassicResolution_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportIndex_notForClassicResolution_test.go new file mode 100644 index 000000000..c95838662 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportIndex_notForClassicResolution_test.go @@ -0,0 +1,37 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportIndex_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportIndex_test.go new file mode 100644 index 000000000..f8e79fecc --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportIndex_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportNodeModules0_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportNodeModules0_test.go new file mode 100644 index 000000000..77d589e54 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportNodeModules0_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportNodeModules1_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportNodeModules1_test.go new file mode 100644 index 000000000..2e0cf8f5d --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportNodeModules1_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportNodeModules2_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportNodeModules2_test.go new file mode 100644 index 000000000..9d03251fd --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportNodeModules2_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportNodeModules3_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportNodeModules3_test.go new file mode 100644 index 000000000..3741dc05b --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportNodeModules3_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportNodeModules4_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportNodeModules4_test.go new file mode 100644 index 000000000..b7ce4b400 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportNodeModules4_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportNodeModules6_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportNodeModules6_test.go new file mode 100644 index 000000000..97e76b10a --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportNodeModules6_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportNodeModules7_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportNodeModules7_test.go new file mode 100644 index 000000000..5faa1dd41 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportNodeModules7_test.go @@ -0,0 +1,30 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportNodeModules8_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportNodeModules8_test.go new file mode 100644 index 000000000..8e7c3b0fe --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportNodeModules8_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportPaths0_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportPaths0_test.go new file mode 100644 index 000000000..faef469de --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportPaths0_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportPaths1_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportPaths1_test.go new file mode 100644 index 000000000..4e831c966 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportPaths1_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportPaths2_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportPaths2_test.go new file mode 100644 index 000000000..fe63c798d --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportPaths2_test.go @@ -0,0 +1,37 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportPaths_withExtension_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportPaths_withExtension_test.go new file mode 100644 index 000000000..09d611748 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportPaths_withExtension_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportPaths_withLeadingDotSlash_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportPaths_withLeadingDotSlash_test.go new file mode 100644 index 000000000..83d46d7d2 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportPaths_withLeadingDotSlash_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportPaths_withParentRelativePath_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportPaths_withParentRelativePath_test.go new file mode 100644 index 000000000..ff0ee4bb1 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportPaths_withParentRelativePath_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportRootDirs0_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportRootDirs0_test.go new file mode 100644 index 000000000..79fee89d4 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportRootDirs0_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportRootDirs1_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportRootDirs1_test.go new file mode 100644 index 000000000..3d8cf6e46 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportRootDirs1_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportTypeRoots0_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportTypeRoots0_test.go new file mode 100644 index 000000000..0b890d6c7 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportTypeRoots0_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixNewImportTypeRoots1_test.go b/pkg/fourslash/tests/gen/importNameCodeFixNewImportTypeRoots1_test.go new file mode 100644 index 000000000..fdb4606fa --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixNewImportTypeRoots1_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixOptionalImport0_test.go b/pkg/fourslash/tests/gen/importNameCodeFixOptionalImport0_test.go new file mode 100644 index 000000000..cd19c20ff --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixOptionalImport0_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixOptionalImport1_test.go b/pkg/fourslash/tests/gen/importNameCodeFixOptionalImport1_test.go new file mode 100644 index 000000000..5eed61831 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixOptionalImport1_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixShebang_test.go b/pkg/fourslash/tests/gen/importNameCodeFixShebang_test.go new file mode 100644 index 000000000..7cb1598c1 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixShebang_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixUMDGlobal0_test.go b/pkg/fourslash/tests/gen/importNameCodeFixUMDGlobal0_test.go new file mode 100644 index 000000000..fab51d8f1 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixUMDGlobal0_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixUMDGlobal1_test.go b/pkg/fourslash/tests/gen/importNameCodeFixUMDGlobal1_test.go new file mode 100644 index 000000000..800052baa --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixUMDGlobal1_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixUMDGlobalJavaScript_test.go b/pkg/fourslash/tests/gen/importNameCodeFixUMDGlobalJavaScript_test.go new file mode 100644 index 000000000..b140f84dd --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixUMDGlobalJavaScript_test.go @@ -0,0 +1,31 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixUMDGlobalReact0_test.go b/pkg/fourslash/tests/gen/importNameCodeFixUMDGlobalReact0_test.go new file mode 100644 index 000000000..a9b924a2e --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixUMDGlobalReact0_test.go @@ -0,0 +1,50 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixUMDGlobalReact1_test.go b/pkg/fourslash/tests/gen/importNameCodeFixUMDGlobalReact1_test.go new file mode 100644 index 000000000..fc183e9de --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixUMDGlobalReact1_test.go @@ -0,0 +1,41 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFixUMDGlobalReact2_test.go b/pkg/fourslash/tests/gen/importNameCodeFixUMDGlobalReact2_test.go new file mode 100644 index 000000000..37d9d5821 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFixUMDGlobalReact2_test.go @@ -0,0 +1,32 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_HeaderComment1_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_HeaderComment1_test.go new file mode 100644 index 000000000..c3da7e331 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_HeaderComment1_test.go @@ -0,0 +1,36 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_HeaderComment2_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_HeaderComment2_test.go new file mode 100644 index 000000000..67df4fc9c --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_HeaderComment2_test.go @@ -0,0 +1,42 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_avoidRelativeNodeModules_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_avoidRelativeNodeModules_test.go new file mode 100644 index 000000000..0d5dfe57e --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_avoidRelativeNodeModules_test.go @@ -0,0 +1,34 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_commonjs_allowSynthetic_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_commonjs_allowSynthetic_test.go new file mode 100644 index 000000000..88e2221d3 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_commonjs_allowSynthetic_test.go @@ -0,0 +1,35 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_defaultExport_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_defaultExport_test.go new file mode 100644 index 000000000..d7eac1599 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_defaultExport_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_dollar_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_dollar_test.go new file mode 100644 index 000000000..c52b49e26 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_dollar_test.go @@ -0,0 +1,27 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_fileWithNoTrailingNewline_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_fileWithNoTrailingNewline_test.go new file mode 100644 index 000000000..2b8717545 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_fileWithNoTrailingNewline_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_fromPathMapping_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_fromPathMapping_test.go new file mode 100644 index 000000000..6c6a560ee --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_fromPathMapping_test.go @@ -0,0 +1,34 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_getCanonicalFileName_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_getCanonicalFileName_test.go new file mode 100644 index 000000000..9a0039ef1 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_getCanonicalFileName_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_importType1_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_importType1_test.go new file mode 100644 index 000000000..2520953e2 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_importType1_test.go @@ -0,0 +1,37 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_importType2_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_importType2_test.go new file mode 100644 index 000000000..09dc827f8 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_importType2_test.go @@ -0,0 +1,57 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_importType3_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_importType3_test.go new file mode 100644 index 000000000..a5dd18aa8 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_importType3_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_importType4_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_importType4_test.go new file mode 100644 index 000000000..5c1517a4a --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_importType4_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_importType5_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_importType5_test.go new file mode 100644 index 000000000..80e5f503d --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_importType5_test.go @@ -0,0 +1,27 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_importType6_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_importType6_test.go new file mode 100644 index 000000000..f494dda8b --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_importType6_test.go @@ -0,0 +1,30 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_importType7_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_importType7_test.go new file mode 100644 index 000000000..b25e9a9b7 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_importType7_test.go @@ -0,0 +1,54 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_importType8_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_importType8_test.go new file mode 100644 index 000000000..8bf36bbc6 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_importType8_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_importType_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_importType_test.go new file mode 100644 index 000000000..48843834a --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_importType_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_jsCJSvsESM1_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_jsCJSvsESM1_test.go new file mode 100644 index 000000000..a54b24655 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_jsCJSvsESM1_test.go @@ -0,0 +1,29 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_jsCJSvsESM2_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_jsCJSvsESM2_test.go new file mode 100644 index 000000000..75563508a --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_jsCJSvsESM2_test.go @@ -0,0 +1,31 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_jsCJSvsESM3_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_jsCJSvsESM3_test.go new file mode 100644 index 000000000..576a2834e --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_jsCJSvsESM3_test.go @@ -0,0 +1,34 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_jsx1_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_jsx1_test.go new file mode 100644 index 000000000..9510af60a --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_jsx1_test.go @@ -0,0 +1,42 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_jsxOpeningTagImportDefault_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_jsxOpeningTagImportDefault_test.go new file mode 100644 index 000000000..be0cd89b5 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_jsxOpeningTagImportDefault_test.go @@ -0,0 +1,31 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_jsxReact17_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_jsxReact17_test.go new file mode 100644 index 000000000..02b16e7cf --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_jsxReact17_test.go @@ -0,0 +1,41 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_order2_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_order2_test.go new file mode 100644 index 000000000..2b3b62d00 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_order2_test.go @@ -0,0 +1,54 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_order_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_order_test.go new file mode 100644 index 000000000..0684246db --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_order_test.go @@ -0,0 +1,31 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_pathsWithoutBaseUrl1_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_pathsWithoutBaseUrl1_test.go new file mode 100644 index 000000000..57d5aa204 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_pathsWithoutBaseUrl1_test.go @@ -0,0 +1,34 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_pathsWithoutBaseUrl2_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_pathsWithoutBaseUrl2_test.go new file mode 100644 index 000000000..6a5bf16c6 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_pathsWithoutBaseUrl2_test.go @@ -0,0 +1,34 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_pnpm1_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_pnpm1_test.go new file mode 100644 index 000000000..c69051e81 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_pnpm1_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_preferBaseUrl_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_preferBaseUrl_test.go new file mode 100644 index 000000000..fba93ccdf --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_preferBaseUrl_test.go @@ -0,0 +1,27 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_quoteStyle_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_quoteStyle_test.go new file mode 100644 index 000000000..afacd3b6f --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_quoteStyle_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/ls/lsutil" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_reExportDefault_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_reExportDefault_test.go new file mode 100644 index 000000000..3fb8c1bf6 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_reExportDefault_test.go @@ -0,0 +1,54 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_reExport_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_reExport_test.go new file mode 100644 index 000000000..f7e672fdd --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_reExport_test.go @@ -0,0 +1,30 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_shorthandPropertyAssignment1_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_shorthandPropertyAssignment1_test.go new file mode 100644 index 000000000..67f0ebfce --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_shorthandPropertyAssignment1_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_shorthandPropertyAssignment2_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_shorthandPropertyAssignment2_test.go new file mode 100644 index 000000000..6cfdb0caf --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_shorthandPropertyAssignment2_test.go @@ -0,0 +1,26 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_sortByDistance_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_sortByDistance_test.go new file mode 100644 index 000000000..30b4dbe49 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_sortByDistance_test.go @@ -0,0 +1,41 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_symlink_own_package_2_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_symlink_own_package_2_test.go new file mode 100644 index 000000000..27a9eb590 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_symlink_own_package_2_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_symlink_own_package_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_symlink_own_package_test.go new file mode 100644 index 000000000..0bb1e5af8 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_symlink_own_package_test.go @@ -0,0 +1,31 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_symlink_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_symlink_test.go new file mode 100644 index 000000000..44ed2cddd --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_symlink_test.go @@ -0,0 +1,33 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_trailingComma_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_trailingComma_test.go new file mode 100644 index 000000000..92be5dfa8 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_trailingComma_test.go @@ -0,0 +1,35 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_tripleSlashOrdering_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_tripleSlashOrdering_test.go new file mode 100644 index 000000000..db31392f5 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_tripleSlashOrdering_test.go @@ -0,0 +1,119 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_typeOnly_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_typeOnly_test.go new file mode 100644 index 000000000..f95b03628 --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_typeOnly_test.go @@ -0,0 +1,27 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_typeUsedAsValue_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_typeUsedAsValue_test.go new file mode 100644 index 000000000..03b78e39e --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_typeUsedAsValue_test.go @@ -0,0 +1,25 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importNameCodeFix_withJson_test.go b/pkg/fourslash/tests/gen/importNameCodeFix_withJson_test.go new file mode 100644 index 000000000..128403fba --- /dev/null +++ b/pkg/fourslash/tests/gen/importNameCodeFix_withJson_test.go @@ -0,0 +1,28 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/importTypeCompletions5_test.go b/pkg/fourslash/tests/gen/importTypeCompletions5_test.go index 06b5f3d6e..5f1e30fd1 100644 --- a/pkg/fourslash/tests/gen/importTypeCompletions5_test.go +++ b/pkg/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/pkg/fourslash/tests/gen/javascriptModules21_test.go b/pkg/fourslash/tests/gen/javascriptModules21_test.go index 83f8253a8..fe8c83752 100644 --- a/pkg/fourslash/tests/gen/javascriptModules21_test.go +++ b/pkg/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/pkg/fourslash/tests/gen/moduleNodeNextAutoImport1_test.go b/pkg/fourslash/tests/gen/moduleNodeNextAutoImport1_test.go new file mode 100644 index 000000000..47650d28d --- /dev/null +++ b/pkg/fourslash/tests/gen/moduleNodeNextAutoImport1_test.go @@ -0,0 +1,31 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/moduleNodeNextAutoImport2_test.go b/pkg/fourslash/tests/gen/moduleNodeNextAutoImport2_test.go new file mode 100644 index 000000000..a1a839a9e --- /dev/null +++ b/pkg/fourslash/tests/gen/moduleNodeNextAutoImport2_test.go @@ -0,0 +1,31 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/moduleNodeNextAutoImport3_test.go b/pkg/fourslash/tests/gen/moduleNodeNextAutoImport3_test.go new file mode 100644 index 000000000..a00c69ed5 --- /dev/null +++ b/pkg/fourslash/tests/gen/moduleNodeNextAutoImport3_test.go @@ -0,0 +1,31 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/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/pkg/fourslash/tests/gen/tsxCompletion14_test.go b/pkg/fourslash/tests/gen/tsxCompletion14_test.go index 01244a680..15d28ec02 100644 --- a/pkg/fourslash/tests/gen/tsxCompletion14_test.go +++ b/pkg/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/pkg/fourslash/tests/gen/tsxCompletion15_test.go b/pkg/fourslash/tests/gen/tsxCompletion15_test.go index 27dd1a397..c24650f58 100644 --- a/pkg/fourslash/tests/gen/tsxCompletion15_test.go +++ b/pkg/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/pkg/fourslash/tests/inlayHintsTupleTypeCrash_test.go b/pkg/fourslash/tests/inlayHintsTupleTypeCrash_test.go new file mode 100644 index 000000000..e75542e5e --- /dev/null +++ b/pkg/fourslash/tests/inlayHintsTupleTypeCrash_test.go @@ -0,0 +1,22 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/ls/lsutil" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestInlayHintsTupleTypeCrash(t *testing.T) { + t.Parallel() + + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `function iterateTuples(tuples: [string][]): void { + tuples.forEach((l) => {}) +}` + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyBaselineInlayHints(t, nil /*span*/, &lsutil.UserPreferences{ + IncludeInlayFunctionParameterTypeHints: true, + }) +} diff --git a/pkg/ls/autoimportfixes.go b/pkg/ls/autoimportfixes.go index e78ed3cb9..5ace63874 100644 --- a/pkg/ls/autoimportfixes.go +++ b/pkg/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/pkg/ls/autoimports.go b/pkg/ls/autoimports.go index a5f86a044..0c251404d 100644 --- a/pkg/ls/autoimports.go +++ b/pkg/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/pkg/ls/autoimportsexportinfo.go b/pkg/ls/autoimportsexportinfo.go index 8116624db..af9197e14 100644 --- a/pkg/ls/autoimportsexportinfo.go +++ b/pkg/ls/autoimportsexportinfo.go @@ -11,7 +11,7 @@ import ( "github.com/buke/typescript-go-internal/pkg/stringutil" ) -func (l *LanguageService) getExportInfos( +func (l *LanguageService) getExportInfoMap( ctx context.Context, ch *checker.Checker, importingFile *ast.SourceFile, diff --git a/pkg/ls/change/delete.go b/pkg/ls/change/delete.go new file mode 100644 index 000000000..e60fe5cb9 --- /dev/null +++ b/pkg/ls/change/delete.go @@ -0,0 +1,270 @@ +package change + +import ( + "slices" + + "github.com/buke/typescript-go-internal/pkg/ast" + "github.com/buke/typescript-go-internal/pkg/astnav" + "github.com/buke/typescript-go-internal/pkg/core" + "github.com/buke/typescript-go-internal/pkg/debug" + "github.com/buke/typescript-go-internal/pkg/format" + "github.com/buke/typescript-go-internal/pkg/lsp/lsproto" + "github.com/buke/typescript-go-internal/pkg/scanner" + "github.com/buke/typescript-go-internal/pkg/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/pkg/ls/change/tracker.go b/pkg/ls/change/tracker.go index a28aaf801..780bbd9ae 100644 --- a/pkg/ls/change/tracker.go +++ b/pkg/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/pkg/ls/change/trackerimpl.go b/pkg/ls/change/trackerimpl.go index 901e763f0..35e133a37 100644 --- a/pkg/ls/change/trackerimpl.go +++ b/pkg/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/pkg/ls/codeactions.go b/pkg/ls/codeactions.go new file mode 100644 index 000000000..dc426dec7 --- /dev/null +++ b/pkg/ls/codeactions.go @@ -0,0 +1,126 @@ +package ls + +import ( + "context" + "slices" + + "github.com/buke/typescript-go-internal/pkg/ast" + "github.com/buke/typescript-go-internal/pkg/checker" + "github.com/buke/typescript-go-internal/pkg/compiler" + "github.com/buke/typescript-go-internal/pkg/core" + "github.com/buke/typescript-go-internal/pkg/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/pkg/ls/codeactions_importfixes.go b/pkg/ls/codeactions_importfixes.go new file mode 100644 index 000000000..407ec8775 --- /dev/null +++ b/pkg/ls/codeactions_importfixes.go @@ -0,0 +1,640 @@ +package ls + +import ( + "cmp" + "context" + "fmt" + "slices" + "strings" + + "github.com/buke/typescript-go-internal/pkg/ast" + "github.com/buke/typescript-go-internal/pkg/astnav" + "github.com/buke/typescript-go-internal/pkg/checker" + "github.com/buke/typescript-go-internal/pkg/collections" + "github.com/buke/typescript-go-internal/pkg/compiler" + "github.com/buke/typescript-go-internal/pkg/core" + "github.com/buke/typescript-go-internal/pkg/diagnostics" + "github.com/buke/typescript-go-internal/pkg/ls/change" + "github.com/buke/typescript-go-internal/pkg/ls/lsutil" + "github.com/buke/typescript-go-internal/pkg/ls/organizeimports" + "github.com/buke/typescript-go-internal/pkg/lsp/lsproto" + "github.com/buke/typescript-go-internal/pkg/outputpaths" + "github.com/buke/typescript-go-internal/pkg/scanner" + "github.com/buke/typescript-go-internal/pkg/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/pkg/ls/completions.go b/pkg/ls/completions.go index 3fee14815..8b76c3a37 100644 --- a/pkg/ls/completions.go +++ b/pkg/ls/completions.go @@ -2518,7 +2518,7 @@ func boolToPtr(v bool) *bool { } func getLineOfPosition(file *ast.SourceFile, pos int) int { - line, _ := scanner.GetECMALineAndCharacterOfPosition(file, pos) + line := scanner.GetECMALineOfPosition(file, pos) return line } @@ -3515,8 +3515,8 @@ func getContextualKeywords(file *ast.SourceFile, contextToken *ast.Node, positio // Source: https://tc39.es/proposal-import-assertions/ if contextToken != nil { parent := contextToken.Parent - tokenLine, _ := scanner.GetECMALineAndCharacterOfPosition(file, contextToken.End()) - currentLine, _ := scanner.GetECMALineAndCharacterOfPosition(file, position) + tokenLine := scanner.GetECMALineOfPosition(file, contextToken.End()) + currentLine := scanner.GetECMALineOfPosition(file, position) if (ast.IsImportDeclaration(parent) || ast.IsExportDeclaration(parent) && parent.ModuleSpecifier() != nil) && contextToken == parent.ModuleSpecifier() && diff --git a/pkg/ls/lsutil/asi.go b/pkg/ls/lsutil/asi.go index 52032cea2..9185df266 100644 --- a/pkg/ls/lsutil/asi.go +++ b/pkg/ls/lsutil/asi.go @@ -98,7 +98,7 @@ func NodeIsASICandidate(node *ast.Node, file *ast.SourceFile) bool { return true } - startLine, _ := scanner.GetECMALineAndCharacterOfPosition(file, node.End()) - endLine, _ := scanner.GetECMALineAndCharacterOfPosition(file, astnav.GetStartOfNode(nextToken, file, false /*includeJSDoc*/)) + startLine := scanner.GetECMALineOfPosition(file, node.End()) + endLine := scanner.GetECMALineOfPosition(file, astnav.GetStartOfNode(nextToken, file, false /*includeJSDoc*/)) return startLine != endLine } diff --git a/pkg/ls/lsutil/utilities.go b/pkg/ls/lsutil/utilities.go index 0d1e98143..7b4833d35 100644 --- a/pkg/ls/lsutil/utilities.go +++ b/pkg/ls/lsutil/utilities.go @@ -32,10 +32,10 @@ func ProbablyUsesSemicolons(file *ast.SourceFile) bool { if lastToken != nil && lastToken.Kind == ast.KindSemicolonToken { withSemicolon++ } else if lastToken != nil && lastToken.Kind != ast.KindCommaToken { - lastTokenLine, _ := scanner.GetECMALineAndCharacterOfPosition( + lastTokenLine := scanner.GetECMALineOfPosition( file, astnav.GetStartOfNode(lastToken, file, false /*includeJSDoc*/)) - nextTokenLine, _ := scanner.GetECMALineAndCharacterOfPosition( + nextTokenLine := scanner.GetECMALineOfPosition( file, scanner.GetRangeOfTokenAtPosition(file, lastToken.End()).Pos()) // Avoid counting missing semicolon in single-line objects: diff --git a/pkg/ls/utilities.go b/pkg/ls/utilities.go index 39abf95ae..3c21cfc61 100644 --- a/pkg/ls/utilities.go +++ b/pkg/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/pkg/lsp/server.go b/pkg/lsp/server.go index b42afb1ae..e145a77e0 100644 --- a/pkg/lsp/server.go +++ b/pkg/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/pkg/parser/reparser.go b/pkg/parser/reparser.go index 9611028a3..aa7ab1332 100644 --- a/pkg/parser/reparser.go +++ b/pkg/parser/reparser.go @@ -29,12 +29,9 @@ func (p *Parser) reparseCommonJS(node *ast.Node, jsdoc []*ast.Node) { case ast.JSDeclarationKindModuleExports: export = p.factory.NewJSExportAssignment(nil, p.factory.DeepCloneReparse(bin.Right)) case ast.JSDeclarationKindExportsProperty: - mod := p.factory.NewModifier(ast.KindExportKeyword) - mod.Flags = p.contextFlags | ast.NodeFlagsReparsed - mod.Loc = bin.Loc // TODO: Name can sometimes be a string literal, so downstream code needs to handle this export = p.factory.NewCommonJSExport( - p.newModifierList(bin.Loc, p.nodeSlicePool.NewSlice1(mod)), + nil, p.factory.DeepCloneReparse(ast.GetElementOrPropertyAccessName(bin.Left)), nil, /*typeNode*/ p.factory.DeepCloneReparse(bin.Right)) @@ -93,14 +90,8 @@ func (p *Parser) reparseUnhosted(tag *ast.Node, parent *ast.Node, jsDoc *ast.Nod if callbackTag.TypeExpression == nil { break } - - export := p.factory.NewModifier(ast.KindExportKeyword) - export.Loc = tag.Loc - export.Flags = p.contextFlags | ast.NodeFlagsReparsed - modifiers := p.newModifierList(export.Loc, p.nodeSlicePool.NewSlice1(export)) functionType := p.reparseJSDocSignature(callbackTag.TypeExpression, tag, jsDoc, tag, nil) - - typeAlias := p.factory.NewJSTypeAliasDeclaration(modifiers, p.factory.DeepCloneReparse(callbackTag.FullName), nil, functionType) + typeAlias := p.factory.NewJSTypeAliasDeclaration(nil, p.factory.DeepCloneReparse(callbackTag.FullName), nil, functionType) typeAlias.AsTypeAliasDeclaration().TypeParameters = p.gatherTypeParameters(jsDoc, tag) p.finishReparsedNode(typeAlias, tag) p.reparseList = append(p.reparseList, typeAlias) diff --git a/pkg/scanner/scanner.go b/pkg/scanner/scanner.go index c1cffc062..14e89674c 100644 --- a/pkg/scanner/scanner.go +++ b/pkg/scanner/scanner.go @@ -2337,8 +2337,8 @@ func getErrorRangeForArrowFunction(sourceFile *ast.SourceFile, node *ast.Node) c pos := SkipTrivia(sourceFile.Text(), node.Pos()) body := node.Body() if body != nil && body.Kind == ast.KindBlock { - startLine, _ := GetECMALineAndCharacterOfPosition(sourceFile, body.Pos()) - endLine, _ := GetECMALineAndCharacterOfPosition(sourceFile, body.End()) + startLine := GetECMALineOfPosition(sourceFile, body.Pos()) + endLine := GetECMALineOfPosition(sourceFile, body.End()) if startLine < endLine { // The arrow function spans multiple lines, // make the error span be the first line, inclusive. @@ -2434,9 +2434,15 @@ func GetECMALineStarts(sourceFile ast.SourceFileLike) []core.TextPos { return sourceFile.ECMALineMap() } +func GetECMALineOfPosition(sourceFile ast.SourceFileLike, pos int) int { + lineMap := GetECMALineStarts(sourceFile) + return ComputeLineOfPosition(lineMap, pos) +} + func GetECMALineAndCharacterOfPosition(sourceFile ast.SourceFileLike, pos int) (line int, character int) { lineMap := GetECMALineStarts(sourceFile) line = ComputeLineOfPosition(lineMap, pos) + // !!! TODO: this is suspect; these are rune counts, not UTF-8 _or_ UTF-16 offsets. character = utf8.RuneCountInString(sourceFile.Text()[lineMap[line]:pos]) return line, character } diff --git a/pkg/testrunner/test_case_parser.go b/pkg/testrunner/test_case_parser.go index 0f067854f..1717719c7 100644 --- a/pkg/testrunner/test_case_parser.go +++ b/pkg/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 diff --git a/pkg/testutil/tsbaseline/type_symbol_baseline.go b/pkg/testutil/tsbaseline/type_symbol_baseline.go index 118c4bf92..d104d3486 100644 --- a/pkg/testutil/tsbaseline/type_symbol_baseline.go +++ b/pkg/testutil/tsbaseline/type_symbol_baseline.go @@ -342,7 +342,7 @@ func forEachASTNode(node *ast.Node) []*ast.Node { func (walker *typeWriterWalker) writeTypeOrSymbol(node *ast.Node, isSymbolWalk bool) *typeWriterResult { actualPos := scanner.SkipTrivia(walker.currentSourceFile.Text(), node.Pos()) - line, _ := scanner.GetECMALineAndCharacterOfPosition(walker.currentSourceFile, actualPos) + line := scanner.GetECMALineOfPosition(walker.currentSourceFile, actualPos) sourceText := scanner.GetSourceTextOfNodeFromSourceFile(walker.currentSourceFile, node, false /*includeTrivia*/) fileChecker, done := walker.getTypeCheckerForCurrentFile() defer done() diff --git a/pkg/transformers/utilities.go b/pkg/transformers/utilities.go index 973f265ab..591787cc9 100644 --- a/pkg/transformers/utilities.go +++ b/pkg/transformers/utilities.go @@ -253,8 +253,8 @@ func IsOriginalNodeSingleLine(emitContext *printer.EmitContext, node *ast.Node) if source == nil { return false } - startLine, _ := scanner.GetECMALineAndCharacterOfPosition(source, original.Loc.Pos()) - endLine, _ := scanner.GetECMALineAndCharacterOfPosition(source, original.Loc.End()) + startLine := scanner.GetECMALineOfPosition(source, original.Loc.Pos()) + endLine := scanner.GetECMALineOfPosition(source, original.Loc.End()) return startLine == endLine } diff --git a/pkg/tsoptions/commandlineparser.go b/pkg/tsoptions/commandlineparser.go index b624fba96..1f34ec711 100644 --- a/pkg/tsoptions/commandlineparser.go +++ b/pkg/tsoptions/commandlineparser.go @@ -46,7 +46,7 @@ func ParseCommandLine( commandLine = []string{} } parser := parseCommandLineWorker(CompilerOptionsDidYouMeanDiagnostics, commandLine, host.FS()) - optionsWithAbsolutePaths := convertToOptionsWithAbsolutePaths(parser.options, CommandLineCompilerOptionsMap, host.GetCurrentDirectory()) + optionsWithAbsolutePaths := convertToOptionsWithAbsolutePaths(parser.options.Clone(), CommandLineCompilerOptionsMap, host.GetCurrentDirectory()) compilerOptions := convertMapToOptions(optionsWithAbsolutePaths, &compilerOptionsParser{&core.CompilerOptions{}}).CompilerOptions watchOptions := convertMapToOptions(optionsWithAbsolutePaths, &watchOptionsParser{&core.WatchOptions{}}).WatchOptions result := NewParsedCommandLine(compilerOptions, parser.fileNames, tspath.ComparePathsOptions{ diff --git a/testdata/baselines/reference/compiler/jsDocCallbackExport1.js b/testdata/baselines/reference/compiler/jsDocCallbackExport1.js new file mode 100644 index 000000000..eac66bbf5 --- /dev/null +++ b/testdata/baselines/reference/compiler/jsDocCallbackExport1.js @@ -0,0 +1,21 @@ +//// [tests/cases/compiler/jsDocCallbackExport1.ts] //// + +//// [x.js] +/** + * @callback Foo + * @param {string} x + * @returns {number} + */ +function f1() {} + + + + +//// [x.d.ts] +type Foo = (x: string) => number; +/** + * @callback Foo + * @param {string} x + * @returns {number} + */ +declare function f1(): void; diff --git a/testdata/baselines/reference/compiler/jsDocCallbackExport1.symbols b/testdata/baselines/reference/compiler/jsDocCallbackExport1.symbols new file mode 100644 index 000000000..bcf7834fa --- /dev/null +++ b/testdata/baselines/reference/compiler/jsDocCallbackExport1.symbols @@ -0,0 +1,11 @@ +//// [tests/cases/compiler/jsDocCallbackExport1.ts] //// + +=== x.js === +/** + * @callback Foo + * @param {string} x + * @returns {number} + */ +function f1() {} +>f1 : Symbol(f1, Decl(x.js, 0, 0)) + diff --git a/testdata/baselines/reference/compiler/jsDocCallbackExport1.types b/testdata/baselines/reference/compiler/jsDocCallbackExport1.types new file mode 100644 index 000000000..f5d648222 --- /dev/null +++ b/testdata/baselines/reference/compiler/jsDocCallbackExport1.types @@ -0,0 +1,11 @@ +//// [tests/cases/compiler/jsDocCallbackExport1.ts] //// + +=== x.js === +/** + * @callback Foo + * @param {string} x + * @returns {number} + */ +function f1() {} +>f1 : () => void + diff --git a/testdata/baselines/reference/compiler/jsDocCallbackExport2.js b/testdata/baselines/reference/compiler/jsDocCallbackExport2.js new file mode 100644 index 000000000..8a894ce57 --- /dev/null +++ b/testdata/baselines/reference/compiler/jsDocCallbackExport2.js @@ -0,0 +1,21 @@ +//// [tests/cases/compiler/jsDocCallbackExport2.ts] //// + +//// [x.js] +/** + * @callback Foo + * @param {string} x + * @returns {number} + */ +export function f1() {} + + + + +//// [x.d.ts] +export type Foo = (x: string) => number; +/** + * @callback Foo + * @param {string} x + * @returns {number} + */ +export declare function f1(): void; diff --git a/testdata/baselines/reference/compiler/jsDocCallbackExport2.symbols b/testdata/baselines/reference/compiler/jsDocCallbackExport2.symbols new file mode 100644 index 000000000..cccf7626e --- /dev/null +++ b/testdata/baselines/reference/compiler/jsDocCallbackExport2.symbols @@ -0,0 +1,11 @@ +//// [tests/cases/compiler/jsDocCallbackExport2.ts] //// + +=== x.js === +/** + * @callback Foo + * @param {string} x + * @returns {number} + */ +export function f1() {} +>f1 : Symbol(f1, Decl(x.js, 0, 0)) + diff --git a/testdata/baselines/reference/compiler/jsDocCallbackExport2.types b/testdata/baselines/reference/compiler/jsDocCallbackExport2.types new file mode 100644 index 000000000..2a6b03502 --- /dev/null +++ b/testdata/baselines/reference/compiler/jsDocCallbackExport2.types @@ -0,0 +1,11 @@ +//// [tests/cases/compiler/jsDocCallbackExport2.ts] //// + +=== x.js === +/** + * @callback Foo + * @param {string} x + * @returns {number} + */ +export function f1() {} +>f1 : () => void + diff --git a/testdata/baselines/reference/fourslash/inlayHints/inlayHintsTupleTypeCrash.baseline b/testdata/baselines/reference/fourslash/inlayHints/inlayHintsTupleTypeCrash.baseline new file mode 100644 index 000000000..c4db2d46e --- /dev/null +++ b/testdata/baselines/reference/fourslash/inlayHints/inlayHintsTupleTypeCrash.baseline @@ -0,0 +1,25 @@ +// === Inlay Hints === + tuples.forEach((l) => {}) + ^ +{ + "position": { + "line": 1, + "character": 19 + }, + "label": [ + { + "value": ": " + }, + { + "value": "[" + }, + { + "value": "string" + }, + { + "value": "]" + } + ], + "kind": 1, + "paddingLeft": true +} \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/jsFileAlternativeUseOfOverloadTag.js b/testdata/baselines/reference/submodule/compiler/jsFileAlternativeUseOfOverloadTag.js index 8fbcf1ca1..885131f80 100644 --- a/testdata/baselines/reference/submodule/compiler/jsFileAlternativeUseOfOverloadTag.js +++ b/testdata/baselines/reference/submodule/compiler/jsFileAlternativeUseOfOverloadTag.js @@ -139,7 +139,7 @@ declare const example2: { constructor: () => void; }; declare function evaluate(): any; -export type callback = (error: any, result: any) ; +type callback = (error: any, result: any) ; declare const example3: { /** * @overload evaluate(options = {}, [callback]) @@ -161,7 +161,7 @@ declare const example3: { //// [DtsFileErrors] -dist/jsFileAlternativeUseOfOverloadTag.d.ts(34,50): error TS1005: '=>' expected. +dist/jsFileAlternativeUseOfOverloadTag.d.ts(34,43): error TS1005: '=>' expected. ==== dist/jsFileAlternativeUseOfOverloadTag.d.ts (1 errors) ==== @@ -198,8 +198,8 @@ dist/jsFileAlternativeUseOfOverloadTag.d.ts(34,50): error TS1005: '=>' expected. constructor: () => void; }; declare function evaluate(): any; - export type callback = (error: any, result: any) ; - ~ + type callback = (error: any, result: any) ; + ~ !!! error TS1005: '=>' expected. declare const example3: { /** diff --git a/testdata/baselines/reference/submodule/compiler/jsFileAlternativeUseOfOverloadTag.js.diff b/testdata/baselines/reference/submodule/compiler/jsFileAlternativeUseOfOverloadTag.js.diff index e6be9c01f..d133646bf 100644 --- a/testdata/baselines/reference/submodule/compiler/jsFileAlternativeUseOfOverloadTag.js.diff +++ b/testdata/baselines/reference/submodule/compiler/jsFileAlternativeUseOfOverloadTag.js.diff @@ -82,7 +82,7 @@ + constructor: () => void; +}; +declare function evaluate(): any; -+export type callback = (error: any, result: any) ; ++type callback = (error: any, result: any) ; +declare const example3: { /** * @overload evaluate(options = {}, [callback]) @@ -105,7 +105,7 @@ +//// [DtsFileErrors] + + -+dist/jsFileAlternativeUseOfOverloadTag.d.ts(34,50): error TS1005: '=>' expected. ++dist/jsFileAlternativeUseOfOverloadTag.d.ts(34,43): error TS1005: '=>' expected. + + +==== dist/jsFileAlternativeUseOfOverloadTag.d.ts (1 errors) ==== @@ -142,8 +142,8 @@ + constructor: () => void; + }; + declare function evaluate(): any; -+ export type callback = (error: any, result: any) ; -+ ~ ++ type callback = (error: any, result: any) ; ++ ~ +!!! error TS1005: '=>' expected. + declare const example3: { + /** diff --git a/testdata/baselines/reference/submodule/conformance/callbackTagNestedParameter.js b/testdata/baselines/reference/submodule/conformance/callbackTagNestedParameter.js index 6021aa159..88bac0df5 100644 --- a/testdata/baselines/reference/submodule/conformance/callbackTagNestedParameter.js +++ b/testdata/baselines/reference/submodule/conformance/callbackTagNestedParameter.js @@ -29,7 +29,7 @@ function eachPerson(callback) { * @param {number} [person.age] * @returns {void} */ -export type WorksWithPeopleCallback = (person: { +type WorksWithPeopleCallback = (person: { name: string; age?: number; }) => void; diff --git a/testdata/baselines/reference/submodule/conformance/callbackTagNestedParameter.js.diff b/testdata/baselines/reference/submodule/conformance/callbackTagNestedParameter.js.diff index 8fe8a594c..2f50a7a41 100644 --- a/testdata/baselines/reference/submodule/conformance/callbackTagNestedParameter.js.diff +++ b/testdata/baselines/reference/submodule/conformance/callbackTagNestedParameter.js.diff @@ -4,7 +4,7 @@ * @param {number} [person.age] * @returns {void} */ -+export type WorksWithPeopleCallback = (person: { ++type WorksWithPeopleCallback = (person: { + name: string; + age?: number; +}) => void; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsParameterTagReusesInputNodeInEmit1.js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsParameterTagReusesInputNodeInEmit1.js index 5806719ef..bd3ba2627 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsParameterTagReusesInputNodeInEmit1.js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsParameterTagReusesInputNodeInEmit1.js @@ -70,7 +70,7 @@ declare namespace BaseFactory { export = BaseFactory; //// [file.d.ts] type BaseFactory = import('./base'); -export type BaseFactoryFactory = (factory: import('./base')) ; +type BaseFactoryFactory = (factory: import('./base')) ; /** @typedef {import('./base')} BaseFactory */ /** * @callback BaseFactoryFactory diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsParameterTagReusesInputNodeInEmit1.js.diff b/testdata/baselines/reference/submodule/conformance/jsDeclarationsParameterTagReusesInputNodeInEmit1.js.diff index 192a51589..d941bf0e8 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsParameterTagReusesInputNodeInEmit1.js.diff +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsParameterTagReusesInputNodeInEmit1.js.diff @@ -25,7 +25,7 @@ - }; -}; +type BaseFactory = import('./base'); -+export type BaseFactoryFactory = (factory: import('./base')) ; ++type BaseFactoryFactory = (factory: import('./base')) ; /** @typedef {import('./base')} BaseFactory */ /** * @callback BaseFactoryFactory diff --git a/testdata/baselines/reference/submodule/conformance/templateInsideCallback.js b/testdata/baselines/reference/submodule/conformance/templateInsideCallback.js index 26167e83c..4f99ceb20 100644 --- a/testdata/baselines/reference/submodule/conformance/templateInsideCallback.js +++ b/testdata/baselines/reference/submodule/conformance/templateInsideCallback.js @@ -113,7 +113,7 @@ function flatMap(array, iterable = identity) { //// [templateInsideCallback.d.ts] -export type Call = () ; +type Call = () ; /** * @typedef Oops * @template T diff --git a/testdata/baselines/reference/submodule/conformance/templateInsideCallback.js.diff b/testdata/baselines/reference/submodule/conformance/templateInsideCallback.js.diff index 17015f261..2abe09db9 100644 --- a/testdata/baselines/reference/submodule/conformance/templateInsideCallback.js.diff +++ b/testdata/baselines/reference/submodule/conformance/templateInsideCallback.js.diff @@ -28,7 +28,7 @@ - * @returns {T[]} - */ -declare function flatMap(): any; -+export type Call = () ; ++type Call = () ; /** * @typedef Oops * @template T diff --git a/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with---help.js b/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with---help.js new file mode 100644 index 000000000..45e76c186 --- /dev/null +++ b/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with---help.js @@ -0,0 +1,58 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: + +tsgo --init --help +ExitStatus:: Success +Output:: + +Created a new tsconfig.json + +You can learn more at https://aka.ms/tsconfig +//// [/home/src/workspaces/project/tsconfig.json] *new* +{ + // Visit https://aka.ms/tsconfig to read more about this file + "compilerOptions": { + // File Layout + // "rootDir": "./src", + // "outDir": "./dist", + + // Environment Settings + // See also https://aka.ms/tsconfig/module + "module": "nodenext", + "target": "esnext", + "types": [], + // For nodejs: + // "lib": ["esnext"], + // "types": ["node"], + // and npm install -D @types/node + + // Other Outputs + "sourceMap": true, + "declaration": true, + "declarationMap": true, + + // Stricter Typechecking Options + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + + // Style Options + // "noImplicitReturns": true, + // "noImplicitOverride": true, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + // "noFallthroughCasesInSwitch": true, + // "noPropertyAccessFromIndexSignature": true, + + // Recommended Options + "strict": true, + "jsx": "react-jsx", + "verbatimModuleSyntax": true, + "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "moduleDetection": "force", + "skipLibCheck": true, + } +} + + diff --git a/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-advanced-options.js b/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-advanced-options.js new file mode 100644 index 000000000..c930bfeb1 --- /dev/null +++ b/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-advanced-options.js @@ -0,0 +1,61 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: + +tsgo --init --declaration --declarationDir lib --skipLibCheck --noErrorTruncation +ExitStatus:: Success +Output:: + +Created a new tsconfig.json + +You can learn more at https://aka.ms/tsconfig +//// [/home/src/workspaces/project/tsconfig.json] *new* +{ + // Visit https://aka.ms/tsconfig to read more about this file + "compilerOptions": { + // File Layout + // "rootDir": "./src", + // "outDir": "./dist", + + // Environment Settings + // See also https://aka.ms/tsconfig/module + "module": "nodenext", + "target": "esnext", + "types": [], + // For nodejs: + // "lib": ["esnext"], + // "types": ["node"], + // and npm install -D @types/node + + // Other Outputs + "sourceMap": true, + "declaration": true, + "declarationMap": true, + + // Stricter Typechecking Options + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + + // Style Options + // "noImplicitReturns": true, + // "noImplicitOverride": true, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + // "noFallthroughCasesInSwitch": true, + // "noPropertyAccessFromIndexSignature": true, + + // Recommended Options + "strict": true, + "jsx": "react-jsx", + "verbatimModuleSyntax": true, + "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "moduleDetection": "force", + "skipLibCheck": true, + + "declarationDir": "lib", + "noErrorTruncation": true, + } +} + + diff --git a/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-boolean-value-compiler-options.js b/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-boolean-value-compiler-options.js new file mode 100644 index 000000000..6991ef17a --- /dev/null +++ b/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-boolean-value-compiler-options.js @@ -0,0 +1,58 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: + +tsgo --init --noUnusedLocals +ExitStatus:: Success +Output:: + +Created a new tsconfig.json + +You can learn more at https://aka.ms/tsconfig +//// [/home/src/workspaces/project/tsconfig.json] *new* +{ + // Visit https://aka.ms/tsconfig to read more about this file + "compilerOptions": { + // File Layout + // "rootDir": "./src", + // "outDir": "./dist", + + // Environment Settings + // See also https://aka.ms/tsconfig/module + "module": "nodenext", + "target": "esnext", + "types": [], + // For nodejs: + // "lib": ["esnext"], + // "types": ["node"], + // and npm install -D @types/node + + // Other Outputs + "sourceMap": true, + "declaration": true, + "declarationMap": true, + + // Stricter Typechecking Options + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + + // Style Options + // "noImplicitReturns": true, + // "noImplicitOverride": true, + "noUnusedLocals": true, + // "noUnusedParameters": true, + // "noFallthroughCasesInSwitch": true, + // "noPropertyAccessFromIndexSignature": true, + + // Recommended Options + "strict": true, + "jsx": "react-jsx", + "verbatimModuleSyntax": true, + "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "moduleDetection": "force", + "skipLibCheck": true, + } +} + + diff --git a/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-enum-value-compiler-options.js b/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-enum-value-compiler-options.js new file mode 100644 index 000000000..5f38857fa --- /dev/null +++ b/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-enum-value-compiler-options.js @@ -0,0 +1,58 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: + +tsgo --init --target es5 --jsx react +ExitStatus:: Success +Output:: + +Created a new tsconfig.json + +You can learn more at https://aka.ms/tsconfig +//// [/home/src/workspaces/project/tsconfig.json] *new* +{ + // Visit https://aka.ms/tsconfig to read more about this file + "compilerOptions": { + // File Layout + // "rootDir": "./src", + // "outDir": "./dist", + + // Environment Settings + // See also https://aka.ms/tsconfig/module + "module": "nodenext", + "target": "es5", + "types": [], + // For nodejs: + // "lib": ["esnext"], + // "types": ["node"], + // and npm install -D @types/node + + // Other Outputs + "sourceMap": true, + "declaration": true, + "declarationMap": true, + + // Stricter Typechecking Options + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + + // Style Options + // "noImplicitReturns": true, + // "noImplicitOverride": true, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + // "noFallthroughCasesInSwitch": true, + // "noPropertyAccessFromIndexSignature": true, + + // Recommended Options + "strict": true, + "jsx": "react", + "verbatimModuleSyntax": true, + "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "moduleDetection": "force", + "skipLibCheck": true, + } +} + + diff --git a/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-files-options.js b/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-files-options.js new file mode 100644 index 000000000..b48c72562 --- /dev/null +++ b/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-files-options.js @@ -0,0 +1,58 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: + +tsgo --init file0.st file1.ts file2.ts +ExitStatus:: Success +Output:: + +Created a new tsconfig.json + +You can learn more at https://aka.ms/tsconfig +//// [/home/src/workspaces/project/tsconfig.json] *new* +{ + // Visit https://aka.ms/tsconfig to read more about this file + "compilerOptions": { + // File Layout + // "rootDir": "./src", + // "outDir": "./dist", + + // Environment Settings + // See also https://aka.ms/tsconfig/module + "module": "nodenext", + "target": "esnext", + "types": [], + // For nodejs: + // "lib": ["esnext"], + // "types": ["node"], + // and npm install -D @types/node + + // Other Outputs + "sourceMap": true, + "declaration": true, + "declarationMap": true, + + // Stricter Typechecking Options + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + + // Style Options + // "noImplicitReturns": true, + // "noImplicitOverride": true, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + // "noFallthroughCasesInSwitch": true, + // "noPropertyAccessFromIndexSignature": true, + + // Recommended Options + "strict": true, + "jsx": "react-jsx", + "verbatimModuleSyntax": true, + "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "moduleDetection": "force", + "skipLibCheck": true, + } +} + + diff --git a/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-incorrect-compiler-option-value.js b/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-incorrect-compiler-option-value.js new file mode 100644 index 000000000..037fb5cc4 --- /dev/null +++ b/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-incorrect-compiler-option-value.js @@ -0,0 +1,9 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: + +tsgo --init --lib nonExistLib,es5,es2015.promise +ExitStatus:: DiagnosticsPresent_OutputsSkipped +Output:: +error TS6046: Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'es2023', 'es2024', 'esnext', 'dom', 'dom.iterable', 'dom.asynciterable', 'webworker', 'webworker.importscripts', 'webworker.iterable', 'webworker.asynciterable', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2016.intl', 'es2017.arraybuffer', 'es2017.date', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'es2018.asyncgenerator', 'es2018.asynciterable', 'es2018.intl', 'es2018.promise', 'es2018.regexp', 'es2019.array', 'es2019.object', 'es2019.string', 'es2019.symbol', 'es2019.intl', 'es2020.bigint', 'es2020.date', 'es2020.promise', 'es2020.sharedmemory', 'es2020.string', 'es2020.symbol.wellknown', 'es2020.intl', 'es2020.number', 'es2021.promise', 'es2021.string', 'es2021.weakref', 'es2021.intl', 'es2022.array', 'es2022.error', 'es2022.intl', 'es2022.object', 'es2022.string', 'es2022.regexp', 'es2023.array', 'es2023.collection', 'es2023.intl', 'es2024.arraybuffer', 'es2024.collection', 'es2024.object', 'es2024.promise', 'es2024.regexp', 'es2024.sharedmemory', 'es2024.string', 'esnext.array', 'esnext.collection', 'esnext.symbol', 'esnext.asynciterable', 'esnext.intl', 'esnext.disposable', 'esnext.bigint', 'esnext.string', 'esnext.promise', 'esnext.weakref', 'esnext.decorators', 'esnext.object', 'esnext.regexp', 'esnext.iterator', 'esnext.float16', 'esnext.error', 'esnext.sharedmemory', 'decorators', 'decorators.legacy'. + diff --git a/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-incorrect-compiler-option.js b/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-incorrect-compiler-option.js new file mode 100644 index 000000000..c31779e87 --- /dev/null +++ b/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-incorrect-compiler-option.js @@ -0,0 +1,9 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: + +tsgo --init --someNonExistOption +ExitStatus:: DiagnosticsPresent_OutputsSkipped +Output:: +error TS5023: Unknown compiler option '--someNonExistOption'. + diff --git a/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-list-compiler-options-with-enum-value.js b/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-list-compiler-options-with-enum-value.js new file mode 100644 index 000000000..f30964703 --- /dev/null +++ b/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-list-compiler-options-with-enum-value.js @@ -0,0 +1,59 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: + +tsgo --init --lib es5,es2015.core +ExitStatus:: Success +Output:: + +Created a new tsconfig.json + +You can learn more at https://aka.ms/tsconfig +//// [/home/src/workspaces/project/tsconfig.json] *new* +{ + // Visit https://aka.ms/tsconfig to read more about this file + "compilerOptions": { + // File Layout + // "rootDir": "./src", + // "outDir": "./dist", + + // Environment Settings + // See also https://aka.ms/tsconfig/module + "module": "nodenext", + "target": "esnext", + "types": [], + "lib": ["es5", "es2015.core"], + // For nodejs: + // "lib": ["esnext"], + // "types": ["node"], + // and npm install -D @types/node + + // Other Outputs + "sourceMap": true, + "declaration": true, + "declarationMap": true, + + // Stricter Typechecking Options + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + + // Style Options + // "noImplicitReturns": true, + // "noImplicitOverride": true, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + // "noFallthroughCasesInSwitch": true, + // "noPropertyAccessFromIndexSignature": true, + + // Recommended Options + "strict": true, + "jsx": "react-jsx", + "verbatimModuleSyntax": true, + "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "moduleDetection": "force", + "skipLibCheck": true, + } +} + + diff --git a/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-list-compiler-options.js b/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-list-compiler-options.js new file mode 100644 index 000000000..7fb35046f --- /dev/null +++ b/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-list-compiler-options.js @@ -0,0 +1,58 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: + +tsgo --init --types jquery,mocha +ExitStatus:: Success +Output:: + +Created a new tsconfig.json + +You can learn more at https://aka.ms/tsconfig +//// [/home/src/workspaces/project/tsconfig.json] *new* +{ + // Visit https://aka.ms/tsconfig to read more about this file + "compilerOptions": { + // File Layout + // "rootDir": "./src", + // "outDir": "./dist", + + // Environment Settings + // See also https://aka.ms/tsconfig/module + "module": "nodenext", + "target": "esnext", + "types": ["jquery", "mocha"], + // For nodejs: + // "lib": ["esnext"], + // "types": ["node"], + // and npm install -D @types/node + + // Other Outputs + "sourceMap": true, + "declaration": true, + "declarationMap": true, + + // Stricter Typechecking Options + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + + // Style Options + // "noImplicitReturns": true, + // "noImplicitOverride": true, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + // "noFallthroughCasesInSwitch": true, + // "noPropertyAccessFromIndexSignature": true, + + // Recommended Options + "strict": true, + "jsx": "react-jsx", + "verbatimModuleSyntax": true, + "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "moduleDetection": "force", + "skipLibCheck": true, + } +} + + diff --git a/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-tsconfig.json.js b/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-tsconfig.json.js new file mode 100644 index 000000000..543a4d498 --- /dev/null +++ b/testdata/baselines/reference/tsc/commandLine/Initialized-TSConfig-with-tsconfig.json.js @@ -0,0 +1,18 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: +//// [/home/src/workspaces/project/first.ts] *new* +export const a = 1 +//// [/home/src/workspaces/project/tsconfig.json] *new* +{ + "compilerOptions": { + "strict": true, + "noEmit": true + } +} + +tsgo --init +ExitStatus:: Success +Output:: +error TS5054: A 'tsconfig.json' file is already defined at: '/home/src/workspaces/project/tsconfig.json'. + diff --git a/testdata/baselines/reference/tscWatch/commandLine/Initialized-TSConfig-with---watch.js b/testdata/baselines/reference/tscWatch/commandLine/Initialized-TSConfig-with---watch.js new file mode 100644 index 000000000..24a3371f4 --- /dev/null +++ b/testdata/baselines/reference/tscWatch/commandLine/Initialized-TSConfig-with---watch.js @@ -0,0 +1,58 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: + +tsgo --init --watch +ExitStatus:: Success +Output:: + +Created a new tsconfig.json + +You can learn more at https://aka.ms/tsconfig +//// [/home/src/workspaces/project/tsconfig.json] *new* +{ + // Visit https://aka.ms/tsconfig to read more about this file + "compilerOptions": { + // File Layout + // "rootDir": "./src", + // "outDir": "./dist", + + // Environment Settings + // See also https://aka.ms/tsconfig/module + "module": "nodenext", + "target": "esnext", + "types": [], + // For nodejs: + // "lib": ["esnext"], + // "types": ["node"], + // and npm install -D @types/node + + // Other Outputs + "sourceMap": true, + "declaration": true, + "declarationMap": true, + + // Stricter Typechecking Options + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + + // Style Options + // "noImplicitReturns": true, + // "noImplicitOverride": true, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + // "noFallthroughCasesInSwitch": true, + // "noPropertyAccessFromIndexSignature": true, + + // Recommended Options + "strict": true, + "jsx": "react-jsx", + "verbatimModuleSyntax": true, + "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "moduleDetection": "force", + "skipLibCheck": true, + } +} + + diff --git a/testdata/tests/cases/compiler/jsDocCallbackExport1.ts b/testdata/tests/cases/compiler/jsDocCallbackExport1.ts new file mode 100644 index 000000000..a46c07f8d --- /dev/null +++ b/testdata/tests/cases/compiler/jsDocCallbackExport1.ts @@ -0,0 +1,12 @@ +// @emitDeclarationOnly: true +// @declaration: true +// @allowJs: true +// @checkJs: true + +// @filename: x.js +/** + * @callback Foo + * @param {string} x + * @returns {number} + */ +function f1() {} diff --git a/testdata/tests/cases/compiler/jsDocCallbackExport2.ts b/testdata/tests/cases/compiler/jsDocCallbackExport2.ts new file mode 100644 index 000000000..a37d2c1b4 --- /dev/null +++ b/testdata/tests/cases/compiler/jsDocCallbackExport2.ts @@ -0,0 +1,12 @@ +// @emitDeclarationOnly: true +// @declaration: true +// @allowJs: true +// @checkJs: true + +// @filename: x.js +/** + * @callback Foo + * @param {string} x + * @returns {number} + */ +export function f1() {}