diff --git a/cmd/tsgo/lsp.go b/cmd/tsgo/lsp.go
index 0dd04c57fb..ae3d7ec768 100644
--- a/cmd/tsgo/lsp.go
+++ b/cmd/tsgo/lsp.go
@@ -64,6 +64,7 @@ func runLSP(args []string) int {
defer stop()
if err := s.Run(ctx); err != nil {
+ fmt.Fprintln(os.Stderr, err)
return 1
}
return 0
diff --git a/internal/api/api.go b/internal/api/api.go
index 2c4fa079ac..a730713594 100644
--- a/internal/api/api.go
+++ b/internal/api/api.go
@@ -161,7 +161,7 @@ func (api *API) GetSymbolAtPosition(ctx context.Context, projectId Handle[projec
return nil, errors.New("project not found")
}
- languageService := ls.NewLanguageService(project.GetProgram(), snapshot)
+ languageService := ls.NewLanguageService(project.ConfigFilePath(), project.GetProgram(), snapshot)
symbol, err := languageService.GetSymbolAtPosition(ctx, fileName, position)
if err != nil || symbol == nil {
return nil, err
@@ -203,7 +203,7 @@ func (api *API) GetSymbolAtLocation(ctx context.Context, projectId Handle[projec
if node == nil {
return nil, fmt.Errorf("node of kind %s not found at position %d in file %q", kind.String(), pos, sourceFile.FileName())
}
- languageService := ls.NewLanguageService(project.GetProgram(), snapshot)
+ languageService := ls.NewLanguageService(project.ConfigFilePath(), project.GetProgram(), snapshot)
symbol := languageService.GetSymbolAtLocation(ctx, node)
if symbol == nil {
return nil, nil
@@ -233,7 +233,7 @@ func (api *API) GetTypeOfSymbol(ctx context.Context, projectId Handle[project.Pr
if !ok {
return nil, fmt.Errorf("symbol %q not found", symbolHandle)
}
- languageService := ls.NewLanguageService(project.GetProgram(), snapshot)
+ languageService := ls.NewLanguageService(project.ConfigFilePath(), project.GetProgram(), snapshot)
t := languageService.GetTypeOfSymbol(ctx, symbol)
if t == nil {
return nil, nil
diff --git a/internal/ast/symbol.go b/internal/ast/symbol.go
index 7d0875adc0..f60ca0cee3 100644
--- a/internal/ast/symbol.go
+++ b/internal/ast/symbol.go
@@ -35,6 +35,14 @@ func (s *Symbol) IsStatic() bool {
return modifierFlags&ModifierFlagsStatic != 0
}
+// See comment on `declareModuleMember` in `binder.go`.
+func (s *Symbol) CombinedLocalAndExportSymbolFlags() SymbolFlags {
+ if s.ExportSymbol != nil {
+ return s.Flags | s.ExportSymbol.Flags
+ }
+ return s.Flags
+}
+
// SymbolTable
type SymbolTable map[string]*Symbol
diff --git a/internal/ast/utilities.go b/internal/ast/utilities.go
index 6338dedc58..1c07b64715 100644
--- a/internal/ast/utilities.go
+++ b/internal/ast/utilities.go
@@ -1870,11 +1870,11 @@ func IsExpressionNode(node *Node) bool {
for node.Parent.Kind == KindQualifiedName {
node = node.Parent
}
- return IsTypeQueryNode(node.Parent) || IsJSDocLinkLike(node.Parent) || IsJSDocNameReference(node.Parent) || isJSXTagName(node)
+ return IsTypeQueryNode(node.Parent) || IsJSDocLinkLike(node.Parent) || IsJSDocNameReference(node.Parent) || IsJsxTagName(node)
case KindPrivateIdentifier:
return IsBinaryExpression(node.Parent) && node.Parent.AsBinaryExpression().Left == node && node.Parent.AsBinaryExpression().OperatorToken.Kind == KindInKeyword
case KindIdentifier:
- if IsTypeQueryNode(node.Parent) || IsJSDocLinkLike(node.Parent) || IsJSDocNameReference(node.Parent) || isJSXTagName(node) {
+ if IsTypeQueryNode(node.Parent) || IsJSDocLinkLike(node.Parent) || IsJSDocNameReference(node.Parent) || IsJsxTagName(node) {
return true
}
fallthrough
@@ -1991,15 +1991,6 @@ func IsJSDocTag(node *Node) bool {
return node.Kind >= KindFirstJSDocTagNode && node.Kind <= KindLastJSDocTagNode
}
-func isJSXTagName(node *Node) bool {
- parent := node.Parent
- switch parent.Kind {
- case KindJsxOpeningElement, KindJsxSelfClosingElement, KindJsxClosingElement:
- return parent.TagName() == node
- }
- return false
-}
-
func IsSuperCall(node *Node) bool {
return IsCallExpression(node) && node.Expression().Kind == KindSuperKeyword
}
@@ -3407,12 +3398,12 @@ func IsExternalModuleAugmentation(node *Node) bool {
func GetSourceFileOfModule(module *Symbol) *SourceFile {
declaration := module.ValueDeclaration
if declaration == nil {
- declaration = getNonAugmentationDeclaration(module)
+ declaration = GetNonAugmentationDeclaration(module)
}
return GetSourceFileOfNode(declaration)
}
-func getNonAugmentationDeclaration(symbol *Symbol) *Node {
+func GetNonAugmentationDeclaration(symbol *Symbol) *Node {
return core.Find(symbol.Declarations, func(d *Node) bool {
return !IsExternalModuleAugmentation(d) && !IsGlobalScopeAugmentation(d)
})
@@ -3888,6 +3879,37 @@ func GetContainingFunction(node *Node) *Node {
return FindAncestor(node.Parent, IsFunctionLike)
}
+func ImportFromModuleSpecifier(node *Node) *Node {
+ if result := TryGetImportFromModuleSpecifier(node); result != nil {
+ return result
+ }
+ debug.FailBadSyntaxKind(node.Parent)
+ return nil
+}
+
+func TryGetImportFromModuleSpecifier(node *StringLiteralLike) *Node {
+ switch node.Parent.Kind {
+ case KindImportDeclaration, KindJSImportDeclaration, KindExportDeclaration:
+ return node.Parent
+ case KindExternalModuleReference:
+ return node.Parent.Parent
+ case KindCallExpression:
+ if IsImportCall(node.Parent) || IsRequireCall(node.Parent, false /*requireStringLiteralLikeArgument*/) {
+ return node.Parent
+ }
+ return nil
+ case KindLiteralType:
+ if !IsStringLiteral(node) {
+ return nil
+ }
+ if IsImportTypeNode(node.Parent.Parent) {
+ return node.Parent.Parent
+ }
+ return nil
+ }
+ return nil
+}
+
func IsImplicitlyExportedJSTypeAlias(node *Node) bool {
return IsJSTypeAliasDeclaration(node) && IsSourceFile(node.Parent) && IsExternalOrCommonJSModule(node.Parent.AsSourceFile())
}
diff --git a/internal/checker/checker.go b/internal/checker/checker.go
index 64e9f79f69..8c711c6325 100644
--- a/internal/checker/checker.go
+++ b/internal/checker/checker.go
@@ -14430,6 +14430,9 @@ func (c *Checker) getEmitSyntaxForModuleSpecifierExpression(usage *ast.Node) cor
}
func (c *Checker) errorNoModuleMemberSymbol(moduleSymbol *ast.Symbol, targetSymbol *ast.Symbol, node *ast.Node, name *ast.Node) {
+ if c.compilerOptions.NoCheck.IsTrue() {
+ return
+ }
moduleName := c.getFullyQualifiedName(moduleSymbol, node)
declarationName := scanner.DeclarationNameToString(name)
var suggestion *ast.Symbol
@@ -14641,6 +14644,7 @@ func (c *Checker) markSymbolOfAliasDeclarationIfTypeOnly(aliasDeclaration *ast.N
func (c *Checker) resolveExternalModuleName(location *ast.Node, moduleReferenceExpression *ast.Node, ignoreErrors bool) *ast.Symbol {
errorMessage := diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations
+ ignoreErrors = ignoreErrors || c.compilerOptions.NoCheck.IsTrue()
return c.resolveExternalModuleNameWorker(location, moduleReferenceExpression, core.IfElse(ignoreErrors, nil, errorMessage), ignoreErrors, false /*isForAugmentation*/)
}
diff --git a/internal/checker/services.go b/internal/checker/services.go
index ba0236d1b5..5d1f5f557f 100644
--- a/internal/checker/services.go
+++ b/internal/checker/services.go
@@ -26,7 +26,7 @@ func (c *Checker) getSymbolsInScope(location *ast.Node, meaning ast.SymbolFlags)
// Copy the given symbol into symbol tables if the symbol has the given meaning
// and it doesn't already exists in the symbol table.
copySymbol := func(symbol *ast.Symbol, meaning ast.SymbolFlags) {
- if GetCombinedLocalAndExportSymbolFlags(symbol)&meaning != 0 {
+ if symbol.CombinedLocalAndExportSymbolFlags()&meaning != 0 {
id := symbol.Name
// We will copy all symbol regardless of its reserved name because
// symbolsToArray will check whether the key is a reserved name and
@@ -393,6 +393,13 @@ func (c *Checker) GetRootSymbols(symbol *ast.Symbol) []*ast.Symbol {
return result
}
+func (c *Checker) GetMappedTypeSymbolOfProperty(symbol *ast.Symbol) *ast.Symbol {
+ if valueLinks := c.valueSymbolLinks.TryGet(symbol); valueLinks != nil {
+ return valueLinks.containingType.symbol
+ }
+ return nil
+}
+
func (c *Checker) getImmediateRootSymbols(symbol *ast.Symbol) []*ast.Symbol {
if symbol.CheckFlags&ast.CheckFlagsSynthetic != 0 {
return core.MapNonNil(
diff --git a/internal/checker/utilities.go b/internal/checker/utilities.go
index 6fdedd0a91..d1b852fe9e 100644
--- a/internal/checker/utilities.go
+++ b/internal/checker/utilities.go
@@ -1622,14 +1622,6 @@ func symbolsToArray(symbols ast.SymbolTable) []*ast.Symbol {
return result
}
-// See comment on `declareModuleMember` in `binder.go`.
-func GetCombinedLocalAndExportSymbolFlags(symbol *ast.Symbol) ast.SymbolFlags {
- if symbol.ExportSymbol != nil {
- return symbol.Flags | symbol.ExportSymbol.Flags
- }
- return symbol.Flags
-}
-
func SkipAlias(symbol *ast.Symbol, checker *Checker) *ast.Symbol {
if symbol.Flags&ast.SymbolFlagsAlias != 0 {
return checker.GetAliasedSymbol(symbol)
diff --git a/internal/collections/multimap.go b/internal/collections/multimap.go
index e7df1ae5c7..a996917e21 100644
--- a/internal/collections/multimap.go
+++ b/internal/collections/multimap.go
@@ -10,6 +10,12 @@ type MultiMap[K comparable, V comparable] struct {
M map[K][]V
}
+func NewMultiMapWithSizeHint[K comparable, V comparable](hint int) *MultiMap[K, V] {
+ return &MultiMap[K, V]{
+ M: make(map[K][]V, hint),
+ }
+}
+
func GroupBy[K comparable, V comparable](items []V, groupId func(V) K) *MultiMap[K, V] {
m := &MultiMap[K, V]{}
for _, item := range items {
diff --git a/internal/collections/set.go b/internal/collections/set.go
index 6dfd3c90d6..7b0aabff50 100644
--- a/internal/collections/set.go
+++ b/internal/collections/set.go
@@ -14,6 +14,9 @@ func NewSetWithSizeHint[T comparable](hint int) *Set[T] {
}
func (s *Set[T]) Has(key T) bool {
+ if s == nil {
+ return false
+ }
_, ok := s.M[key]
return ok
}
@@ -30,14 +33,23 @@ func (s *Set[T]) Delete(key T) {
}
func (s *Set[T]) Len() int {
+ if s == nil {
+ return 0
+ }
return len(s.M)
}
func (s *Set[T]) Keys() map[T]struct{} {
+ if s == nil {
+ return nil
+ }
return s.M
}
func (s *Set[T]) Clear() {
+ if s == nil {
+ return
+ }
clear(s.M)
}
@@ -58,6 +70,37 @@ func (s *Set[T]) Clone() *Set[T] {
return clone
}
+func (s *Set[T]) Union(other *Set[T]) {
+ if s.Len() == 0 && other.Len() == 0 {
+ return
+ }
+ if s == nil {
+ panic("cannot modify nil Set")
+ }
+ if s.M == nil {
+ s.M = maps.Clone(other.M)
+ return
+ }
+ maps.Copy(s.M, other.M)
+}
+
+func (s *Set[T]) UnionedWith(other *Set[T]) *Set[T] {
+ if s == nil && other == nil {
+ return nil
+ }
+ result := s.Clone()
+ if other != nil {
+ if result == nil {
+ result = &Set[T]{}
+ }
+ if result.M == nil {
+ result.M = make(map[T]struct{}, len(other.M))
+ }
+ maps.Copy(result.M, other.M)
+ }
+ return result
+}
+
func (s *Set[T]) Equals(other *Set[T]) bool {
if s == other {
return true
@@ -68,6 +111,33 @@ func (s *Set[T]) Equals(other *Set[T]) bool {
return maps.Equal(s.M, other.M)
}
+func (s *Set[T]) IsSubsetOf(other *Set[T]) bool {
+ if s == nil {
+ return true
+ }
+ if other == nil {
+ return false
+ }
+ for key := range s.M {
+ if !other.Has(key) {
+ return false
+ }
+ }
+ return true
+}
+
+func (s *Set[T]) Intersects(other *Set[T]) bool {
+ if s == nil || other == nil {
+ return false
+ }
+ for key := range s.M {
+ if other.Has(key) {
+ return true
+ }
+ }
+ return false
+}
+
func NewSetFromItems[T comparable](items ...T) *Set[T] {
s := &Set[T]{}
for _, item := range items {
diff --git a/internal/compiler/program.go b/internal/compiler/program.go
index 8527d018cf..834d842ab7 100644
--- a/internal/compiler/program.go
+++ b/internal/compiler/program.go
@@ -19,6 +19,7 @@ import (
"github.com/microsoft/typescript-go/internal/diagnostics"
"github.com/microsoft/typescript-go/internal/locale"
"github.com/microsoft/typescript-go/internal/module"
+ "github.com/microsoft/typescript-go/internal/modulespecifiers"
"github.com/microsoft/typescript-go/internal/outputpaths"
"github.com/microsoft/typescript-go/internal/packagejson"
"github.com/microsoft/typescript-go/internal/parser"
@@ -72,6 +73,11 @@ type Program struct {
knownSymlinks *symlinks.KnownSymlinks
knownSymlinksOnce sync.Once
+ // Used by auto-imports
+ packageNamesOnce sync.Once
+ resolvedPackageNames *collections.Set[string]
+ unresolvedPackageNames *collections.Set[string]
+
// Used by workspace/symbol
hasTSFileOnce sync.Once
hasTSFile bool
@@ -89,7 +95,7 @@ func (p *Program) GetCurrentDirectory() string {
// GetGlobalTypingsCacheLocation implements checker.Program.
func (p *Program) GetGlobalTypingsCacheLocation() string {
- return "" // !!! see src/tsserver/nodeServer.ts for strada's node-specific implementation
+ return p.opts.TypingsLocation
}
// GetNearestAncestorDirectoryWithPackageJson implements checker.Program.
@@ -173,8 +179,8 @@ func (p *Program) UseCaseSensitiveFileNames() bool {
return p.Host().FS().UseCaseSensitiveFileNames()
}
-func (p *Program) UsesUriStyleNodeCoreModules() bool {
- return p.usesUriStyleNodeCoreModules.IsTrue()
+func (p *Program) UsesUriStyleNodeCoreModules() core.Tristate {
+ return p.usesUriStyleNodeCoreModules
}
var _ checker.Program = (*Program)(nil)
@@ -250,6 +256,8 @@ func (p *Program) UpdateProgram(changedFilePath tspath.Path, newHost CompilerHos
programDiagnostics: p.programDiagnostics,
hasEmitBlockingDiagnostics: p.hasEmitBlockingDiagnostics,
unresolvedImports: p.unresolvedImports,
+ resolvedPackageNames: p.resolvedPackageNames,
+ unresolvedPackageNames: p.unresolvedPackageNames,
knownSymlinks: p.knownSymlinks,
}
result.initCheckerPool()
@@ -1309,6 +1317,13 @@ func (p *Program) IsSourceFileDefaultLibrary(path tspath.Path) bool {
return ok
}
+func (p *Program) IsGlobalTypingsFile(fileName string) bool {
+ if !tspath.IsDeclarationFileName(fileName) {
+ return false
+ }
+ return tspath.ContainsPath(p.GetGlobalTypingsCacheLocation(), fileName, p.comparePathsOptions)
+}
+
func (p *Program) GetDefaultLibFile(path tspath.Path) *LibFile {
if libFile, ok := p.libFiles[path]; ok {
return libFile
@@ -1630,6 +1645,54 @@ func (p *Program) SourceFileMayBeEmitted(sourceFile *ast.SourceFile, forceDtsEmi
return sourceFileMayBeEmitted(sourceFile, p, forceDtsEmit)
}
+func (p *Program) ResolvedPackageNames() *collections.Set[string] {
+ p.collectPackageNames()
+ return p.resolvedPackageNames
+}
+
+func (p *Program) UnresolvedPackageNames() *collections.Set[string] {
+ p.collectPackageNames()
+ return p.unresolvedPackageNames
+}
+
+func (p *Program) collectPackageNames() {
+ p.packageNamesOnce.Do(func() {
+ if p.resolvedPackageNames == nil {
+ p.resolvedPackageNames = &collections.Set[string]{}
+ p.unresolvedPackageNames = &collections.Set[string]{}
+ for _, file := range p.files {
+ if p.IsSourceFileDefaultLibrary(file.Path()) || p.IsSourceFileFromExternalLibrary(file) || strings.Contains(file.FileName(), "/node_modules/") {
+ // Checking for /node_modules/ is a little imprecise, but ATA treats locally installed typings
+ // as root files, which would not pass IsSourceFileFromExternalLibrary.
+ continue
+ }
+ for _, imp := range file.Imports() {
+ if tspath.IsExternalModuleNameRelative(imp.Text()) {
+ continue
+ }
+ if resolvedModules, ok := p.resolvedModules[file.Path()]; ok {
+ key := module.ModeAwareCacheKey{Name: imp.Text(), Mode: p.GetModeForUsageLocation(file, imp)}
+ if resolvedModule, ok := resolvedModules[key]; ok && resolvedModule.IsResolved() {
+ if !resolvedModule.IsExternalLibraryImport {
+ continue
+ }
+ name := resolvedModule.PackageId.Name
+ if name == "" {
+ // node_modules package, but no name in package.json - this can happen in a monorepo package,
+ // and unfortunately in lots of fourslash tests
+ name = modulespecifiers.GetPackageNameFromDirectory(resolvedModule.ResolvedFileName)
+ }
+ p.resolvedPackageNames.Add(name)
+ continue
+ }
+ }
+ p.unresolvedPackageNames.Add(imp.Text())
+ }
+ }
+ }
+ })
+}
+
func (p *Program) IsLibFile(sourceFile *ast.SourceFile) bool {
_, ok := p.libFiles[sourceFile.Path()]
return ok
diff --git a/internal/core/compileroptions.go b/internal/core/compileroptions.go
index ad6a4ceeca..6fc84c84e1 100644
--- a/internal/core/compileroptions.go
+++ b/internal/core/compileroptions.go
@@ -166,6 +166,8 @@ type noCopy struct{}
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}
+var EmptyCompilerOptions = &CompilerOptions{}
+
var optionsType = reflect.TypeFor[CompilerOptions]()
// Clone creates a shallow copy of the CompilerOptions.
diff --git a/internal/core/core.go b/internal/core/core.go
index 345867fccc..c07284ea3d 100644
--- a/internal/core/core.go
+++ b/internal/core/core.go
@@ -266,6 +266,16 @@ func FirstNonNil[T any, U comparable](slice []T, f func(T) U) U {
return *new(U)
}
+func FirstNonZero[T comparable](values ...T) T {
+ var zero T
+ for _, value := range values {
+ if value != zero {
+ return value
+ }
+ }
+ return zero
+}
+
func Concatenate[T any](s1 []T, s2 []T) []T {
if len(s2) == 0 {
return s1
@@ -317,6 +327,30 @@ func InsertSorted[T any](slice []T, element T, cmp func(T, T) int) []T {
return slices.Insert(slice, i, element)
}
+// MinAllFunc returns all minimum elements from xs according to the comparison function cmp.
+func MinAllFunc[T any](xs []T, cmp func(a, b T) int) []T {
+ if len(xs) == 0 {
+ return nil
+ }
+
+ m := xs[0]
+ mins := []T{m}
+
+ for _, x := range xs[1:] {
+ c := cmp(x, m)
+ switch {
+ case c < 0:
+ m = x
+ mins = mins[:0]
+ mins = append(mins, x)
+ case c == 0:
+ mins = append(mins, x)
+ }
+ }
+
+ return mins
+}
+
func AppendIfUnique[T comparable](slice []T, element T) []T {
if slices.Contains(slice, element) {
return slice
@@ -621,15 +655,20 @@ func DiffMaps[K comparable, V comparable](m1 map[K]V, m2 map[K]V, onAdded func(K
DiffMapsFunc(m1, m2, comparableValuesEqual, onAdded, onRemoved, onChanged)
}
-func DiffMapsFunc[K comparable, V any](m1 map[K]V, m2 map[K]V, equalValues func(V, V) bool, onAdded func(K, V), onRemoved func(K, V), onChanged func(K, V, V)) {
- for k, v2 := range m2 {
- if _, ok := m1[k]; !ok {
- onAdded(k, v2)
+func DiffMapsFunc[K comparable, V1 any, V2 any](m1 map[K]V1, m2 map[K]V2, equalValues func(V1, V2) bool, onAdded func(K, V2), onRemoved func(K, V1), onChanged func(K, V1, V2)) {
+ if onAdded != nil {
+ for k, v2 := range m2 {
+ if _, ok := m1[k]; !ok {
+ onAdded(k, v2)
+ }
}
}
+ if onChanged == nil && onRemoved == nil {
+ return
+ }
for k, v1 := range m1 {
if v2, ok := m2[k]; ok {
- if !equalValues(v1, v2) {
+ if onChanged != nil && !equalValues(v1, v2) {
onChanged(k, v1, v2)
}
} else {
@@ -648,6 +687,24 @@ func CopyMapInto[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2)
return dst
}
+// UnorderedEqual returns true if s1 and s2 contain the same elements, regardless of order.
+func UnorderedEqual[T comparable](s1 []T, s2 []T) bool {
+ if len(s1) != len(s2) {
+ return false
+ }
+ counts := make(map[T]int)
+ for _, v := range s1 {
+ counts[v]++
+ }
+ for _, v := range s2 {
+ counts[v]--
+ if counts[v] < 0 {
+ return false
+ }
+ }
+ return true
+}
+
func Deduplicate[T comparable](slice []T) []T {
if len(slice) > 1 {
for i, value := range slice {
diff --git a/internal/fourslash/_scripts/convertFourslash.mts b/internal/fourslash/_scripts/convertFourslash.mts
index d958369d81..45b92ccc74 100644
--- a/internal/fourslash/_scripts/convertFourslash.mts
+++ b/internal/fourslash/_scripts/convertFourslash.mts
@@ -548,28 +548,9 @@ function parseVerifyApplyCodeActionArgs(arg: ts.Expression): string | undefined
}
dataProps.push(`ModuleSpecifier: ${getGoStringLiteral(moduleSpecifierInit.text)},`);
break;
- case "exportName":
- const exportNameInit = getStringLiteralLike(dataProp.initializer);
- if (!exportNameInit) {
- console.error(`Expected string literal for exportName in verify.applyCodeActionFromCompletion data, got ${dataProp.initializer.getText()}`);
- return undefined;
- }
- dataProps.push(`ExportName: ${getGoStringLiteral(exportNameInit.text)},`);
- break;
- case "fileName":
- const fileNameInit = getStringLiteralLike(dataProp.initializer);
- if (!fileNameInit) {
- console.error(`Expected string literal for fileName in verify.applyCodeActionFromCompletion data, got ${dataProp.initializer.getText()}`);
- return undefined;
- }
- dataProps.push(`FileName: ${getGoStringLiteral(fileNameInit.text)},`);
- break;
- default:
- console.error(`Unrecognized property in verify.applyCodeActionFromCompletion data: ${dataProp.getText()}`);
- return undefined;
}
}
- props.push(`AutoImportData: &lsproto.AutoImportData{\n${dataProps.join("\n")}\n},`);
+ props.push(`AutoImportFix: &lsproto.AutoImportFix{\n${dataProps.join("\n")}\n},`);
break;
case "description":
descInit = getStringLiteralLike(init);
@@ -1060,7 +1041,7 @@ function parseExpectedCompletionItem(expr: ts.Expression, codeActionArgs?: Verif
break;
}
itemProps.push(`Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: ${getGoStringLiteral(sourceInit.text)},
},
},`);
diff --git a/internal/fourslash/_scripts/crashingTests.txt b/internal/fourslash/_scripts/crashingTests.txt
index c8ceb4d30c..63c7a62466 100644
--- a/internal/fourslash/_scripts/crashingTests.txt
+++ b/internal/fourslash/_scripts/crashingTests.txt
@@ -1,6 +1,4 @@
TestCompletionsAfterJSDoc
-TestCompletionsImport_require_addToExisting
-TestCompletionsUniqueSymbol_import
TestFindReferencesBindingPatternInJsdocNoCrash1
TestFindReferencesBindingPatternInJsdocNoCrash2
TestGetOccurrencesIfElseBroken
diff --git a/internal/fourslash/_scripts/failingTests.txt b/internal/fourslash/_scripts/failingTests.txt
index be20094672..5105853a8f 100644
--- a/internal/fourslash/_scripts/failingTests.txt
+++ b/internal/fourslash/_scripts/failingTests.txt
@@ -11,10 +11,8 @@ TestAutoImportCompletionExportListAugmentation1
TestAutoImportCompletionExportListAugmentation2
TestAutoImportCompletionExportListAugmentation3
TestAutoImportCompletionExportListAugmentation4
-TestAutoImportCrossPackage_pathsAndSymlink
TestAutoImportCrossProject_symlinks_stripSrc
TestAutoImportCrossProject_symlinks_toDist
-TestAutoImportCrossProject_symlinks_toSrc
TestAutoImportFileExcludePatterns2
TestAutoImportFileExcludePatterns3
TestAutoImportJsDocImport1
@@ -22,22 +20,12 @@ TestAutoImportModuleNone1
TestAutoImportNodeModuleSymlinkRenamed
TestAutoImportNodeNextJSRequire
TestAutoImportPackageJsonImportsCaseSensitivity
-TestAutoImportProvider_exportMap1
+TestAutoImportPackageRootPath
+TestAutoImportPathsNodeModules
TestAutoImportProvider_exportMap2
-TestAutoImportProvider_exportMap3
-TestAutoImportProvider_exportMap4
TestAutoImportProvider_exportMap5
-TestAutoImportProvider_exportMap6
-TestAutoImportProvider_exportMap7
-TestAutoImportProvider_exportMap8
TestAutoImportProvider_exportMap9
TestAutoImportProvider_globalTypingsCache
-TestAutoImportProvider_namespaceSameNameAsIntrinsic
-TestAutoImportProvider_pnpm
-TestAutoImportProvider_wildcardExports1
-TestAutoImportProvider_wildcardExports2
-TestAutoImportProvider_wildcardExports3
-TestAutoImportProvider4
TestAutoImportProvider9
TestAutoImportSortCaseSensitivity1
TestAutoImportTypeImport1
@@ -132,6 +120,7 @@ TestCompletionListInNamedFunctionExpression
TestCompletionListInNamedFunctionExpression1
TestCompletionListInNamedFunctionExpressionWithShadowing
TestCompletionListInScope
+TestCompletionListInScope_doesNotIncludeAugmentations
TestCompletionListInTemplateLiteralParts1
TestCompletionListInUnclosedCommaExpression01
TestCompletionListInUnclosedCommaExpression02
@@ -177,19 +166,19 @@ TestCompletionsImport_default_anonymous
TestCompletionsImport_details_withMisspelledName
TestCompletionsImport_exportEquals_global
TestCompletionsImport_filteredByInvalidPackageJson_direct
+TestCompletionsImport_filteredByPackageJson_ambient
TestCompletionsImport_filteredByPackageJson_direct
TestCompletionsImport_filteredByPackageJson_nested
TestCompletionsImport_filteredByPackageJson_peerDependencies
TestCompletionsImport_filteredByPackageJson_typesImplicit
TestCompletionsImport_filteredByPackageJson_typesOnly
-TestCompletionsImport_jsxOpeningTagImportDefault
-TestCompletionsImport_mergedReExport
+TestCompletionsImport_importType
TestCompletionsImport_named_didNotExistBefore
+TestCompletionsImport_named_namespaceImportExists
TestCompletionsImport_noSemicolons
TestCompletionsImport_packageJsonImportsPreference
TestCompletionsImport_quoteStyle
TestCompletionsImport_reExport_wrongName
-TestCompletionsImport_reExportDefault2
TestCompletionsImport_require_addToExisting
TestCompletionsImport_typeOnly
TestCompletionsImport_umdDefaultNoCrash1
@@ -344,6 +333,8 @@ TestImportNameCodeFix_trailingComma
TestImportNameCodeFix_uriStyleNodeCoreModules2
TestImportNameCodeFix_uriStyleNodeCoreModules3
TestImportNameCodeFix_withJson
+TestImportNameCodeFixDefaultExport4
+TestImportNameCodeFixDefaultExport7
TestImportNameCodeFixExistingImport10
TestImportNameCodeFixExistingImport11
TestImportNameCodeFixExistingImport8
@@ -362,6 +353,7 @@ TestImportNameCodeFixNewImportFileQuoteStyle2
TestImportNameCodeFixNewImportFileQuoteStyleMixed0
TestImportNameCodeFixNewImportFileQuoteStyleMixed1
TestImportNameCodeFixNewImportTypeRoots1
+TestImportNameCodeFixOptionalImport0
TestImportTypeCompletions1
TestImportTypeCompletions3
TestImportTypeCompletions4
diff --git a/internal/fourslash/_scripts/manualTests.txt b/internal/fourslash/_scripts/manualTests.txt
index 5373890ae0..3fda8e48fc 100644
--- a/internal/fourslash/_scripts/manualTests.txt
+++ b/internal/fourslash/_scripts/manualTests.txt
@@ -21,9 +21,18 @@ quickInfoForOverloadOnConst1
renameDefaultKeyword
renameForDefaultExport01
tsxCompletion12
+completionsImport_reExportDefault
+completionsImport_reexportTransient
jsDocFunctionSignatures2
jsDocFunctionSignatures12
outliningHintSpansForFunction
getOutliningSpans
outliningForNonCompleteInterfaceDeclaration
-incrementalParsingWithJsDoc
\ No newline at end of file
+incrementalParsingWithJsDoc
+autoImportPackageRootPathTypeModule
+completionListWithLabel
+completionsImport_defaultAndNamedConflict
+completionsWithStringReplacementMode1
+jsdocParameterNameCompletion
+stringLiteralCompletionsInPositionTypedUsingRest
+importNameCodeFix_uriStyleNodeCoreModules1
\ No newline at end of file
diff --git a/internal/fourslash/fourslash.go b/internal/fourslash/fourslash.go
index 488c0beff8..41254911ae 100644
--- a/internal/fourslash/fourslash.go
+++ b/internal/fourslash/fourslash.go
@@ -620,7 +620,7 @@ func sendRequest[Params, Resp any](t *testing.T, f *FourslashTest, info lsproto.
t.Fatalf(prefix+"%s request returned error: %s", info.Method, resp.Error.String())
}
if !resultOk {
- t.Fatalf(prefix+"Unexpected %s response type: %T", info.Method, resp.Result)
+ t.Fatalf(prefix+"Unexpected %s response type: %T, error: %v", info.Method, resp.Result, resp.Error)
}
return result
}
@@ -928,6 +928,7 @@ type MarkerInput = any
// !!! user preferences param
// !!! completion context param
func (f *FourslashTest) VerifyCompletions(t *testing.T, markerInput MarkerInput, expected *CompletionsExpectedList) VerifyCompletionsResult {
+ t.Helper()
var list *lsproto.CompletionList
switch marker := markerInput.(type) {
case string:
@@ -998,6 +999,12 @@ func (f *FourslashTest) getCompletions(t *testing.T, userPreferences *lsutil.Use
defer reset()
}
result := sendRequest(t, f, lsproto.TextDocumentCompletionInfo, params)
+ // For performance, the server may return unsorted completion lists.
+ // The client is expected to sort them by SortText and then by Label.
+ // We are the client here.
+ if result.List != nil {
+ slices.SortStableFunc(result.List.Items, ls.CompareCompletionEntries)
+ }
return result.List
}
@@ -1075,7 +1082,7 @@ func (f *FourslashTest) verifyCompletionsItems(t *testing.T, prefix string, actu
t.Fatal(prefix + "Expected exact completion list but also specified 'unsorted'.")
}
if len(actual) != len(expected.Exact) {
- t.Fatalf(prefix+"Expected %d exact completion items but got %d: %s", len(expected.Exact), len(actual), cmp.Diff(actual, expected.Exact))
+ t.Fatalf(prefix+"Expected %d exact completion items but got %d.", len(expected.Exact), len(actual))
}
if len(actual) > 0 {
f.verifyCompletionsAreExactly(t, prefix, actual, expected.Exact)
@@ -1098,13 +1105,13 @@ func (f *FourslashTest) verifyCompletionsItems(t *testing.T, prefix string, actu
case string:
_, ok := nameToActualItems[item]
if !ok {
- t.Fatalf("%sLabel '%s' not found in actual items. Actual items: %s", prefix, item, cmp.Diff(actual, nil))
+ t.Fatalf("%sLabel '%s' not found in actual items.", prefix, item)
}
delete(nameToActualItems, item)
case *lsproto.CompletionItem:
actualItems, ok := nameToActualItems[item.Label]
if !ok {
- t.Fatalf("%sLabel '%s' not found in actual items. Actual items: %s", prefix, item.Label, cmp.Diff(actual, nil))
+ t.Fatalf("%sLabel '%s' not found in actual items.", prefix, item.Label)
}
actualItem := actualItems[0]
actualItems = actualItems[1:]
@@ -1130,12 +1137,12 @@ func (f *FourslashTest) verifyCompletionsItems(t *testing.T, prefix string, actu
case string:
_, ok := nameToActualItems[item]
if !ok {
- t.Fatalf("%sLabel '%s' not found in actual items. Actual items: %s", prefix, item, cmp.Diff(actual, nil))
+ t.Fatalf("%sLabel '%s' not found in actual items.", prefix, item)
}
case *lsproto.CompletionItem:
actualItems, ok := nameToActualItems[item.Label]
if !ok {
- t.Fatalf("%sLabel '%s' not found in actual items. Actual items: %s", prefix, item.Label, cmp.Diff(actual, nil))
+ t.Fatalf("%sLabel '%s' not found in actual items.", prefix, item.Label)
}
actualItem := actualItems[0]
actualItems = actualItems[1:]
@@ -1152,7 +1159,7 @@ func (f *FourslashTest) verifyCompletionsItems(t *testing.T, prefix string, actu
}
for _, exclude := range expected.Excludes {
if _, ok := nameToActualItems[exclude]; ok {
- t.Fatalf("%sLabel '%s' should not be in actual items but was found. Actual items: %s", prefix, exclude, cmp.Diff(actual, nil))
+ t.Fatalf("%sLabel '%s' should not be in actual items but was found.", prefix, exclude)
}
}
}
@@ -1190,22 +1197,22 @@ var (
)
func (f *FourslashTest) verifyCompletionItem(t *testing.T, prefix string, actual *lsproto.CompletionItem, expected *lsproto.CompletionItem) {
- var actualAutoImportData, expectedAutoImportData *lsproto.AutoImportData
+ var actualAutoImportFix, expectedAutoImportFix *lsproto.AutoImportFix
if actual.Data != nil {
- actualAutoImportData = actual.Data.AutoImport
+ actualAutoImportFix = actual.Data.AutoImport
}
if expected.Data != nil {
- expectedAutoImportData = expected.Data.AutoImport
+ expectedAutoImportFix = expected.Data.AutoImport
}
- if (actualAutoImportData == nil) != (expectedAutoImportData == nil) {
+ if (actualAutoImportFix == nil) != (expectedAutoImportFix == nil) {
t.Fatal(prefix + "Mismatch in auto-import data presence")
}
- if expected.Detail != nil || expected.Documentation != nil || actualAutoImportData != nil {
+ if expected.Detail != nil || expected.Documentation != nil || actualAutoImportFix != nil {
actual = f.resolveCompletionItem(t, actual)
}
- if actualAutoImportData != nil {
+ if actualAutoImportFix != nil {
assertDeepEqual(t, actual, expected, prefix, autoImportIgnoreOpts)
if expected.AdditionalTextEdits == AnyTextEdits {
assert.Check(t, actual.AdditionalTextEdits != nil && len(*actual.AdditionalTextEdits) > 0, prefix+" Expected non-nil AdditionalTextEdits for auto-import completion item")
@@ -1214,7 +1221,7 @@ func (f *FourslashTest) verifyCompletionItem(t *testing.T, prefix string, actual
assertDeepEqual(t, actual.LabelDetails, expected.LabelDetails, prefix+" LabelDetails mismatch")
}
- assert.Equal(t, actualAutoImportData.ModuleSpecifier, expectedAutoImportData.ModuleSpecifier, prefix+" ModuleSpecifier mismatch")
+ assert.Equal(t, actualAutoImportFix.ModuleSpecifier, expectedAutoImportFix.ModuleSpecifier, prefix+" ModuleSpecifier mismatch")
} else {
assertDeepEqual(t, actual, expected, prefix, completionIgnoreOpts)
}
@@ -1257,7 +1264,7 @@ func assertDeepEqual(t *testing.T, actual any, expected any, prefix string, opts
type ApplyCodeActionFromCompletionOptions struct {
Name string
Source string
- AutoImportData *lsproto.AutoImportData
+ AutoImportFix *lsproto.AutoImportFix
Description string
NewFileContent *string
NewRangeContent *string
@@ -1281,13 +1288,14 @@ func (f *FourslashTest) VerifyApplyCodeActionFromCompletion(t *testing.T, marker
if item.Label != options.Name || item.Data == nil {
return false
}
+
data := item.Data
- if options.AutoImportData != nil {
- return data.AutoImport != nil && ((data.AutoImport.FileName == options.AutoImportData.FileName) &&
- (options.AutoImportData.ModuleSpecifier == "" || data.AutoImport.ModuleSpecifier == options.AutoImportData.ModuleSpecifier) &&
- (options.AutoImportData.ExportName == "" || data.AutoImport.ExportName == options.AutoImportData.ExportName) &&
- (options.AutoImportData.AmbientModuleName == "" || data.AutoImport.AmbientModuleName == options.AutoImportData.AmbientModuleName) &&
- data.AutoImport.IsPackageJsonImport == options.AutoImportData.IsPackageJsonImport)
+ if data == nil {
+ return false
+ }
+ if options.AutoImportFix != nil {
+ return data.AutoImport != nil &&
+ (options.AutoImportFix.ModuleSpecifier == "" || data.AutoImport.ModuleSpecifier == options.AutoImportFix.ModuleSpecifier)
}
if data.AutoImport == nil && data.Source != "" && data.Source == options.Source {
return true
@@ -1314,6 +1322,7 @@ func (f *FourslashTest) VerifyApplyCodeActionFromCompletion(t *testing.T, marker
}
func (f *FourslashTest) VerifyImportFixAtPosition(t *testing.T, expectedTexts []string, preferences *lsutil.UserPreferences) {
+ t.Helper()
fileName := f.activeFilename
ranges := f.Ranges()
var filteredRanges []*RangeMarker
@@ -1445,6 +1454,7 @@ func (f *FourslashTest) VerifyImportFixModuleSpecifiers(
expectedModuleSpecifiers []string,
preferences *lsutil.UserPreferences,
) {
+ t.Helper()
f.GoToMarker(t, markerName)
if preferences != nil {
diff --git a/internal/fourslash/statebaseline.go b/internal/fourslash/statebaseline.go
index df2ac9189b..68fa153525 100644
--- a/internal/fourslash/statebaseline.go
+++ b/internal/fourslash/statebaseline.go
@@ -339,7 +339,7 @@ func (f *FourslashTest) printOpenFilesDiff(t *testing.T, snapshot *project.Snaps
options := diffTableOptions{indent: " ", sortKeys: true}
for fileName := range f.openFiles {
path := tspath.ToPath(fileName, "/", f.vfs.UseCaseSensitiveFileNames())
- defaultProject := snapshot.ProjectCollection.GetDefaultProject(fileName, path)
+ defaultProject := snapshot.ProjectCollection.GetDefaultProject(path)
newFileInfo := &openFileInfo{}
if defaultProject != nil {
newFileInfo.defaultProjectName = defaultProject.Name()
diff --git a/internal/fourslash/tests/autoImportErrorMixedExportKinds_test.go b/internal/fourslash/tests/autoImportErrorMixedExportKinds_test.go
new file mode 100644
index 0000000000..0c47738f08
--- /dev/null
+++ b/internal/fourslash/tests/autoImportErrorMixedExportKinds_test.go
@@ -0,0 +1,28 @@
+package fourslash_test
+
+import (
+ "testing"
+
+ "github.com/microsoft/typescript-go/internal/fourslash"
+ "github.com/microsoft/typescript-go/internal/testutil"
+)
+
+func TestAutoImportErrorMixedExportKinds(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ const content = `// @Filename: a.ts
+export function foo(): number {
+ return 10
+}
+
+const bar = 20;
+export { bar as foo };
+
+// @Filename: b.ts
+foo/**/
+`
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
+ defer done()
+ // Verify we don't crash from the mixed exports
+ f.BaselineAutoImportsCompletions(t, []string{""})
+}
diff --git a/internal/fourslash/tests/autoImportModuleAugmentation_test.go b/internal/fourslash/tests/autoImportModuleAugmentation_test.go
new file mode 100644
index 0000000000..97472535b2
--- /dev/null
+++ b/internal/fourslash/tests/autoImportModuleAugmentation_test.go
@@ -0,0 +1,30 @@
+package fourslash_test
+
+import (
+ "testing"
+
+ "github.com/microsoft/typescript-go/internal/fourslash"
+ "github.com/microsoft/typescript-go/internal/testutil"
+)
+
+func TestAutoImportModuleAugmentation(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ const content = `// @Filename: /a.ts
+export interface Foo {
+ x: number;
+}
+
+// @Filename: /b.ts
+export {};
+declare module "./a" {
+ export const Foo: any;
+}
+
+// @Filename: /c.ts
+Foo/**/
+`
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
+ defer done()
+ f.BaselineAutoImportsCompletions(t, []string{""})
+}
diff --git a/internal/fourslash/tests/gen/autoImportFileExcludePatterns2_test.go b/internal/fourslash/tests/gen/autoImportFileExcludePatterns2_test.go
index eb26fe8703..d8b4d253a2 100644
--- a/internal/fourslash/tests/gen/autoImportFileExcludePatterns2_test.go
+++ b/internal/fourslash/tests/gen/autoImportFileExcludePatterns2_test.go
@@ -41,7 +41,7 @@ Button/**/`
&lsproto.CompletionItem{
Label: "Button",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./lib/main",
},
},
diff --git a/internal/fourslash/tests/gen/autoImportFileExcludePatterns3_test.go b/internal/fourslash/tests/gen/autoImportFileExcludePatterns3_test.go
index 084d51881f..45f1c56285 100644
--- a/internal/fourslash/tests/gen/autoImportFileExcludePatterns3_test.go
+++ b/internal/fourslash/tests/gen/autoImportFileExcludePatterns3_test.go
@@ -39,7 +39,7 @@ declare module "foo" {
&lsproto.CompletionItem{
Label: "x",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "foo",
},
},
@@ -49,7 +49,7 @@ declare module "foo" {
&lsproto.CompletionItem{
Label: "y",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "foo",
},
},
diff --git a/internal/fourslash/tests/gen/autoImportModuleNone2_test.go b/internal/fourslash/tests/gen/autoImportModuleNone2_test.go
index 1a60437e36..1bacc49bc9 100644
--- a/internal/fourslash/tests/gen/autoImportModuleNone2_test.go
+++ b/internal/fourslash/tests/gen/autoImportModuleNone2_test.go
@@ -34,7 +34,7 @@ export const x: number;
&lsproto.CompletionItem{
Label: "x",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "dep",
},
},
diff --git a/internal/fourslash/tests/gen/autoImportPathsAliasesAndBarrels_test.go b/internal/fourslash/tests/gen/autoImportPathsAliasesAndBarrels_test.go
index 3cebefb1c4..44942de609 100644
--- a/internal/fourslash/tests/gen/autoImportPathsAliasesAndBarrels_test.go
+++ b/internal/fourslash/tests/gen/autoImportPathsAliasesAndBarrels_test.go
@@ -51,7 +51,7 @@ func TestAutoImportPathsAliasesAndBarrels(t *testing.T) {
&lsproto.CompletionItem{
Label: "Thing2A",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./thing2A",
},
},
@@ -61,7 +61,7 @@ func TestAutoImportPathsAliasesAndBarrels(t *testing.T) {
&lsproto.CompletionItem{
Label: "Thing1B",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "~/dirB",
},
},
@@ -71,7 +71,7 @@ func TestAutoImportPathsAliasesAndBarrels(t *testing.T) {
&lsproto.CompletionItem{
Label: "Thing2B",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "~/dirB",
},
},
diff --git a/internal/fourslash/tests/gen/autoImportProvider6_test.go b/internal/fourslash/tests/gen/autoImportProvider6_test.go
index d520ba516e..96f2d7096d 100644
--- a/internal/fourslash/tests/gen/autoImportProvider6_test.go
+++ b/internal/fourslash/tests/gen/autoImportProvider6_test.go
@@ -40,7 +40,7 @@ Component/**/`
Label: "Component",
AdditionalTextEdits: fourslash.AnyTextEdits,
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "react",
},
},
diff --git a/internal/fourslash/tests/gen/autoImportProvider_exportMap1_test.go b/internal/fourslash/tests/gen/autoImportProvider_exportMap1_test.go
index 8bccceaf8d..f09e4ed228 100644
--- a/internal/fourslash/tests/gen/autoImportProvider_exportMap1_test.go
+++ b/internal/fourslash/tests/gen/autoImportProvider_exportMap1_test.go
@@ -62,7 +62,7 @@ fooFrom/**/`
&lsproto.CompletionItem{
Label: "fooFromIndex",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "dependency",
},
},
@@ -72,7 +72,7 @@ fooFrom/**/`
&lsproto.CompletionItem{
Label: "fooFromLol",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "dependency/lol",
},
},
diff --git a/internal/fourslash/tests/gen/autoImportProvider_exportMap2_test.go b/internal/fourslash/tests/gen/autoImportProvider_exportMap2_test.go
index abab093258..3808434490 100644
--- a/internal/fourslash/tests/gen/autoImportProvider_exportMap2_test.go
+++ b/internal/fourslash/tests/gen/autoImportProvider_exportMap2_test.go
@@ -65,7 +65,7 @@ fooFrom/**/`
&lsproto.CompletionItem{
Label: "fooFromIndex",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "dependency",
},
},
diff --git a/internal/fourslash/tests/gen/autoImportProvider_exportMap3_test.go b/internal/fourslash/tests/gen/autoImportProvider_exportMap3_test.go
index 9257feb475..faa80cfab3 100644
--- a/internal/fourslash/tests/gen/autoImportProvider_exportMap3_test.go
+++ b/internal/fourslash/tests/gen/autoImportProvider_exportMap3_test.go
@@ -55,7 +55,7 @@ fooFrom/**/`
&lsproto.CompletionItem{
Label: "fooFromLol",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "dependency",
},
},
diff --git a/internal/fourslash/tests/gen/autoImportProvider_exportMap4_test.go b/internal/fourslash/tests/gen/autoImportProvider_exportMap4_test.go
index f3fd417586..fa1ba7871e 100644
--- a/internal/fourslash/tests/gen/autoImportProvider_exportMap4_test.go
+++ b/internal/fourslash/tests/gen/autoImportProvider_exportMap4_test.go
@@ -58,7 +58,7 @@ fooFrom/**/`
&lsproto.CompletionItem{
Label: "fooFromIndex",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "dependency",
},
},
diff --git a/internal/fourslash/tests/gen/autoImportProvider_exportMap5_test.go b/internal/fourslash/tests/gen/autoImportProvider_exportMap5_test.go
index e407094ba9..a5f1037fcb 100644
--- a/internal/fourslash/tests/gen/autoImportProvider_exportMap5_test.go
+++ b/internal/fourslash/tests/gen/autoImportProvider_exportMap5_test.go
@@ -73,7 +73,7 @@ fooFrom/**/`
&lsproto.CompletionItem{
Label: "fooFromIndex",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "dependency",
},
},
@@ -83,7 +83,7 @@ fooFrom/**/`
&lsproto.CompletionItem{
Label: "fooFromLol",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "dependency/lol",
},
},
diff --git a/internal/fourslash/tests/gen/autoImportProvider_exportMap6_test.go b/internal/fourslash/tests/gen/autoImportProvider_exportMap6_test.go
index 43a8389555..be9f2dd8c5 100644
--- a/internal/fourslash/tests/gen/autoImportProvider_exportMap6_test.go
+++ b/internal/fourslash/tests/gen/autoImportProvider_exportMap6_test.go
@@ -80,7 +80,7 @@ fooFrom/**/`
&lsproto.CompletionItem{
Label: "fooFromIndex",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "dependency",
},
},
@@ -90,7 +90,7 @@ fooFrom/**/`
&lsproto.CompletionItem{
Label: "fooFromLol",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "dependency/lol",
},
},
diff --git a/internal/fourslash/tests/gen/autoImportProvider_exportMap7_test.go b/internal/fourslash/tests/gen/autoImportProvider_exportMap7_test.go
index 03661a308e..2d15900d22 100644
--- a/internal/fourslash/tests/gen/autoImportProvider_exportMap7_test.go
+++ b/internal/fourslash/tests/gen/autoImportProvider_exportMap7_test.go
@@ -64,7 +64,7 @@ fooFrom/**/`
&lsproto.CompletionItem{
Label: "fooFromIndex",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "dependency",
},
},
@@ -74,7 +74,7 @@ fooFrom/**/`
&lsproto.CompletionItem{
Label: "fooFromLol",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "dependency/lol",
},
},
diff --git a/internal/fourslash/tests/gen/autoImportProvider_exportMap8_test.go b/internal/fourslash/tests/gen/autoImportProvider_exportMap8_test.go
index b274b1a89a..3fa0ee44cd 100644
--- a/internal/fourslash/tests/gen/autoImportProvider_exportMap8_test.go
+++ b/internal/fourslash/tests/gen/autoImportProvider_exportMap8_test.go
@@ -64,7 +64,7 @@ fooFrom/*mts*/`
&lsproto.CompletionItem{
Label: "fooFromLol",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "dependency/lol",
},
},
@@ -89,7 +89,7 @@ fooFrom/*mts*/`
&lsproto.CompletionItem{
Label: "fooFromIndex",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "dependency/lol",
},
},
diff --git a/internal/fourslash/tests/gen/autoImportProvider_exportMap9_test.go b/internal/fourslash/tests/gen/autoImportProvider_exportMap9_test.go
index b04572f70e..079596b583 100644
--- a/internal/fourslash/tests/gen/autoImportProvider_exportMap9_test.go
+++ b/internal/fourslash/tests/gen/autoImportProvider_exportMap9_test.go
@@ -59,7 +59,7 @@ fooFrom/**/`
&lsproto.CompletionItem{
Label: "fooFromIndex",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "dependency/lol",
},
},
diff --git a/internal/fourslash/tests/gen/autoImportProvider_globalTypingsCache_test.go b/internal/fourslash/tests/gen/autoImportProvider_globalTypingsCache_test.go
index 2f3de96fcf..5231db7143 100644
--- a/internal/fourslash/tests/gen/autoImportProvider_globalTypingsCache_test.go
+++ b/internal/fourslash/tests/gen/autoImportProvider_globalTypingsCache_test.go
@@ -46,7 +46,7 @@ BrowserRouter/**/`
&lsproto.CompletionItem{
Label: "BrowserRouterFromDts",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "react-router-dom",
},
},
diff --git a/internal/fourslash/tests/gen/autoImportProvider_namespaceSameNameAsIntrinsic_test.go b/internal/fourslash/tests/gen/autoImportProvider_namespaceSameNameAsIntrinsic_test.go
index 9e93cfb541..13ed373b8e 100644
--- a/internal/fourslash/tests/gen/autoImportProvider_namespaceSameNameAsIntrinsic_test.go
+++ b/internal/fourslash/tests/gen/autoImportProvider_namespaceSameNameAsIntrinsic_test.go
@@ -47,7 +47,7 @@ type A = { name: string/**/ }`
Label: "string",
SortText: PtrTo(string(ls.SortTextAutoImportSuggestions)),
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "fp-ts",
},
},
diff --git a/internal/fourslash/tests/gen/autoImportProvider_wildcardExports1_test.go b/internal/fourslash/tests/gen/autoImportProvider_wildcardExports1_test.go
index c191718112..e725877fce 100644
--- a/internal/fourslash/tests/gen/autoImportProvider_wildcardExports1_test.go
+++ b/internal/fourslash/tests/gen/autoImportProvider_wildcardExports1_test.go
@@ -68,7 +68,7 @@ export const d1: number;
&lsproto.CompletionItem{
Label: "a1",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "pkg/a1",
},
},
@@ -78,7 +78,7 @@ export const d1: number;
&lsproto.CompletionItem{
Label: "b1",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "pkg/b/b1.js",
},
},
@@ -88,7 +88,7 @@ export const d1: number;
&lsproto.CompletionItem{
Label: "c1",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "pkg/c/c1.js",
},
},
@@ -98,7 +98,7 @@ export const d1: number;
&lsproto.CompletionItem{
Label: "c2",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "pkg/c/subfolder/c2.mjs",
},
},
@@ -108,7 +108,7 @@ export const d1: number;
&lsproto.CompletionItem{
Label: "d1",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "pkg/d/d1",
},
},
diff --git a/internal/fourslash/tests/gen/autoImportProvider_wildcardExports2_test.go b/internal/fourslash/tests/gen/autoImportProvider_wildcardExports2_test.go
index 213223ca54..b8aa2d3a94 100644
--- a/internal/fourslash/tests/gen/autoImportProvider_wildcardExports2_test.go
+++ b/internal/fourslash/tests/gen/autoImportProvider_wildcardExports2_test.go
@@ -56,7 +56,7 @@ export function test(): void;
&lsproto.CompletionItem{
Label: "test",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "pkg/core/test",
},
},
diff --git a/internal/fourslash/tests/gen/autoImportProvider_wildcardExports3_test.go b/internal/fourslash/tests/gen/autoImportProvider_wildcardExports3_test.go
index 576947151d..f2df0bee2b 100644
--- a/internal/fourslash/tests/gen/autoImportProvider_wildcardExports3_test.go
+++ b/internal/fourslash/tests/gen/autoImportProvider_wildcardExports3_test.go
@@ -60,7 +60,7 @@ export const Card = () => null;
&lsproto.CompletionItem{
Label: "Card",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "@repo/ui/Card",
},
},
diff --git a/internal/fourslash/tests/gen/autoImportReExportFromAmbientModule_test.go b/internal/fourslash/tests/gen/autoImportReExportFromAmbientModule_test.go
index ccbc98af20..7bc0cd8167 100644
--- a/internal/fourslash/tests/gen/autoImportReExportFromAmbientModule_test.go
+++ b/internal/fourslash/tests/gen/autoImportReExportFromAmbientModule_test.go
@@ -42,7 +42,7 @@ access/**/`
&lsproto.CompletionItem{
Label: "accessSync",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "fs",
},
},
@@ -52,7 +52,7 @@ access/**/`
&lsproto.CompletionItem{
Label: "accessSync",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "fs-extra",
},
},
@@ -69,9 +69,7 @@ access/**/`
NewFileContent: PtrTo(`import { accessSync } from "fs-extra";
access`),
- AutoImportData: &lsproto.AutoImportData{
- ExportName: "accessSync",
- FileName: "/home/src/workspaces/project/node_modules/@types/fs-extra/index.d.ts",
+ AutoImportFix: &lsproto.AutoImportFix{
ModuleSpecifier: "fs-extra",
},
})
diff --git a/internal/fourslash/tests/gen/autoImportSameNameDefaultExported_test.go b/internal/fourslash/tests/gen/autoImportSameNameDefaultExported_test.go
index e913cbc444..4c1ab7852e 100644
--- a/internal/fourslash/tests/gen/autoImportSameNameDefaultExported_test.go
+++ b/internal/fourslash/tests/gen/autoImportSameNameDefaultExported_test.go
@@ -37,7 +37,7 @@ Table/**/`
&lsproto.CompletionItem{
Label: "Table",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "antd",
},
},
@@ -47,7 +47,7 @@ Table/**/`
&lsproto.CompletionItem{
Label: "Table",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "rc-table",
},
},
diff --git a/internal/fourslash/tests/gen/autoImportSortCaseSensitivity2_test.go b/internal/fourslash/tests/gen/autoImportSortCaseSensitivity2_test.go
index a731fa3a69..51a0ea271c 100644
--- a/internal/fourslash/tests/gen/autoImportSortCaseSensitivity2_test.go
+++ b/internal/fourslash/tests/gen/autoImportSortCaseSensitivity2_test.go
@@ -35,7 +35,7 @@ f/**/;`
&lsproto.CompletionItem{
Label: "foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
diff --git a/internal/fourslash/tests/gen/autoImportSpecifierExcludeRegexes1_test.go b/internal/fourslash/tests/gen/autoImportSpecifierExcludeRegexes1_test.go
index b5f6ad8dbb..da57ab8751 100644
--- a/internal/fourslash/tests/gen/autoImportSpecifierExcludeRegexes1_test.go
+++ b/internal/fourslash/tests/gen/autoImportSpecifierExcludeRegexes1_test.go
@@ -48,7 +48,7 @@ x/**/`
&lsproto.CompletionItem{
Label: "x",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "ambient",
},
},
@@ -58,7 +58,7 @@ x/**/`
&lsproto.CompletionItem{
Label: "x",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "ambient/utils",
},
},
diff --git a/internal/fourslash/tests/gen/autoImportTypeOnlyPreferred1_test.go b/internal/fourslash/tests/gen/autoImportTypeOnlyPreferred1_test.go
index ce2a39b446..2efa76c718 100644
--- a/internal/fourslash/tests/gen/autoImportTypeOnlyPreferred1_test.go
+++ b/internal/fourslash/tests/gen/autoImportTypeOnlyPreferred1_test.go
@@ -42,7 +42,7 @@ export interface VFS {
&lsproto.CompletionItem{
Label: "ts",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./ts",
},
},
diff --git a/internal/fourslash/tests/gen/autoImportVerbatimTypeOnly1_test.go b/internal/fourslash/tests/gen/autoImportVerbatimTypeOnly1_test.go
index 8ae8ea3969..1a1e8ab6d8 100644
--- a/internal/fourslash/tests/gen/autoImportVerbatimTypeOnly1_test.go
+++ b/internal/fourslash/tests/gen/autoImportVerbatimTypeOnly1_test.go
@@ -27,9 +27,7 @@ const x: /**/`
Name: "I",
Source: "./mod",
Description: "Add import from \"./mod.js\"",
- AutoImportData: &lsproto.AutoImportData{
- ExportName: "I",
- FileName: "/mod.ts",
+ AutoImportFix: &lsproto.AutoImportFix{
ModuleSpecifier: "./mod.js",
},
NewFileContent: PtrTo(`import type { I } from "./mod.js";
@@ -41,9 +39,7 @@ const x: `),
Name: "C",
Source: "./mod",
Description: "Update import from \"./mod.js\"",
- AutoImportData: &lsproto.AutoImportData{
- ExportName: "C",
- FileName: "/mod.ts",
+ AutoImportFix: &lsproto.AutoImportFix{
ModuleSpecifier: "./mod.js",
},
NewFileContent: PtrTo(`import { C, type I } from "./mod.js";
diff --git a/internal/fourslash/tests/gen/completionForObjectProperty_test.go b/internal/fourslash/tests/gen/completionForObjectProperty_test.go
index 53139f0e03..be41873772 100644
--- a/internal/fourslash/tests/gen/completionForObjectProperty_test.go
+++ b/internal/fourslash/tests/gen/completionForObjectProperty_test.go
@@ -45,7 +45,7 @@ const test8: { foo: string } = { foo/*8*/ }`
&lsproto.CompletionItem{
Label: "foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
@@ -66,7 +66,7 @@ const test8: { foo: string } = { foo/*8*/ }`
&lsproto.CompletionItem{
Label: "foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
@@ -87,7 +87,7 @@ const test8: { foo: string } = { foo/*8*/ }`
&lsproto.CompletionItem{
Label: "foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
@@ -108,7 +108,7 @@ const test8: { foo: string } = { foo/*8*/ }`
&lsproto.CompletionItem{
Label: "foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
@@ -129,7 +129,7 @@ const test8: { foo: string } = { foo/*8*/ }`
&lsproto.CompletionItem{
Label: "foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
@@ -150,7 +150,7 @@ const test8: { foo: string } = { foo/*8*/ }`
&lsproto.CompletionItem{
Label: "foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
diff --git a/internal/fourslash/tests/gen/completionPropertyShorthandForObjectLiteral5_test.go b/internal/fourslash/tests/gen/completionPropertyShorthandForObjectLiteral5_test.go
index 88327b3e1e..f28d7e3307 100644
--- a/internal/fourslash/tests/gen/completionPropertyShorthandForObjectLiteral5_test.go
+++ b/internal/fourslash/tests/gen/completionPropertyShorthandForObjectLiteral5_test.go
@@ -33,7 +33,7 @@ const obj = { exp/**/`
&lsproto.CompletionItem{
Label: "exportedConstant",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImportBaseUrl_test.go b/internal/fourslash/tests/gen/completionsImportBaseUrl_test.go
index 9153cfd690..3942239797 100644
--- a/internal/fourslash/tests/gen/completionsImportBaseUrl_test.go
+++ b/internal/fourslash/tests/gen/completionsImportBaseUrl_test.go
@@ -38,7 +38,7 @@ fo/**/`
&lsproto.CompletionItem{
Label: "foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImportDefaultExportCrash2_test.go b/internal/fourslash/tests/gen/completionsImportDefaultExportCrash2_test.go
index b24c1842ca..de13c022b8 100644
--- a/internal/fourslash/tests/gen/completionsImportDefaultExportCrash2_test.go
+++ b/internal/fourslash/tests/gen/completionsImportDefaultExportCrash2_test.go
@@ -56,7 +56,7 @@ export default methods.$;
Label: "$",
AdditionalTextEdits: fourslash.AnyTextEdits,
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "dom7",
},
},
@@ -66,7 +66,7 @@ export default methods.$;
Label: "Dom7",
AdditionalTextEdits: fourslash.AnyTextEdits,
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./dom7",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImportPathsConflict_test.go b/internal/fourslash/tests/gen/completionsImportPathsConflict_test.go
index 9c763b8b63..75b9061bfe 100644
--- a/internal/fourslash/tests/gen/completionsImportPathsConflict_test.go
+++ b/internal/fourslash/tests/gen/completionsImportPathsConflict_test.go
@@ -44,7 +44,7 @@ import {} from "@reduxjs/toolkit";
&lsproto.CompletionItem{
Label: "configureStore",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "@reduxjs/toolkit",
},
},
@@ -57,9 +57,7 @@ import {} from "@reduxjs/toolkit";
f.VerifyApplyCodeActionFromCompletion(t, PtrTo(""), &fourslash.ApplyCodeActionFromCompletionOptions{
Name: "configureStore",
Source: "@reduxjs/toolkit",
- AutoImportData: &lsproto.AutoImportData{
- ExportName: "configureStore",
- FileName: "/src/configureStore.ts",
+ AutoImportFix: &lsproto.AutoImportFix{
ModuleSpecifier: "@reduxjs/toolkit",
},
Description: "Update import from \"@reduxjs/toolkit\"",
diff --git a/internal/fourslash/tests/gen/completionsImportTypeKeyword_test.go b/internal/fourslash/tests/gen/completionsImportTypeKeyword_test.go
index f8b0c34309..8090966cdc 100644
--- a/internal/fourslash/tests/gen/completionsImportTypeKeyword_test.go
+++ b/internal/fourslash/tests/gen/completionsImportTypeKeyword_test.go
@@ -38,7 +38,7 @@ type/**/`
&lsproto.CompletionItem{
Label: "type",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "os",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_46332_test.go b/internal/fourslash/tests/gen/completionsImport_46332_test.go
index 1387d19c84..d05556844d 100644
--- a/internal/fourslash/tests/gen/completionsImport_46332_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_46332_test.go
@@ -78,7 +78,7 @@ ref/**/`
&lsproto.CompletionItem{
Label: "ref",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "vue",
},
},
@@ -89,13 +89,10 @@ ref/**/`
},
})
f.VerifyApplyCodeActionFromCompletion(t, PtrTo(""), &fourslash.ApplyCodeActionFromCompletionOptions{
- Name: "ref",
- Source: "vue",
- Description: "Update import from \"vue\"",
- AutoImportData: &lsproto.AutoImportData{
- ExportName: "ref",
- FileName: "/node_modules/vue/dist/vue.d.ts",
- },
+ Name: "ref",
+ Source: "vue",
+ Description: "Update import from \"vue\"",
+ AutoImportFix: &lsproto.AutoImportFix{},
NewFileContent: PtrTo(`import { ref } from "vue";
ref`),
})
diff --git a/internal/fourslash/tests/gen/completionsImport_ambient_test.go b/internal/fourslash/tests/gen/completionsImport_ambient_test.go
index 7671627582..f671b2f52f 100644
--- a/internal/fourslash/tests/gen/completionsImport_ambient_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_ambient_test.go
@@ -46,7 +46,7 @@ Ba/**/`
&lsproto.CompletionItem{
Label: "Bar",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "path1",
},
},
@@ -56,7 +56,7 @@ Ba/**/`
&lsproto.CompletionItem{
Label: "Bar",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "path2longer",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_augmentation_test.go b/internal/fourslash/tests/gen/completionsImport_augmentation_test.go
index 164dcb2a29..84020d3a06 100644
--- a/internal/fourslash/tests/gen/completionsImport_augmentation_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_augmentation_test.go
@@ -37,7 +37,7 @@ declare module "./a" {
Label: "foo",
Detail: PtrTo("const foo: 0"),
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
@@ -48,7 +48,7 @@ declare module "./a" {
Label: "bar",
Detail: PtrTo("const bar: 0"),
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_compilerOptionsModule_test.go b/internal/fourslash/tests/gen/completionsImport_compilerOptionsModule_test.go
index ed7c0aef83..6fb86812cc 100644
--- a/internal/fourslash/tests/gen/completionsImport_compilerOptionsModule_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_compilerOptionsModule_test.go
@@ -51,7 +51,7 @@ fo/*dts*/`
&lsproto.CompletionItem{
Label: "foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "a",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_defaultFalsePositive_test.go b/internal/fourslash/tests/gen/completionsImport_defaultFalsePositive_test.go
index 96f09c062b..d802463651 100644
--- a/internal/fourslash/tests/gen/completionsImport_defaultFalsePositive_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_defaultFalsePositive_test.go
@@ -35,7 +35,7 @@ conca/**/`
&lsproto.CompletionItem{
Label: "concat",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "bar/concat",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_default_addToNamedImports_test.go b/internal/fourslash/tests/gen/completionsImport_default_addToNamedImports_test.go
index 48cc77bc86..c26168bde7 100644
--- a/internal/fourslash/tests/gen/completionsImport_default_addToNamedImports_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_default_addToNamedImports_test.go
@@ -33,7 +33,7 @@ f/**/;`
&lsproto.CompletionItem{
Label: "foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_default_addToNamespaceImport_test.go b/internal/fourslash/tests/gen/completionsImport_default_addToNamespaceImport_test.go
index aa29c29cc7..822b42bd86 100644
--- a/internal/fourslash/tests/gen/completionsImport_default_addToNamespaceImport_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_default_addToNamespaceImport_test.go
@@ -32,7 +32,7 @@ f/**/;`
&lsproto.CompletionItem{
Label: "foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_default_alreadyExistedWithRename_test.go b/internal/fourslash/tests/gen/completionsImport_default_alreadyExistedWithRename_test.go
index 45ab15e30c..02bf02d012 100644
--- a/internal/fourslash/tests/gen/completionsImport_default_alreadyExistedWithRename_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_default_alreadyExistedWithRename_test.go
@@ -32,7 +32,7 @@ f/**/;`
&lsproto.CompletionItem{
Label: "foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_default_anonymous_test.go b/internal/fourslash/tests/gen/completionsImport_default_anonymous_test.go
index a424df0f8e..10b5d808f4 100644
--- a/internal/fourslash/tests/gen/completionsImport_default_anonymous_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_default_anonymous_test.go
@@ -46,7 +46,7 @@ fooB/*1*/`
&lsproto.CompletionItem{
Label: "fooBar",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./foo-bar",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_default_didNotExistBefore_test.go b/internal/fourslash/tests/gen/completionsImport_default_didNotExistBefore_test.go
index 572707ec34..e6a7fa7531 100644
--- a/internal/fourslash/tests/gen/completionsImport_default_didNotExistBefore_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_default_didNotExistBefore_test.go
@@ -32,7 +32,7 @@ f/**/;`
&lsproto.CompletionItem{
Label: "foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_default_exportDefaultIdentifier_test.go b/internal/fourslash/tests/gen/completionsImport_default_exportDefaultIdentifier_test.go
index 0e8bd33cb9..a3f255dda0 100644
--- a/internal/fourslash/tests/gen/completionsImport_default_exportDefaultIdentifier_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_default_exportDefaultIdentifier_test.go
@@ -34,7 +34,7 @@ f/**/;`
&lsproto.CompletionItem{
Label: "foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_default_fromMergedDeclarations_test.go b/internal/fourslash/tests/gen/completionsImport_default_fromMergedDeclarations_test.go
index ffe65452de..4d1979efce 100644
--- a/internal/fourslash/tests/gen/completionsImport_default_fromMergedDeclarations_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_default_fromMergedDeclarations_test.go
@@ -38,7 +38,7 @@ declare module "m" {
&lsproto.CompletionItem{
Label: "M",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "m",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_default_reExport_test.go b/internal/fourslash/tests/gen/completionsImport_default_reExport_test.go
index 73aee66f6c..0a93ac1d2c 100644
--- a/internal/fourslash/tests/gen/completionsImport_default_reExport_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_default_reExport_test.go
@@ -42,7 +42,7 @@ export default foo.b;`
&lsproto.CompletionItem{
Label: "a",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./file1",
},
},
@@ -52,7 +52,7 @@ export default foo.b;`
&lsproto.CompletionItem{
Label: "b",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./file1",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_default_symbolName_test.go b/internal/fourslash/tests/gen/completionsImport_default_symbolName_test.go
index 0ee79308ec..0248f4d554 100644
--- a/internal/fourslash/tests/gen/completionsImport_default_symbolName_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_default_symbolName_test.go
@@ -41,7 +41,7 @@ R/*0*/`
Label: "RangeParser",
Kind: PtrTo(lsproto.CompletionItemKindFunction),
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "range-parser",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_details_withMisspelledName_test.go b/internal/fourslash/tests/gen/completionsImport_details_withMisspelledName_test.go
index b6489903c0..4a22ecd20b 100644
--- a/internal/fourslash/tests/gen/completionsImport_details_withMisspelledName_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_details_withMisspelledName_test.go
@@ -34,9 +34,7 @@ acb;`),
f.VerifyApplyCodeActionFromCompletion(t, PtrTo("2"), &fourslash.ApplyCodeActionFromCompletionOptions{
Name: "abc",
Source: "./a",
- AutoImportData: &lsproto.AutoImportData{
- ExportName: "abc",
- FileName: "/a.ts",
+ AutoImportFix: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
Description: "Add import from \"./a\"",
diff --git a/internal/fourslash/tests/gen/completionsImport_duplicatePackages_scopedTypesAndNotTypes_test.go b/internal/fourslash/tests/gen/completionsImport_duplicatePackages_scopedTypesAndNotTypes_test.go
index 5f0b5a1739..b429f47723 100644
--- a/internal/fourslash/tests/gen/completionsImport_duplicatePackages_scopedTypesAndNotTypes_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_duplicatePackages_scopedTypesAndNotTypes_test.go
@@ -52,7 +52,7 @@ import "react";
&lsproto.CompletionItem{
Label: "render",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "@scope/react-dom",
},
},
@@ -62,7 +62,7 @@ import "react";
&lsproto.CompletionItem{
Label: "useState",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "@scope/react",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_duplicatePackages_scopedTypes_test.go b/internal/fourslash/tests/gen/completionsImport_duplicatePackages_scopedTypes_test.go
index bce8f0f932..2a18d36022 100644
--- a/internal/fourslash/tests/gen/completionsImport_duplicatePackages_scopedTypes_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_duplicatePackages_scopedTypes_test.go
@@ -52,7 +52,7 @@ import "@scope/react";
&lsproto.CompletionItem{
Label: "render",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "@scope/react-dom",
},
},
@@ -62,7 +62,7 @@ import "@scope/react";
&lsproto.CompletionItem{
Label: "useState",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "@scope/react",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_duplicatePackages_scoped_test.go b/internal/fourslash/tests/gen/completionsImport_duplicatePackages_scoped_test.go
index ac34c3da44..7d9a607244 100644
--- a/internal/fourslash/tests/gen/completionsImport_duplicatePackages_scoped_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_duplicatePackages_scoped_test.go
@@ -52,7 +52,7 @@ import "@scope/react";
&lsproto.CompletionItem{
Label: "render",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "@scope/react-dom",
},
},
@@ -62,7 +62,7 @@ import "@scope/react";
&lsproto.CompletionItem{
Label: "useState",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "@scope/react",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_duplicatePackages_typesAndNotTypes_test.go b/internal/fourslash/tests/gen/completionsImport_duplicatePackages_typesAndNotTypes_test.go
index b61bd1e826..6eaadee5fc 100644
--- a/internal/fourslash/tests/gen/completionsImport_duplicatePackages_typesAndNotTypes_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_duplicatePackages_typesAndNotTypes_test.go
@@ -52,7 +52,7 @@ useState/**/`
&lsproto.CompletionItem{
Label: "useState",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "react",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_duplicatePackages_types_test.go b/internal/fourslash/tests/gen/completionsImport_duplicatePackages_types_test.go
index 682619b529..aa075ea68b 100644
--- a/internal/fourslash/tests/gen/completionsImport_duplicatePackages_types_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_duplicatePackages_types_test.go
@@ -52,7 +52,7 @@ import "react";
&lsproto.CompletionItem{
Label: "render",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "react-dom",
},
},
@@ -62,7 +62,7 @@ import "react";
&lsproto.CompletionItem{
Label: "useState",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "react",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_exportEqualsNamespace_noDuplicate_test.go b/internal/fourslash/tests/gen/completionsImport_exportEqualsNamespace_noDuplicate_test.go
index f88c4048eb..fbffa4dcaf 100644
--- a/internal/fourslash/tests/gen/completionsImport_exportEqualsNamespace_noDuplicate_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_exportEqualsNamespace_noDuplicate_test.go
@@ -40,7 +40,7 @@ import * as a from "a";
&lsproto.CompletionItem{
Label: "foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "a",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_exportEquals_anonymous_test.go b/internal/fourslash/tests/gen/completionsImport_exportEquals_anonymous_test.go
index db5eff488b..6fe56b7721 100644
--- a/internal/fourslash/tests/gen/completionsImport_exportEquals_anonymous_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_exportEquals_anonymous_test.go
@@ -49,7 +49,7 @@ fooB/*1*/`
&lsproto.CompletionItem{
Label: "fooBar",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./foo-bar",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_exportEquals_test.go b/internal/fourslash/tests/gen/completionsImport_exportEquals_test.go
index 4ef13ee441..cb18fea642 100644
--- a/internal/fourslash/tests/gen/completionsImport_exportEquals_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_exportEquals_test.go
@@ -39,7 +39,7 @@ let x: b/*1*/;`
&lsproto.CompletionItem{
Label: "a",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
@@ -60,7 +60,7 @@ let x: b/*1*/;`
&lsproto.CompletionItem{
Label: "b",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_filteredByInvalidPackageJson_direct_test.go b/internal/fourslash/tests/gen/completionsImport_filteredByInvalidPackageJson_direct_test.go
index a8eeaf953a..0ed45545ff 100644
--- a/internal/fourslash/tests/gen/completionsImport_filteredByInvalidPackageJson_direct_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_filteredByInvalidPackageJson_direct_test.go
@@ -52,7 +52,7 @@ const x = Re/**/`
Label: "React",
AdditionalTextEdits: fourslash.AnyTextEdits,
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "react",
},
},
@@ -62,7 +62,7 @@ const x = Re/**/`
Label: "ReactFake",
AdditionalTextEdits: fourslash.AnyTextEdits,
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "fake-react",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_filteredByPackageJson_@typesImplicit_test.go b/internal/fourslash/tests/gen/completionsImport_filteredByPackageJson_@typesImplicit_test.go
index 93cf19b77d..c39288af98 100644
--- a/internal/fourslash/tests/gen/completionsImport_filteredByPackageJson_@typesImplicit_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_filteredByPackageJson_@typesImplicit_test.go
@@ -49,7 +49,7 @@ const x = Re/**/`
Label: "React",
AdditionalTextEdits: fourslash.AnyTextEdits,
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "react",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_filteredByPackageJson_@typesOnly_test.go b/internal/fourslash/tests/gen/completionsImport_filteredByPackageJson_@typesOnly_test.go
index 06e6dd10ae..a009352c97 100644
--- a/internal/fourslash/tests/gen/completionsImport_filteredByPackageJson_@typesOnly_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_filteredByPackageJson_@typesOnly_test.go
@@ -49,7 +49,7 @@ const x = Re/**/`
Label: "React",
AdditionalTextEdits: fourslash.AnyTextEdits,
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "react",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_filteredByPackageJson_ambient_test.go b/internal/fourslash/tests/gen/completionsImport_filteredByPackageJson_ambient_test.go
index deb5424f10..a014c63301 100644
--- a/internal/fourslash/tests/gen/completionsImport_filteredByPackageJson_ambient_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_filteredByPackageJson_ambient_test.go
@@ -78,7 +78,7 @@ loca/*5*/`
&lsproto.CompletionItem{
Label: "agate",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "react-syntax-highlighter/sub",
},
},
@@ -99,7 +99,7 @@ loca/*5*/`
&lsproto.CompletionItem{
Label: "somethingElse",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "something-else",
},
},
@@ -120,7 +120,7 @@ loca/*5*/`
&lsproto.CompletionItem{
Label: "declaredBySomethingNotInPackageJson",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "declared-by-foo",
},
},
@@ -141,7 +141,7 @@ loca/*5*/`
&lsproto.CompletionItem{
Label: "local",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "local",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_filteredByPackageJson_direct_test.go b/internal/fourslash/tests/gen/completionsImport_filteredByPackageJson_direct_test.go
index 556c0ca835..f63f2af248 100644
--- a/internal/fourslash/tests/gen/completionsImport_filteredByPackageJson_direct_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_filteredByPackageJson_direct_test.go
@@ -51,7 +51,7 @@ const x = Re/**/`
Label: "React",
AdditionalTextEdits: fourslash.AnyTextEdits,
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "react",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_filteredByPackageJson_nested_test.go b/internal/fourslash/tests/gen/completionsImport_filteredByPackageJson_nested_test.go
index 2fd771c5ba..b7f194b91e 100644
--- a/internal/fourslash/tests/gen/completionsImport_filteredByPackageJson_nested_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_filteredByPackageJson_nested_test.go
@@ -57,7 +57,7 @@ const x = Re/**/`
Label: "React",
AdditionalTextEdits: fourslash.AnyTextEdits,
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "react",
},
},
@@ -78,7 +78,7 @@ const x = Re/**/`
Label: "Redux",
AdditionalTextEdits: fourslash.AnyTextEdits,
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "redux",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_filteredByPackageJson_peerDependencies_test.go b/internal/fourslash/tests/gen/completionsImport_filteredByPackageJson_peerDependencies_test.go
index 3ffefa8b3a..10ab87d1aa 100644
--- a/internal/fourslash/tests/gen/completionsImport_filteredByPackageJson_peerDependencies_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_filteredByPackageJson_peerDependencies_test.go
@@ -51,7 +51,7 @@ const x = Re/**/`
Label: "React",
AdditionalTextEdits: fourslash.AnyTextEdits,
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "react",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_importType_test.go b/internal/fourslash/tests/gen/completionsImport_importType_test.go
index 0cc686fe96..1a18540fd0 100644
--- a/internal/fourslash/tests/gen/completionsImport_importType_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_importType_test.go
@@ -36,7 +36,7 @@ export const m = 0;
&lsproto.CompletionItem{
Label: "C",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
@@ -47,7 +47,7 @@ export const m = 0;
&lsproto.CompletionItem{
Label: "T",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_jsxOpeningTagImportDefault_test.go b/internal/fourslash/tests/gen/completionsImport_jsxOpeningTagImportDefault_test.go
index a0d3f40ac5..a13ece3e5d 100644
--- a/internal/fourslash/tests/gen/completionsImport_jsxOpeningTagImportDefault_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_jsxOpeningTagImportDefault_test.go
@@ -36,7 +36,7 @@ export function Index() {
&lsproto.CompletionItem{
Label: "Component",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./component",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_mergedReExport_test.go b/internal/fourslash/tests/gen/completionsImport_mergedReExport_test.go
index 830373ab69..1519327ca3 100644
--- a/internal/fourslash/tests/gen/completionsImport_mergedReExport_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_mergedReExport_test.go
@@ -52,7 +52,7 @@ C/**/`
&lsproto.CompletionItem{
Label: "Config",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "@jest/types",
},
},
@@ -74,7 +74,7 @@ C/**/`
&lsproto.CompletionItem{
Label: "Config",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "@jest/types",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_multipleWithSameName_test.go b/internal/fourslash/tests/gen/completionsImport_multipleWithSameName_test.go
index 2ee272e38e..e41b898c32 100644
--- a/internal/fourslash/tests/gen/completionsImport_multipleWithSameName_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_multipleWithSameName_test.go
@@ -45,7 +45,7 @@ fo/**/`
&lsproto.CompletionItem{
Label: "foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
@@ -57,7 +57,7 @@ fo/**/`
&lsproto.CompletionItem{
Label: "foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./b",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_named_addToNamedImports_test.go b/internal/fourslash/tests/gen/completionsImport_named_addToNamedImports_test.go
index 34735e25a5..ddc37acd9c 100644
--- a/internal/fourslash/tests/gen/completionsImport_named_addToNamedImports_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_named_addToNamedImports_test.go
@@ -33,7 +33,7 @@ f/**/;`
&lsproto.CompletionItem{
Label: "foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_named_didNotExistBefore_test.go b/internal/fourslash/tests/gen/completionsImport_named_didNotExistBefore_test.go
index fa0f514eb9..627697c77e 100644
--- a/internal/fourslash/tests/gen/completionsImport_named_didNotExistBefore_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_named_didNotExistBefore_test.go
@@ -40,7 +40,7 @@ t/**/`
&lsproto.CompletionItem{
Label: "Test1",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_named_exportEqualsNamespace_merged_test.go b/internal/fourslash/tests/gen/completionsImport_named_exportEqualsNamespace_merged_test.go
index b647b87b7a..6102590a0a 100644
--- a/internal/fourslash/tests/gen/completionsImport_named_exportEqualsNamespace_merged_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_named_exportEqualsNamespace_merged_test.go
@@ -39,7 +39,7 @@ fo/**/`
&lsproto.CompletionItem{
Label: "foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "n",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_named_exportEqualsNamespace_test.go b/internal/fourslash/tests/gen/completionsImport_named_exportEqualsNamespace_test.go
index 5129c1c3d9..936c25ed3c 100644
--- a/internal/fourslash/tests/gen/completionsImport_named_exportEqualsNamespace_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_named_exportEqualsNamespace_test.go
@@ -35,7 +35,7 @@ f/**/;`
&lsproto.CompletionItem{
Label: "foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_named_fromMergedDeclarations_test.go b/internal/fourslash/tests/gen/completionsImport_named_fromMergedDeclarations_test.go
index aa8c399f5a..7826368e8f 100644
--- a/internal/fourslash/tests/gen/completionsImport_named_fromMergedDeclarations_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_named_fromMergedDeclarations_test.go
@@ -38,7 +38,7 @@ declare module "m" {
&lsproto.CompletionItem{
Label: "M",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "m",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_named_namespaceImportExists_test.go b/internal/fourslash/tests/gen/completionsImport_named_namespaceImportExists_test.go
index bc0e0ef490..4f9b816218 100644
--- a/internal/fourslash/tests/gen/completionsImport_named_namespaceImportExists_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_named_namespaceImportExists_test.go
@@ -32,7 +32,7 @@ f/**/;`
&lsproto.CompletionItem{
Label: "foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_ofAlias_preferShortPath_test.go b/internal/fourslash/tests/gen/completionsImport_ofAlias_preferShortPath_test.go
index f21c68a358..6126dd31f2 100644
--- a/internal/fourslash/tests/gen/completionsImport_ofAlias_preferShortPath_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_ofAlias_preferShortPath_test.go
@@ -36,7 +36,7 @@ fo/**/`
&lsproto.CompletionItem{
Label: "foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./foo",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_packageJsonImportsPreference_test.go b/internal/fourslash/tests/gen/completionsImport_packageJsonImportsPreference_test.go
index da5ce3eb18..599a61bb0e 100644
--- a/internal/fourslash/tests/gen/completionsImport_packageJsonImportsPreference_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_packageJsonImportsPreference_test.go
@@ -44,7 +44,7 @@ internalFoo/**/`
&lsproto.CompletionItem{
Label: "internalFoo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "#internal/foo",
},
},
@@ -65,7 +65,7 @@ internalFoo/**/`
&lsproto.CompletionItem{
Label: "internalFoo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./other",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_preferUpdatingExistingImport_test.go b/internal/fourslash/tests/gen/completionsImport_preferUpdatingExistingImport_test.go
index 5239412e41..eaa18d529e 100644
--- a/internal/fourslash/tests/gen/completionsImport_preferUpdatingExistingImport_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_preferUpdatingExistingImport_test.go
@@ -40,7 +40,7 @@ y/**/`
&lsproto.CompletionItem{
Label: "y",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./deep/module/why/you/want/this/path",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_previousTokenIsSemicolon_test.go b/internal/fourslash/tests/gen/completionsImport_previousTokenIsSemicolon_test.go
index 6dbd4f5ed9..999142b2f8 100644
--- a/internal/fourslash/tests/gen/completionsImport_previousTokenIsSemicolon_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_previousTokenIsSemicolon_test.go
@@ -32,7 +32,7 @@ import * as a from 'a';
&lsproto.CompletionItem{
Label: "foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_reExportDefault2_test.go b/internal/fourslash/tests/gen/completionsImport_reExportDefault2_test.go
index a77af5ca53..54b1ef9b25 100644
--- a/internal/fourslash/tests/gen/completionsImport_reExportDefault2_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_reExportDefault2_test.go
@@ -44,7 +44,7 @@ defaultExp/**/`
&lsproto.CompletionItem{
Label: "defaultExport",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "example",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_reExport_wrongName_test.go b/internal/fourslash/tests/gen/completionsImport_reExport_wrongName_test.go
index 9ef283a691..78d7156713 100644
--- a/internal/fourslash/tests/gen/completionsImport_reExport_wrongName_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_reExport_wrongName_test.go
@@ -35,7 +35,7 @@ export { x as y } from "./a";
&lsproto.CompletionItem{
Label: "x",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
@@ -46,7 +46,7 @@ export { x as y } from "./a";
&lsproto.CompletionItem{
Label: "y",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: ".",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_require_addNew_test.go b/internal/fourslash/tests/gen/completionsImport_require_addNew_test.go
index 4e76c0f239..dcabb1123b 100644
--- a/internal/fourslash/tests/gen/completionsImport_require_addNew_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_require_addNew_test.go
@@ -33,7 +33,7 @@ x/**/`
&lsproto.CompletionItem{
Label: "x",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_require_addToExisting_test.go b/internal/fourslash/tests/gen/completionsImport_require_addToExisting_test.go
index 9c6c0337be..3c9bbd672e 100644
--- a/internal/fourslash/tests/gen/completionsImport_require_addToExisting_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_require_addToExisting_test.go
@@ -36,7 +36,7 @@ x/**/`
&lsproto.CompletionItem{
Label: "x",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_require_test.go b/internal/fourslash/tests/gen/completionsImport_require_test.go
index 4e66ec4d9e..fa9558d893 100644
--- a/internal/fourslash/tests/gen/completionsImport_require_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_require_test.go
@@ -33,7 +33,7 @@ fo/*b*/`
&lsproto.CompletionItem{
Label: "foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_sortingModuleSpecifiers_test.go b/internal/fourslash/tests/gen/completionsImport_sortingModuleSpecifiers_test.go
index e476f2f4a0..bedcf95cc6 100644
--- a/internal/fourslash/tests/gen/completionsImport_sortingModuleSpecifiers_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_sortingModuleSpecifiers_test.go
@@ -43,7 +43,7 @@ normalize/**/`
&lsproto.CompletionItem{
Label: "normalize",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "path",
},
},
@@ -53,7 +53,7 @@ normalize/**/`
&lsproto.CompletionItem{
Label: "normalize",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "path/posix",
},
},
@@ -63,7 +63,7 @@ normalize/**/`
&lsproto.CompletionItem{
Label: "normalize",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "path/win32",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_tsx_test.go b/internal/fourslash/tests/gen/completionsImport_tsx_test.go
index 2839a597b8..3b0031c21c 100644
--- a/internal/fourslash/tests/gen/completionsImport_tsx_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_tsx_test.go
@@ -34,7 +34,7 @@ export default function Foo() {};
&lsproto.CompletionItem{
Label: "Foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_umdDefaultNoCrash1_test.go b/internal/fourslash/tests/gen/completionsImport_umdDefaultNoCrash1_test.go
index 6379c8cef9..e89e279cb4 100644
--- a/internal/fourslash/tests/gen/completionsImport_umdDefaultNoCrash1_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_umdDefaultNoCrash1_test.go
@@ -59,7 +59,7 @@ func TestCompletionsImport_umdDefaultNoCrash1(t *testing.T) {
Label: "Dottie",
AdditionalTextEdits: fourslash.AnyTextEdits,
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "dottie",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_umdModules2_moduleExports_test.go b/internal/fourslash/tests/gen/completionsImport_umdModules2_moduleExports_test.go
index 8503f171f0..22b8914502 100644
--- a/internal/fourslash/tests/gen/completionsImport_umdModules2_moduleExports_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_umdModules2_moduleExports_test.go
@@ -43,7 +43,7 @@ const el1 =
foo
;`
Label: "classNames",
AdditionalTextEdits: fourslash.AnyTextEdits,
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "classnames",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_uriStyleNodeCoreModules1_test.go b/internal/fourslash/tests/gen/completionsImport_uriStyleNodeCoreModules1_test.go
index bb495c2c45..a9d32aa576 100644
--- a/internal/fourslash/tests/gen/completionsImport_uriStyleNodeCoreModules1_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_uriStyleNodeCoreModules1_test.go
@@ -36,7 +36,7 @@ write/**/`
&lsproto.CompletionItem{
Label: "writeFile",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "fs",
},
},
@@ -46,7 +46,7 @@ write/**/`
&lsproto.CompletionItem{
Label: "writeFile",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "node:fs",
},
},
@@ -56,7 +56,7 @@ write/**/`
&lsproto.CompletionItem{
Label: "writeFile",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "fs/promises",
},
},
@@ -66,7 +66,7 @@ write/**/`
&lsproto.CompletionItem{
Label: "writeFile",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "node:fs/promises",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_uriStyleNodeCoreModules2_test.go b/internal/fourslash/tests/gen/completionsImport_uriStyleNodeCoreModules2_test.go
index 967646a233..3d2e52cfdb 100644
--- a/internal/fourslash/tests/gen/completionsImport_uriStyleNodeCoreModules2_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_uriStyleNodeCoreModules2_test.go
@@ -38,7 +38,7 @@ write/**/`
&lsproto.CompletionItem{
Label: "writeFile",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "node:fs",
},
},
@@ -48,7 +48,7 @@ write/**/`
&lsproto.CompletionItem{
Label: "writeFile",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "node:fs/promises",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_uriStyleNodeCoreModules3_test.go b/internal/fourslash/tests/gen/completionsImport_uriStyleNodeCoreModules3_test.go
index dc3ebf1cda..6118a9b8f5 100644
--- a/internal/fourslash/tests/gen/completionsImport_uriStyleNodeCoreModules3_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_uriStyleNodeCoreModules3_test.go
@@ -59,7 +59,7 @@ writeFile/*test2*/`
&lsproto.CompletionItem{
Label: "writeFile",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "fs",
},
},
@@ -69,7 +69,7 @@ writeFile/*test2*/`
&lsproto.CompletionItem{
Label: "writeFile",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "fs/promises",
},
},
@@ -91,7 +91,7 @@ writeFile/*test2*/`
&lsproto.CompletionItem{
Label: "writeFile",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "node:fs",
},
},
@@ -101,7 +101,7 @@ writeFile/*test2*/`
&lsproto.CompletionItem{
Label: "writeFile",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "node:fs/promises",
},
},
@@ -123,7 +123,7 @@ writeFile/*test2*/`
&lsproto.CompletionItem{
Label: "writeFile",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "node:fs",
},
},
@@ -133,7 +133,7 @@ writeFile/*test2*/`
&lsproto.CompletionItem{
Label: "writeFile",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "node:fs/promises",
},
},
@@ -155,7 +155,7 @@ writeFile/*test2*/`
&lsproto.CompletionItem{
Label: "writeFile",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "node:fs",
},
},
@@ -165,7 +165,7 @@ writeFile/*test2*/`
&lsproto.CompletionItem{
Label: "writeFile",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "node:fs/promises",
},
},
@@ -189,7 +189,7 @@ writeFile/*test2*/`
&lsproto.CompletionItem{
Label: "writeFile",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "fs",
},
},
@@ -199,7 +199,7 @@ writeFile/*test2*/`
&lsproto.CompletionItem{
Label: "writeFile",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "fs/promises",
},
},
@@ -221,7 +221,7 @@ writeFile/*test2*/`
&lsproto.CompletionItem{
Label: "writeFile",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "node:fs",
},
},
@@ -231,7 +231,7 @@ writeFile/*test2*/`
&lsproto.CompletionItem{
Label: "writeFile",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "node:fs/promises",
},
},
diff --git a/internal/fourslash/tests/gen/completionsImport_windowsPathsProjectRelative_test.go b/internal/fourslash/tests/gen/completionsImport_windowsPathsProjectRelative_test.go
index 6a9f90f7c1..d7225a151c 100644
--- a/internal/fourslash/tests/gen/completionsImport_windowsPathsProjectRelative_test.go
+++ b/internal/fourslash/tests/gen/completionsImport_windowsPathsProjectRelative_test.go
@@ -47,7 +47,7 @@ myFunction/**/`
&lsproto.CompletionItem{
Label: "myFunctionA",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "~/noIndex/a",
},
},
@@ -57,7 +57,7 @@ myFunction/**/`
&lsproto.CompletionItem{
Label: "myFunctionB",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "~/withIndex",
},
},
@@ -78,7 +78,7 @@ myFunction/**/`
&lsproto.CompletionItem{
Label: "myFunctionA",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "../noIndex/a",
},
},
@@ -88,7 +88,7 @@ myFunction/**/`
&lsproto.CompletionItem{
Label: "myFunctionB",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "../withIndex",
},
},
@@ -109,7 +109,7 @@ myFunction/**/`
&lsproto.CompletionItem{
Label: "myFunctionA",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "../noIndex/a",
},
},
@@ -119,7 +119,7 @@ myFunction/**/`
&lsproto.CompletionItem{
Label: "myFunctionB",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "../withIndex",
},
},
diff --git a/internal/fourslash/tests/gen/completionsRecommended_namespace_test.go b/internal/fourslash/tests/gen/completionsRecommended_namespace_test.go
index 3586dec9db..b68431e6f1 100644
--- a/internal/fourslash/tests/gen/completionsRecommended_namespace_test.go
+++ b/internal/fourslash/tests/gen/completionsRecommended_namespace_test.go
@@ -60,7 +60,7 @@ alpha.f(new /*c1*/);`
&lsproto.CompletionItem{
Label: "Name",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
diff --git a/internal/fourslash/tests/gen/completionsUniqueSymbol_import_test.go b/internal/fourslash/tests/gen/completionsUniqueSymbol_import_test.go
index e913fa8634..287c869a39 100644
--- a/internal/fourslash/tests/gen/completionsUniqueSymbol_import_test.go
+++ b/internal/fourslash/tests/gen/completionsUniqueSymbol_import_test.go
@@ -45,7 +45,7 @@ i[|./**/|];`
Label: "publicSym",
InsertText: PtrTo("[publicSym]"),
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
diff --git a/internal/fourslash/tests/gen/completionsWithDeprecatedTag10_test.go b/internal/fourslash/tests/gen/completionsWithDeprecatedTag10_test.go
index a052bfffe2..bf61441fd7 100644
--- a/internal/fourslash/tests/gen/completionsWithDeprecatedTag10_test.go
+++ b/internal/fourslash/tests/gen/completionsWithDeprecatedTag10_test.go
@@ -32,7 +32,7 @@ export const foo = 0;
&lsproto.CompletionItem{
Label: "foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./foo",
},
},
diff --git a/internal/fourslash/tests/gen/importSuggestionsCache_exportUndefined_test.go b/internal/fourslash/tests/gen/importSuggestionsCache_exportUndefined_test.go
index c980419bb3..6264b5e445 100644
--- a/internal/fourslash/tests/gen/importSuggestionsCache_exportUndefined_test.go
+++ b/internal/fourslash/tests/gen/importSuggestionsCache_exportUndefined_test.go
@@ -40,7 +40,7 @@ export = x;
AdditionalTextEdits: fourslash.AnyTextEdits,
SortText: PtrTo(string(ls.SortTextAutoImportSuggestions)),
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./undefinedAlias",
},
},
@@ -61,7 +61,7 @@ export = x;
AdditionalTextEdits: fourslash.AnyTextEdits,
SortText: PtrTo(string(ls.SortTextAutoImportSuggestions)),
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./undefinedAlias",
},
},
diff --git a/internal/fourslash/tests/gen/importSuggestionsCache_invalidPackageJson_test.go b/internal/fourslash/tests/gen/importSuggestionsCache_invalidPackageJson_test.go
index 90d01cb704..8c8210c7fc 100644
--- a/internal/fourslash/tests/gen/importSuggestionsCache_invalidPackageJson_test.go
+++ b/internal/fourslash/tests/gen/importSuggestionsCache_invalidPackageJson_test.go
@@ -47,7 +47,7 @@ readF/**/`
&lsproto.CompletionItem{
Label: "readFile",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "fs",
},
},
diff --git a/internal/fourslash/tests/gen/importTypeCompletions1_test.go b/internal/fourslash/tests/gen/importTypeCompletions1_test.go
index bbf5095b8c..f464ab31cc 100644
--- a/internal/fourslash/tests/gen/importTypeCompletions1_test.go
+++ b/internal/fourslash/tests/gen/importTypeCompletions1_test.go
@@ -33,7 +33,7 @@ export interface Foo {}
Label: "Foo",
InsertText: PtrTo("import type { Foo } from \"./foo\";"),
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./foo",
},
},
diff --git a/internal/fourslash/tests/gen/importTypeCompletions3_test.go b/internal/fourslash/tests/gen/importTypeCompletions3_test.go
index 0453d3c7f4..3b4d63f4e3 100644
--- a/internal/fourslash/tests/gen/importTypeCompletions3_test.go
+++ b/internal/fourslash/tests/gen/importTypeCompletions3_test.go
@@ -33,7 +33,7 @@ export interface Foo {}
Label: "Foo",
InsertText: PtrTo("import type { Foo } from \"./foo\";"),
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./foo",
},
},
diff --git a/internal/fourslash/tests/gen/importTypeCompletions4_test.go b/internal/fourslash/tests/gen/importTypeCompletions4_test.go
index c9b8c87468..24d433e677 100644
--- a/internal/fourslash/tests/gen/importTypeCompletions4_test.go
+++ b/internal/fourslash/tests/gen/importTypeCompletions4_test.go
@@ -34,7 +34,7 @@ export = Foo;
Label: "Foo",
InsertText: PtrTo("import type Foo from \"./foo\";"),
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./foo",
},
},
diff --git a/internal/fourslash/tests/gen/importTypeCompletions5_test.go b/internal/fourslash/tests/gen/importTypeCompletions5_test.go
index d883ce0645..32156191ff 100644
--- a/internal/fourslash/tests/gen/importTypeCompletions5_test.go
+++ b/internal/fourslash/tests/gen/importTypeCompletions5_test.go
@@ -35,7 +35,7 @@ export = Foo;
Label: "Foo",
InsertText: PtrTo("import type Foo = require(\"./foo\");"),
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./foo",
},
},
diff --git a/internal/fourslash/tests/gen/importTypeCompletions6_test.go b/internal/fourslash/tests/gen/importTypeCompletions6_test.go
index 50e0a1b5a8..2173ef5dbe 100644
--- a/internal/fourslash/tests/gen/importTypeCompletions6_test.go
+++ b/internal/fourslash/tests/gen/importTypeCompletions6_test.go
@@ -34,7 +34,7 @@ export interface Foo { };
Label: "Foo",
InsertText: PtrTo("import type { Foo } from \"./foo\";"),
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./foo",
},
},
diff --git a/internal/fourslash/tests/gen/importTypeCompletions7_test.go b/internal/fourslash/tests/gen/importTypeCompletions7_test.go
index 8ab0c4d458..bc038a7d46 100644
--- a/internal/fourslash/tests/gen/importTypeCompletions7_test.go
+++ b/internal/fourslash/tests/gen/importTypeCompletions7_test.go
@@ -36,7 +36,7 @@ export = Foo;
Label: "Foo",
InsertText: PtrTo("import Foo from \"./foo\";"),
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./foo",
},
},
diff --git a/internal/fourslash/tests/gen/importTypeCompletions8_test.go b/internal/fourslash/tests/gen/importTypeCompletions8_test.go
index d7e63483ee..cdff4b9e7b 100644
--- a/internal/fourslash/tests/gen/importTypeCompletions8_test.go
+++ b/internal/fourslash/tests/gen/importTypeCompletions8_test.go
@@ -33,7 +33,7 @@ export interface Foo {}
Label: "Foo",
InsertText: PtrTo("import { type Foo } from \"./foo\";"),
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./foo",
},
},
diff --git a/internal/fourslash/tests/gen/importTypeCompletions9_test.go b/internal/fourslash/tests/gen/importTypeCompletions9_test.go
index 90cc09ef5b..c38bfa7338 100644
--- a/internal/fourslash/tests/gen/importTypeCompletions9_test.go
+++ b/internal/fourslash/tests/gen/importTypeCompletions9_test.go
@@ -33,7 +33,7 @@ export interface Foo {}
Label: "Foo",
InsertText: PtrTo("import { type Foo } from \"./foo\";"),
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./foo",
},
},
diff --git a/internal/fourslash/tests/gen/jsFileImportNoTypes2_test.go b/internal/fourslash/tests/gen/jsFileImportNoTypes2_test.go
index d9ea22f264..03bdb5cb8c 100644
--- a/internal/fourslash/tests/gen/jsFileImportNoTypes2_test.go
+++ b/internal/fourslash/tests/gen/jsFileImportNoTypes2_test.go
@@ -46,7 +46,7 @@ import /**/`
Label: "TestClassBaseline",
InsertText: PtrTo("import { TestClassBaseline } from \"./baseline\";"),
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./baseline",
},
},
@@ -55,7 +55,7 @@ import /**/`
Label: "TestClassExportList",
InsertText: PtrTo("import { TestClassExportList } from \"./exportList\";"),
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./exportList",
},
},
@@ -64,7 +64,7 @@ import /**/`
Label: "TestClassReExport",
InsertText: PtrTo("import { TestClassReExport } from \"./reExport\";"),
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./reExport",
},
},
@@ -73,7 +73,7 @@ import /**/`
Label: "TestDefaultClass",
InsertText: PtrTo("import TestDefaultClass from \"./default\";"),
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./default",
},
},
diff --git a/internal/fourslash/tests/gen/autoImportPackageRootPathTypeModule_test.go b/internal/fourslash/tests/manual/autoImportPackageRootPathTypeModule_test.go
similarity index 86%
rename from internal/fourslash/tests/gen/autoImportPackageRootPathTypeModule_test.go
rename to internal/fourslash/tests/manual/autoImportPackageRootPathTypeModule_test.go
index f574821933..ca68c52c4e 100644
--- a/internal/fourslash/tests/gen/autoImportPackageRootPathTypeModule_test.go
+++ b/internal/fourslash/tests/manual/autoImportPackageRootPathTypeModule_test.go
@@ -8,7 +8,7 @@ import (
)
func TestAutoImportPackageRootPathTypeModule(t *testing.T) {
- fourslash.SkipIfFailing(t)
+ t.Skip()
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @allowJs: true
@@ -31,5 +31,5 @@ export function foo() {};
foo/**/`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
- f.VerifyImportFixModuleSpecifiers(t, "", []string{"pkg/lib"}, nil /*preferences*/)
+ f.VerifyImportFixModuleSpecifiers(t, "", []string{"pkg"}, nil /*preferences*/)
}
diff --git a/internal/fourslash/tests/gen/completionListWithLabel_test.go b/internal/fourslash/tests/manual/completionListWithLabel_test.go
similarity index 100%
rename from internal/fourslash/tests/gen/completionListWithLabel_test.go
rename to internal/fourslash/tests/manual/completionListWithLabel_test.go
index dde0947f86..2c3807e31d 100644
--- a/internal/fourslash/tests/gen/completionListWithLabel_test.go
+++ b/internal/fourslash/tests/manual/completionListWithLabel_test.go
@@ -46,8 +46,8 @@ func TestCompletionListWithLabel(t *testing.T) {
},
Items: &fourslash.CompletionsExpectedItems{
Exact: []fourslash.CompletionsExpectedItem{
- "testlabel",
"label",
+ "testlabel",
},
},
})
diff --git a/internal/fourslash/tests/gen/completionsImport_defaultAndNamedConflict_test.go b/internal/fourslash/tests/manual/completionsImport_defaultAndNamedConflict_test.go
similarity index 84%
rename from internal/fourslash/tests/gen/completionsImport_defaultAndNamedConflict_test.go
rename to internal/fourslash/tests/manual/completionsImport_defaultAndNamedConflict_test.go
index 737ad8d659..779a4ce9cf 100644
--- a/internal/fourslash/tests/gen/completionsImport_defaultAndNamedConflict_test.go
+++ b/internal/fourslash/tests/manual/completionsImport_defaultAndNamedConflict_test.go
@@ -34,39 +34,38 @@ someMo/**/`
&lsproto.CompletionItem{
Label: "someModule",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./someModule",
},
},
- Detail: PtrTo("(property) default: 1"),
- Kind: PtrTo(lsproto.CompletionItemKindField),
+ Detail: PtrTo("const someModule: 0"),
+ Kind: PtrTo(lsproto.CompletionItemKindVariable),
AdditionalTextEdits: fourslash.AnyTextEdits,
SortText: PtrTo(string(ls.SortTextAutoImportSuggestions)),
},
&lsproto.CompletionItem{
Label: "someModule",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./someModule",
},
},
- Detail: PtrTo("const someModule: 0"),
- Kind: PtrTo(lsproto.CompletionItemKindVariable),
+ Detail: PtrTo("(property) default: 1"),
+ Kind: PtrTo(lsproto.CompletionItemKindField),
AdditionalTextEdits: fourslash.AnyTextEdits,
SortText: PtrTo(string(ls.SortTextAutoImportSuggestions)),
},
- }, true),
+ },
+ true,
+ ),
},
})
f.VerifyApplyCodeActionFromCompletion(t, PtrTo(""), &fourslash.ApplyCodeActionFromCompletionOptions{
- Name: "someModule",
- Source: "./someModule",
- AutoImportData: &lsproto.AutoImportData{
- ExportName: "default",
- FileName: "/someModule.ts",
- },
- Description: "Add import from \"./someModule\"",
- NewFileContent: PtrTo(`import someModule from "./someModule";
+ Name: "someModule",
+ Source: "./someModule",
+ AutoImportFix: &lsproto.AutoImportFix{},
+ Description: "Add import from \"./someModule\"",
+ NewFileContent: PtrTo(`import { someModule } from "./someModule";
someMo`),
})
diff --git a/internal/fourslash/tests/gen/completionsImport_reExportDefault_test.go b/internal/fourslash/tests/manual/completionsImport_reExportDefault_test.go
similarity index 93%
rename from internal/fourslash/tests/gen/completionsImport_reExportDefault_test.go
rename to internal/fourslash/tests/manual/completionsImport_reExportDefault_test.go
index fb776c0766..6641d8f53a 100644
--- a/internal/fourslash/tests/gen/completionsImport_reExportDefault_test.go
+++ b/internal/fourslash/tests/manual/completionsImport_reExportDefault_test.go
@@ -35,12 +35,12 @@ fo/**/`
&lsproto.CompletionItem{
Label: "foo",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
+ AutoImport: &lsproto.AutoImportFix{
ModuleSpecifier: "./a",
},
},
Detail: PtrTo("(alias) function foo(): void\nexport foo"),
- Kind: PtrTo(lsproto.CompletionItemKindVariable),
+ Kind: PtrTo(lsproto.CompletionItemKindFunction),
AdditionalTextEdits: fourslash.AnyTextEdits,
SortText: PtrTo(string(ls.SortTextAutoImportSuggestions)),
},
diff --git a/internal/fourslash/tests/gen/completionsImport_reexportTransient_test.go b/internal/fourslash/tests/manual/completionsImport_reexportTransient_test.go
similarity index 79%
rename from internal/fourslash/tests/gen/completionsImport_reexportTransient_test.go
rename to internal/fourslash/tests/manual/completionsImport_reexportTransient_test.go
index 95900a857f..db75c54981 100644
--- a/internal/fourslash/tests/gen/completionsImport_reexportTransient_test.go
+++ b/internal/fourslash/tests/manual/completionsImport_reexportTransient_test.go
@@ -39,8 +39,18 @@ one/**/`
&lsproto.CompletionItem{
Label: "one",
Data: &lsproto.CompletionItemData{
- AutoImport: &lsproto.AutoImportData{
- ModuleSpecifier: "./transient",
+ AutoImport: &lsproto.AutoImportFix{
+ ModuleSpecifier: "./r1",
+ },
+ },
+ AdditionalTextEdits: fourslash.AnyTextEdits,
+ SortText: PtrTo(string(ls.SortTextAutoImportSuggestions)),
+ },
+ &lsproto.CompletionItem{
+ Label: "one",
+ Data: &lsproto.CompletionItemData{
+ AutoImport: &lsproto.AutoImportFix{
+ ModuleSpecifier: "./r2",
},
},
AdditionalTextEdits: fourslash.AnyTextEdits,
diff --git a/internal/fourslash/tests/gen/completionsWithStringReplacementMode1_test.go b/internal/fourslash/tests/manual/completionsWithStringReplacementMode1_test.go
similarity index 100%
rename from internal/fourslash/tests/gen/completionsWithStringReplacementMode1_test.go
rename to internal/fourslash/tests/manual/completionsWithStringReplacementMode1_test.go
index 79e186a910..731833bb77 100644
--- a/internal/fourslash/tests/gen/completionsWithStringReplacementMode1_test.go
+++ b/internal/fourslash/tests/manual/completionsWithStringReplacementMode1_test.go
@@ -44,145 +44,145 @@ f('[|login./**/|]')`
Items: &fourslash.CompletionsExpectedItems{
Exact: []fourslash.CompletionsExpectedItem{
&lsproto.CompletionItem{
- Label: "login.title",
+ Label: "login.description",
TextEdit: &lsproto.TextEditOrInsertReplaceEdit{
TextEdit: &lsproto.TextEdit{
- NewText: "login.title",
+ NewText: "login.description",
Range: f.Ranges()[0].LSRange,
},
},
},
&lsproto.CompletionItem{
- Label: "login.description",
+ Label: "login.emailInputPlaceholder",
TextEdit: &lsproto.TextEditOrInsertReplaceEdit{
TextEdit: &lsproto.TextEdit{
- NewText: "login.description",
+ NewText: "login.emailInputPlaceholder",
Range: f.Ranges()[0].LSRange,
},
},
},
&lsproto.CompletionItem{
- Label: "login.sendEmailAgree",
+ Label: "login.errorGeneralEmailDescription",
TextEdit: &lsproto.TextEditOrInsertReplaceEdit{
TextEdit: &lsproto.TextEdit{
- NewText: "login.sendEmailAgree",
+ NewText: "login.errorGeneralEmailDescription",
Range: f.Ranges()[0].LSRange,
},
},
},
&lsproto.CompletionItem{
- Label: "login.termsOfUse",
+ Label: "login.errorGeneralEmailTitle",
TextEdit: &lsproto.TextEditOrInsertReplaceEdit{
TextEdit: &lsproto.TextEdit{
- NewText: "login.termsOfUse",
+ NewText: "login.errorGeneralEmailTitle",
Range: f.Ranges()[0].LSRange,
},
},
},
&lsproto.CompletionItem{
- Label: "login.privacyPolicy",
+ Label: "login.errorWrongEmailDescription",
TextEdit: &lsproto.TextEditOrInsertReplaceEdit{
TextEdit: &lsproto.TextEdit{
- NewText: "login.privacyPolicy",
+ NewText: "login.errorWrongEmailDescription",
Range: f.Ranges()[0].LSRange,
},
},
},
&lsproto.CompletionItem{
- Label: "login.sendEmailButton",
+ Label: "login.errorWrongEmailTitle",
TextEdit: &lsproto.TextEditOrInsertReplaceEdit{
TextEdit: &lsproto.TextEdit{
- NewText: "login.sendEmailButton",
+ NewText: "login.errorWrongEmailTitle",
Range: f.Ranges()[0].LSRange,
},
},
},
&lsproto.CompletionItem{
- Label: "login.emailInputPlaceholder",
+ Label: "login.loginErrorDescription",
TextEdit: &lsproto.TextEditOrInsertReplaceEdit{
TextEdit: &lsproto.TextEdit{
- NewText: "login.emailInputPlaceholder",
+ NewText: "login.loginErrorDescription",
Range: f.Ranges()[0].LSRange,
},
},
},
&lsproto.CompletionItem{
- Label: "login.errorWrongEmailTitle",
+ Label: "login.loginErrorTitle",
TextEdit: &lsproto.TextEditOrInsertReplaceEdit{
TextEdit: &lsproto.TextEdit{
- NewText: "login.errorWrongEmailTitle",
+ NewText: "login.loginErrorTitle",
Range: f.Ranges()[0].LSRange,
},
},
},
&lsproto.CompletionItem{
- Label: "login.errorWrongEmailDescription",
+ Label: "login.openEmailAppErrorConfirm",
TextEdit: &lsproto.TextEditOrInsertReplaceEdit{
TextEdit: &lsproto.TextEdit{
- NewText: "login.errorWrongEmailDescription",
+ NewText: "login.openEmailAppErrorConfirm",
Range: f.Ranges()[0].LSRange,
},
},
},
&lsproto.CompletionItem{
- Label: "login.errorGeneralEmailTitle",
+ Label: "login.openEmailAppErrorDescription",
TextEdit: &lsproto.TextEditOrInsertReplaceEdit{
TextEdit: &lsproto.TextEdit{
- NewText: "login.errorGeneralEmailTitle",
+ NewText: "login.openEmailAppErrorDescription",
Range: f.Ranges()[0].LSRange,
},
},
},
&lsproto.CompletionItem{
- Label: "login.errorGeneralEmailDescription",
+ Label: "login.openEmailAppErrorTitle",
TextEdit: &lsproto.TextEditOrInsertReplaceEdit{
TextEdit: &lsproto.TextEdit{
- NewText: "login.errorGeneralEmailDescription",
+ NewText: "login.openEmailAppErrorTitle",
Range: f.Ranges()[0].LSRange,
},
},
},
&lsproto.CompletionItem{
- Label: "login.loginErrorTitle",
+ Label: "login.privacyPolicy",
TextEdit: &lsproto.TextEditOrInsertReplaceEdit{
TextEdit: &lsproto.TextEdit{
- NewText: "login.loginErrorTitle",
+ NewText: "login.privacyPolicy",
Range: f.Ranges()[0].LSRange,
},
},
},
&lsproto.CompletionItem{
- Label: "login.loginErrorDescription",
+ Label: "login.sendEmailAgree",
TextEdit: &lsproto.TextEditOrInsertReplaceEdit{
TextEdit: &lsproto.TextEdit{
- NewText: "login.loginErrorDescription",
+ NewText: "login.sendEmailAgree",
Range: f.Ranges()[0].LSRange,
},
},
},
&lsproto.CompletionItem{
- Label: "login.openEmailAppErrorTitle",
+ Label: "login.sendEmailButton",
TextEdit: &lsproto.TextEditOrInsertReplaceEdit{
TextEdit: &lsproto.TextEdit{
- NewText: "login.openEmailAppErrorTitle",
+ NewText: "login.sendEmailButton",
Range: f.Ranges()[0].LSRange,
},
},
},
&lsproto.CompletionItem{
- Label: "login.openEmailAppErrorDescription",
+ Label: "login.termsOfUse",
TextEdit: &lsproto.TextEditOrInsertReplaceEdit{
TextEdit: &lsproto.TextEdit{
- NewText: "login.openEmailAppErrorDescription",
+ NewText: "login.termsOfUse",
Range: f.Ranges()[0].LSRange,
},
},
},
&lsproto.CompletionItem{
- Label: "login.openEmailAppErrorConfirm",
+ Label: "login.title",
TextEdit: &lsproto.TextEditOrInsertReplaceEdit{
TextEdit: &lsproto.TextEdit{
- NewText: "login.openEmailAppErrorConfirm",
+ NewText: "login.title",
Range: f.Ranges()[0].LSRange,
},
},
diff --git a/internal/fourslash/tests/gen/importNameCodeFix_uriStyleNodeCoreModules1_test.go b/internal/fourslash/tests/manual/importNameCodeFix_uriStyleNodeCoreModules1_test.go
similarity index 83%
rename from internal/fourslash/tests/gen/importNameCodeFix_uriStyleNodeCoreModules1_test.go
rename to internal/fourslash/tests/manual/importNameCodeFix_uriStyleNodeCoreModules1_test.go
index badec23586..24043e2252 100644
--- a/internal/fourslash/tests/gen/importNameCodeFix_uriStyleNodeCoreModules1_test.go
+++ b/internal/fourslash/tests/manual/importNameCodeFix_uriStyleNodeCoreModules1_test.go
@@ -8,7 +8,6 @@ import (
)
func TestImportNameCodeFix_uriStyleNodeCoreModules1(t *testing.T) {
- fourslash.SkipIfFailing(t)
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @module: commonjs
@@ -21,5 +20,5 @@ declare module "node:fs/promises" { export * from "fs/promises"; }
writeFile/**/`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
- f.VerifyImportFixModuleSpecifiers(t, "", []string{"fs", "fs/promises", "node:fs", "node:fs/promises"}, nil /*preferences*/)
+ f.VerifyImportFixModuleSpecifiers(t, "", []string{"fs", "node:fs", "fs/promises", "node:fs/promises"}, nil /*preferences*/)
}
diff --git a/internal/fourslash/tests/gen/jsdocParameterNameCompletion_test.go b/internal/fourslash/tests/manual/jsdocParameterNameCompletion_test.go
similarity index 100%
rename from internal/fourslash/tests/gen/jsdocParameterNameCompletion_test.go
rename to internal/fourslash/tests/manual/jsdocParameterNameCompletion_test.go
index 6501937352..aa78f2494c 100644
--- a/internal/fourslash/tests/gen/jsdocParameterNameCompletion_test.go
+++ b/internal/fourslash/tests/manual/jsdocParameterNameCompletion_test.go
@@ -40,8 +40,8 @@ function i(foo, bar) {}`
},
Items: &fourslash.CompletionsExpectedItems{
Exact: []fourslash.CompletionsExpectedItem{
- "foo",
"bar",
+ "foo",
},
},
})
diff --git a/internal/fourslash/tests/gen/stringLiteralCompletionsInPositionTypedUsingRest_test.go b/internal/fourslash/tests/manual/stringLiteralCompletionsInPositionTypedUsingRest_test.go
similarity index 100%
rename from internal/fourslash/tests/gen/stringLiteralCompletionsInPositionTypedUsingRest_test.go
rename to internal/fourslash/tests/manual/stringLiteralCompletionsInPositionTypedUsingRest_test.go
index be7d962ce7..4a68f319de 100644
--- a/internal/fourslash/tests/gen/stringLiteralCompletionsInPositionTypedUsingRest_test.go
+++ b/internal/fourslash/tests/manual/stringLiteralCompletionsInPositionTypedUsingRest_test.go
@@ -47,8 +47,8 @@ new Q<{ id: string; name: string }>().select("name", "/*ts3*/");`
},
Items: &fourslash.CompletionsExpectedItems{
Exact: []fourslash.CompletionsExpectedItem{
- "name",
"id",
+ "name",
},
},
})
diff --git a/internal/ls/autoImports_stringer_generated.go b/internal/ls/autoImports_stringer_generated.go
deleted file mode 100644
index 99243828a3..0000000000
--- a/internal/ls/autoImports_stringer_generated.go
+++ /dev/null
@@ -1,28 +0,0 @@
-// Code generated by "stringer -type=ExportKind -output=autoImports_stringer_generated.go"; DO NOT EDIT.
-
-package ls
-
-import "strconv"
-
-func _() {
- // An "invalid array index" compiler error signifies that the constant values have changed.
- // Re-run the stringer command to generate them again.
- var x [1]struct{}
- _ = x[ExportKindNamed-0]
- _ = x[ExportKindDefault-1]
- _ = x[ExportKindExportEquals-2]
- _ = x[ExportKindUMD-3]
- _ = x[ExportKindModule-4]
-}
-
-const _ExportKind_name = "ExportKindNamedExportKindDefaultExportKindExportEqualsExportKindUMDExportKindModule"
-
-var _ExportKind_index = [...]uint8{0, 15, 32, 54, 67, 83}
-
-func (i ExportKind) String() string {
- idx := int(i) - 0
- if i < 0 || idx >= len(_ExportKind_index)-1 {
- return "ExportKind(" + strconv.FormatInt(int64(i), 10) + ")"
- }
- return _ExportKind_name[_ExportKind_index[idx]:_ExportKind_index[idx+1]]
-}
diff --git a/internal/ls/autoimport/aliasresolver.go b/internal/ls/autoimport/aliasresolver.go
new file mode 100644
index 0000000000..2351c6c4a9
--- /dev/null
+++ b/internal/ls/autoimport/aliasresolver.go
@@ -0,0 +1,219 @@
+package autoimport
+
+import (
+ "github.com/microsoft/typescript-go/internal/ast"
+ "github.com/microsoft/typescript-go/internal/binder"
+ "github.com/microsoft/typescript-go/internal/checker"
+ "github.com/microsoft/typescript-go/internal/collections"
+ "github.com/microsoft/typescript-go/internal/core"
+ "github.com/microsoft/typescript-go/internal/module"
+ "github.com/microsoft/typescript-go/internal/packagejson"
+ "github.com/microsoft/typescript-go/internal/symlinks"
+ "github.com/microsoft/typescript-go/internal/tsoptions"
+ "github.com/microsoft/typescript-go/internal/tspath"
+)
+
+type aliasResolver struct {
+ toPath func(fileName string) tspath.Path
+ host RegistryCloneHost
+ moduleResolver *module.Resolver
+
+ rootFiles []*ast.SourceFile
+ onFailedAmbientModuleLookup func(source ast.HasFileName, moduleName string)
+ resolvedModules collections.SyncMap[tspath.Path, *collections.SyncMap[module.ModeAwareCacheKey, *module.ResolvedModule]]
+}
+
+func newAliasResolver(
+ rootFiles []*ast.SourceFile,
+ host RegistryCloneHost,
+ moduleResolver *module.Resolver,
+ toPath func(fileName string) tspath.Path,
+ onFailedAmbientModuleLookup func(source ast.HasFileName, moduleName string),
+) *aliasResolver {
+ r := &aliasResolver{
+ toPath: toPath,
+ host: host,
+ moduleResolver: moduleResolver,
+ rootFiles: rootFiles,
+ onFailedAmbientModuleLookup: onFailedAmbientModuleLookup,
+ }
+ return r
+}
+
+// BindSourceFiles implements checker.Program.
+func (r *aliasResolver) BindSourceFiles() {
+ // We will bind as we parse
+}
+
+// SourceFiles implements checker.Program.
+func (r *aliasResolver) SourceFiles() []*ast.SourceFile {
+ return r.rootFiles
+}
+
+// Options implements checker.Program.
+func (r *aliasResolver) Options() *core.CompilerOptions {
+ return &core.CompilerOptions{
+ NoCheck: core.TSTrue,
+ }
+}
+
+// GetCurrentDirectory implements checker.Program.
+func (r *aliasResolver) GetCurrentDirectory() string {
+ return r.host.GetCurrentDirectory()
+}
+
+// UseCaseSensitiveFileNames implements checker.Program.
+func (r *aliasResolver) UseCaseSensitiveFileNames() bool {
+ return r.host.FS().UseCaseSensitiveFileNames()
+}
+
+// GetSourceFile implements checker.Program.
+func (r *aliasResolver) GetSourceFile(fileName string) *ast.SourceFile {
+ file := r.host.GetSourceFile(fileName, r.toPath(fileName))
+ binder.BindSourceFile(file)
+ return file
+}
+
+// GetDefaultResolutionModeForFile implements checker.Program.
+func (r *aliasResolver) GetDefaultResolutionModeForFile(file ast.HasFileName) core.ResolutionMode {
+ return core.ModuleKindESNext
+}
+
+// GetEmitModuleFormatOfFile implements checker.Program.
+func (r *aliasResolver) GetEmitModuleFormatOfFile(sourceFile ast.HasFileName) core.ModuleKind {
+ return core.ModuleKindESNext
+}
+
+// GetEmitSyntaxForUsageLocation implements checker.Program.
+func (r *aliasResolver) GetEmitSyntaxForUsageLocation(sourceFile ast.HasFileName, usageLocation *ast.StringLiteralLike) core.ResolutionMode {
+ return core.ModuleKindESNext
+}
+
+// GetImpliedNodeFormatForEmit implements checker.Program.
+func (r *aliasResolver) GetImpliedNodeFormatForEmit(sourceFile ast.HasFileName) core.ModuleKind {
+ return core.ModuleKindESNext
+}
+
+// GetModeForUsageLocation implements checker.Program.
+func (r *aliasResolver) GetModeForUsageLocation(file ast.HasFileName, moduleSpecifier *ast.StringLiteralLike) core.ResolutionMode {
+ return core.ModuleKindESNext
+}
+
+// GetResolvedModule implements checker.Program.
+func (r *aliasResolver) GetResolvedModule(currentSourceFile ast.HasFileName, moduleReference string, mode core.ResolutionMode) *module.ResolvedModule {
+ cache, _ := r.resolvedModules.LoadOrStore(currentSourceFile.Path(), &collections.SyncMap[module.ModeAwareCacheKey, *module.ResolvedModule]{})
+ if resolved, ok := cache.Load(module.ModeAwareCacheKey{Name: moduleReference, Mode: mode}); ok {
+ return resolved
+ }
+ resolved, _ := r.moduleResolver.ResolveModuleName(moduleReference, currentSourceFile.FileName(), mode, nil)
+ resolved, _ = cache.LoadOrStore(module.ModeAwareCacheKey{Name: moduleReference, Mode: mode}, resolved)
+ if !resolved.IsResolved() && !tspath.PathIsRelative(moduleReference) {
+ r.onFailedAmbientModuleLookup(currentSourceFile, moduleReference)
+ }
+ return resolved
+}
+
+// GetSourceFileForResolvedModule implements checker.Program.
+func (r *aliasResolver) GetSourceFileForResolvedModule(fileName string) *ast.SourceFile {
+ return r.GetSourceFile(fileName)
+}
+
+// GetResolvedModules implements checker.Program.
+func (r *aliasResolver) GetResolvedModules() map[tspath.Path]module.ModeAwareCache[*module.ResolvedModule] {
+ // only used when producing diagnostics, which hopefully the checker won't do
+ return nil
+}
+
+// ---
+
+// GetSymlinkCache implements checker.Program.
+func (r *aliasResolver) GetSymlinkCache() *symlinks.KnownSymlinks {
+ panic("unimplemented")
+}
+
+// GetSourceFileMetaData implements checker.Program.
+func (r *aliasResolver) GetSourceFileMetaData(path tspath.Path) ast.SourceFileMetaData {
+ panic("unimplemented")
+}
+
+// CommonSourceDirectory implements checker.Program.
+func (r *aliasResolver) CommonSourceDirectory() string {
+ panic("unimplemented")
+}
+
+// FileExists implements checker.Program.
+func (r *aliasResolver) FileExists(fileName string) bool {
+ panic("unimplemented")
+}
+
+// GetGlobalTypingsCacheLocation implements checker.Program.
+func (r *aliasResolver) GetGlobalTypingsCacheLocation() string {
+ panic("unimplemented")
+}
+
+// GetImportHelpersImportSpecifier implements checker.Program.
+func (r *aliasResolver) GetImportHelpersImportSpecifier(path tspath.Path) *ast.Node {
+ panic("unimplemented")
+}
+
+// GetJSXRuntimeImportSpecifier implements checker.Program.
+func (r *aliasResolver) GetJSXRuntimeImportSpecifier(path tspath.Path) (moduleReference string, specifier *ast.Node) {
+ panic("unimplemented")
+}
+
+// GetNearestAncestorDirectoryWithPackageJson implements checker.Program.
+func (r *aliasResolver) GetNearestAncestorDirectoryWithPackageJson(dirname string) string {
+ panic("unimplemented")
+}
+
+// GetPackageJsonInfo implements checker.Program.
+func (r *aliasResolver) GetPackageJsonInfo(pkgJsonPath string) *packagejson.InfoCacheEntry {
+ panic("unimplemented")
+}
+
+// GetProjectReferenceFromOutputDts implements checker.Program.
+func (r *aliasResolver) GetProjectReferenceFromOutputDts(path tspath.Path) *tsoptions.SourceOutputAndProjectReference {
+ panic("unimplemented")
+}
+
+// GetProjectReferenceFromSource implements checker.Program.
+func (r *aliasResolver) GetProjectReferenceFromSource(path tspath.Path) *tsoptions.SourceOutputAndProjectReference {
+ panic("unimplemented")
+}
+
+// GetRedirectForResolution implements checker.Program.
+func (r *aliasResolver) GetRedirectForResolution(file ast.HasFileName) *tsoptions.ParsedCommandLine {
+ panic("unimplemented")
+}
+
+// GetRedirectTargets implements checker.Program.
+func (r *aliasResolver) GetRedirectTargets(path tspath.Path) []string {
+ panic("unimplemented")
+}
+
+// GetResolvedModuleFromModuleSpecifier implements checker.Program.
+func (r *aliasResolver) GetResolvedModuleFromModuleSpecifier(file ast.HasFileName, moduleSpecifier *ast.StringLiteralLike) *module.ResolvedModule {
+ panic("unimplemented")
+}
+
+// GetSourceOfProjectReferenceIfOutputIncluded implements checker.Program.
+func (r *aliasResolver) GetSourceOfProjectReferenceIfOutputIncluded(file ast.HasFileName) string {
+ panic("unimplemented")
+}
+
+// IsSourceFileDefaultLibrary implements checker.Program.
+func (r *aliasResolver) IsSourceFileDefaultLibrary(path tspath.Path) bool {
+ panic("unimplemented")
+}
+
+// IsSourceFromProjectReference implements checker.Program.
+func (r *aliasResolver) IsSourceFromProjectReference(path tspath.Path) bool {
+ panic("unimplemented")
+}
+
+// SourceFileMayBeEmitted implements checker.Program.
+func (r *aliasResolver) SourceFileMayBeEmitted(sourceFile *ast.SourceFile, forceDtsEmit bool) bool {
+ panic("unimplemented")
+}
+
+var _ checker.Program = (*aliasResolver)(nil)
diff --git a/internal/ls/autoimport/export.go b/internal/ls/autoimport/export.go
new file mode 100644
index 0000000000..a338e0936d
--- /dev/null
+++ b/internal/ls/autoimport/export.go
@@ -0,0 +1,116 @@
+package autoimport
+
+import (
+ "strings"
+
+ "github.com/microsoft/typescript-go/internal/ast"
+ "github.com/microsoft/typescript-go/internal/checker"
+ "github.com/microsoft/typescript-go/internal/collections"
+ "github.com/microsoft/typescript-go/internal/ls/lsutil"
+ "github.com/microsoft/typescript-go/internal/tspath"
+)
+
+//go:generate go tool golang.org/x/tools/cmd/stringer -type=ExportSyntax -output=export_stringer_generated.go
+//go:generate go tool mvdan.cc/gofumpt -w export_stringer_generated.go
+
+// ModuleID uniquely identifies a module across multiple declarations.
+// If the export is from an ambient module declaration, this is the module name.
+// If the export is from a module augmentation, this is the Path() of the resolved module file.
+// Otherwise this is the Path() of the exporting source file.
+type ModuleID string
+
+type ExportID struct {
+ ModuleID ModuleID
+ ExportName string
+}
+
+type ExportSyntax int
+
+const (
+ ExportSyntaxNone ExportSyntax = iota
+ // export const x = {}
+ ExportSyntaxModifier
+ // export { x }
+ ExportSyntaxNamed
+ // export default function f() {}
+ ExportSyntaxDefaultModifier
+ // export default f
+ ExportSyntaxDefaultDeclaration
+ // export = x
+ ExportSyntaxEquals
+ // export as namespace x
+ ExportSyntaxUMD
+ // export * from "module"
+ ExportSyntaxStar
+ // module.exports = {}
+ ExportSyntaxCommonJSModuleExports
+ // exports.x = {}
+ ExportSyntaxCommonJSExportsProperty
+)
+
+type Export struct {
+ ExportID
+ ModuleFileName string
+ Syntax ExportSyntax
+ Flags ast.SymbolFlags
+ localName string
+ // through is the name of the module symbol's export that this export was found on,
+ // either 'export=', InternalSymbolNameExportStar, or empty string.
+ through string
+
+ // Checker-set fields
+
+ Target ExportID
+ IsTypeOnly bool
+ ScriptElementKind lsutil.ScriptElementKind
+ ScriptElementKindModifiers collections.Set[lsutil.ScriptElementKindModifier]
+
+ // The file where the export was found.
+ Path tspath.Path
+
+ NodeModulesDirectory tspath.Path
+ PackageName string
+}
+
+func (e *Export) Name() string {
+ if e.localName != "" {
+ return e.localName
+ }
+ if e.ExportName == ast.InternalSymbolNameExportEquals {
+ return e.Target.ExportName
+ }
+ if strings.HasPrefix(e.ExportName, ast.InternalSymbolNamePrefix) {
+ panic("unexpected internal symbol name in export")
+ }
+ return e.ExportName
+}
+
+func (e *Export) IsRenameable() bool {
+ return e.ExportName == ast.InternalSymbolNameExportEquals || e.ExportName == ast.InternalSymbolNameDefault
+}
+
+func (e *Export) AmbientModuleName() string {
+ if !tspath.IsExternalModuleNameRelative(string(e.ModuleID)) {
+ return string(e.ModuleID)
+ }
+ return ""
+}
+
+func (e *Export) IsUnresolvedAlias() bool {
+ return e.Flags == ast.SymbolFlagsAlias
+}
+
+func SymbolToExport(symbol *ast.Symbol, ch *checker.Checker) *Export {
+ if symbol.Parent == nil || !checker.IsExternalModuleSymbol(symbol.Parent) {
+ return nil
+ }
+ moduleID, moduleFileName := getModuleIDAndFileNameOfModuleSymbol(symbol.Parent)
+ extractor := newSymbolExtractor("", "", ch)
+
+ var exports []*Export
+ extractor.extractFromSymbol(symbol.Name, symbol, moduleID, moduleFileName, ast.GetSourceFileOfModule(symbol.Parent), &exports)
+ if len(exports) > 0 {
+ return exports[0]
+ }
+ return nil
+}
diff --git a/internal/ls/autoimport/export_stringer_generated.go b/internal/ls/autoimport/export_stringer_generated.go
new file mode 100644
index 0000000000..ac00ae761b
--- /dev/null
+++ b/internal/ls/autoimport/export_stringer_generated.go
@@ -0,0 +1,33 @@
+// Code generated by "stringer -type=ExportSyntax -output=export_stringer_generated.go"; DO NOT EDIT.
+
+package autoimport
+
+import "strconv"
+
+func _() {
+ // An "invalid array index" compiler error signifies that the constant values have changed.
+ // Re-run the stringer command to generate them again.
+ var x [1]struct{}
+ _ = x[ExportSyntaxNone-0]
+ _ = x[ExportSyntaxModifier-1]
+ _ = x[ExportSyntaxNamed-2]
+ _ = x[ExportSyntaxDefaultModifier-3]
+ _ = x[ExportSyntaxDefaultDeclaration-4]
+ _ = x[ExportSyntaxEquals-5]
+ _ = x[ExportSyntaxUMD-6]
+ _ = x[ExportSyntaxStar-7]
+ _ = x[ExportSyntaxCommonJSModuleExports-8]
+ _ = x[ExportSyntaxCommonJSExportsProperty-9]
+}
+
+const _ExportSyntax_name = "ExportSyntaxNoneExportSyntaxModifierExportSyntaxNamedExportSyntaxDefaultModifierExportSyntaxDefaultDeclarationExportSyntaxEqualsExportSyntaxUMDExportSyntaxStarExportSyntaxCommonJSModuleExportsExportSyntaxCommonJSExportsProperty"
+
+var _ExportSyntax_index = [...]uint8{0, 16, 36, 53, 80, 110, 128, 143, 159, 192, 227}
+
+func (i ExportSyntax) String() string {
+ idx := int(i) - 0
+ if i < 0 || idx >= len(_ExportSyntax_index)-1 {
+ return "ExportSyntax(" + strconv.FormatInt(int64(i), 10) + ")"
+ }
+ return _ExportSyntax_name[_ExportSyntax_index[idx]:_ExportSyntax_index[idx+1]]
+}
diff --git a/internal/ls/autoimport/extract.go b/internal/ls/autoimport/extract.go
new file mode 100644
index 0000000000..568d1ef391
--- /dev/null
+++ b/internal/ls/autoimport/extract.go
@@ -0,0 +1,401 @@
+package autoimport
+
+import (
+ "slices"
+ "sync/atomic"
+
+ "github.com/microsoft/typescript-go/internal/ast"
+ "github.com/microsoft/typescript-go/internal/binder"
+ "github.com/microsoft/typescript-go/internal/checker"
+ "github.com/microsoft/typescript-go/internal/core"
+ "github.com/microsoft/typescript-go/internal/ls/lsutil"
+ "github.com/microsoft/typescript-go/internal/module"
+ "github.com/microsoft/typescript-go/internal/tspath"
+)
+
+type symbolExtractor struct {
+ nodeModulesDirectory tspath.Path
+ packageName string
+ stats *extractorStats
+
+ localNameResolver *binder.NameResolver
+ checker *checker.Checker
+}
+
+type exportExtractor struct {
+ *symbolExtractor
+ moduleResolver *module.Resolver
+ toPath func(fileName string) tspath.Path
+}
+
+type extractorStats struct {
+ exports atomic.Int32
+ usedChecker atomic.Int32
+}
+
+func (e *exportExtractor) Stats() *extractorStats {
+ return e.stats
+}
+
+type checkerLease struct {
+ used bool
+ checker *checker.Checker
+}
+
+func (l *checkerLease) GetChecker() *checker.Checker {
+ l.used = true
+ return l.checker
+}
+
+func (l *checkerLease) TryChecker() *checker.Checker {
+ if l.used {
+ return l.checker
+ }
+ return nil
+}
+
+func newSymbolExtractor(nodeModulesDirectory tspath.Path, packageName string, checker *checker.Checker) *symbolExtractor {
+ return &symbolExtractor{
+ nodeModulesDirectory: nodeModulesDirectory,
+ packageName: packageName,
+ checker: checker,
+ localNameResolver: &binder.NameResolver{
+ CompilerOptions: core.EmptyCompilerOptions,
+ },
+ stats: &extractorStats{},
+ }
+}
+
+func (b *registryBuilder) newExportExtractor(nodeModulesDirectory tspath.Path, packageName string, checker *checker.Checker) *exportExtractor {
+ return &exportExtractor{
+ symbolExtractor: newSymbolExtractor(nodeModulesDirectory, packageName, checker),
+ moduleResolver: b.resolver,
+ toPath: b.base.toPath,
+ }
+}
+
+func (e *exportExtractor) extractFromFile(file *ast.SourceFile) []*Export {
+ if file.Symbol != nil {
+ return e.extractFromModule(file)
+ }
+ if len(file.AmbientModuleNames) > 0 {
+ moduleDeclarations := core.Filter(file.Statements.Nodes, ast.IsModuleWithStringLiteralName)
+ var exportCount int
+ for _, decl := range moduleDeclarations {
+ exportCount += len(decl.AsModuleDeclaration().Symbol.Exports)
+ }
+ exports := make([]*Export, 0, exportCount)
+ for _, decl := range moduleDeclarations {
+ e.extractFromModuleDeclaration(decl.AsModuleDeclaration(), file, ModuleID(decl.Name().Text()), "", &exports)
+ }
+ return exports
+ }
+ return nil
+}
+
+func (e *exportExtractor) extractFromModule(file *ast.SourceFile) []*Export {
+ moduleAugmentations := core.MapNonNil(file.ModuleAugmentations, func(name *ast.ModuleName) *ast.ModuleDeclaration {
+ decl := name.Parent
+ if ast.IsGlobalScopeAugmentation(decl) {
+ return nil
+ }
+ return decl.AsModuleDeclaration()
+ })
+ var augmentationExportCount int
+ for _, decl := range moduleAugmentations {
+ augmentationExportCount += len(decl.Symbol.Exports)
+ }
+ exports := make([]*Export, 0, len(file.Symbol.Exports)+augmentationExportCount)
+ for name, symbol := range file.Symbol.Exports {
+ e.extractFromSymbol(name, symbol, ModuleID(file.Path()), file.FileName(), file, &exports)
+ }
+ for _, decl := range moduleAugmentations {
+ name := decl.Name().AsStringLiteral().Text
+ moduleID := ModuleID(name)
+ var moduleFileName string
+ if tspath.IsExternalModuleNameRelative(name) {
+ if resolved, _ := e.moduleResolver.ResolveModuleName(name, file.FileName(), core.ModuleKindCommonJS, nil); resolved.IsResolved() {
+ moduleFileName = resolved.ResolvedFileName
+ moduleID = ModuleID(e.toPath(moduleFileName))
+ } else {
+ // :shrug:
+ moduleFileName = tspath.ResolvePath(tspath.GetDirectoryPath(file.FileName()), name)
+ moduleID = ModuleID(e.toPath(moduleFileName))
+ }
+ }
+ e.extractFromModuleDeclaration(decl, file, moduleID, moduleFileName, &exports)
+ }
+ return exports
+}
+
+func (e *exportExtractor) extractFromModuleDeclaration(decl *ast.ModuleDeclaration, file *ast.SourceFile, moduleID ModuleID, moduleFileName string, exports *[]*Export) {
+ for name, symbol := range decl.Symbol.Exports {
+ e.extractFromSymbol(name, symbol, moduleID, moduleFileName, file, exports)
+ }
+}
+
+func (e *symbolExtractor) extractFromSymbol(name string, symbol *ast.Symbol, moduleID ModuleID, moduleFileName string, file *ast.SourceFile, exports *[]*Export) {
+ if shouldIgnoreSymbol(symbol) {
+ return
+ }
+
+ if name == ast.InternalSymbolNameExportStar {
+ checkerLease := &checkerLease{checker: e.checker}
+ allExports := e.checker.GetExportsOfModule(symbol.Parent)
+ // allExports includes named exports from the file that will be processed separately;
+ // we want to add only the ones that come from the star
+ for name, namedExport := range symbol.Parent.Exports {
+ if name != ast.InternalSymbolNameExportStar {
+ idx := slices.Index(allExports, namedExport)
+ if idx >= 0 || shouldIgnoreSymbol(namedExport) {
+ allExports = slices.Delete(allExports, idx, idx+1)
+ }
+ }
+ }
+
+ *exports = slices.Grow(*exports, len(allExports))
+ for _, reexportedSymbol := range allExports {
+ export, _ := e.createExport(reexportedSymbol, moduleID, moduleFileName, ExportSyntaxStar, file, checkerLease)
+ if export != nil {
+ parent := reexportedSymbol.Parent
+ if parent != nil && parent.IsExternalModule() {
+ targetModuleID, _ := getModuleIDAndFileNameOfModuleSymbol(parent)
+ export.Target = ExportID{
+ ExportName: reexportedSymbol.Name,
+ ModuleID: targetModuleID,
+ }
+ }
+ export.through = ast.InternalSymbolNameExportStar
+ *exports = append(*exports, export)
+ }
+ }
+ return
+ }
+
+ syntax := getSyntax(symbol)
+ checkerLease := &checkerLease{checker: e.checker}
+ export, target := e.createExport(symbol, moduleID, moduleFileName, syntax, file, checkerLease)
+ if export == nil {
+ return
+ }
+
+ if symbol.Name == ast.InternalSymbolNameDefault || symbol.Name == ast.InternalSymbolNameExportEquals {
+ namedSymbol := symbol
+ if s := binder.GetLocalSymbolForExportDefault(symbol); s != nil {
+ namedSymbol = s
+ }
+ export.localName = getDefaultLikeExportNameFromDeclaration(namedSymbol)
+ if isUnusableName(export.localName) {
+ export.localName = export.Target.ExportName
+ }
+ if isUnusableName(export.localName) {
+ if target != nil {
+ namedSymbol = target
+ if s := binder.GetLocalSymbolForExportDefault(target); s != nil {
+ namedSymbol = s
+ }
+ export.localName = getDefaultLikeExportNameFromDeclaration(namedSymbol)
+ if isUnusableName(export.localName) {
+ export.localName = lsutil.ModuleSpecifierToValidIdentifier(string(export.Target.ModuleID), core.ScriptTargetESNext, false)
+ }
+ } else {
+ export.localName = lsutil.ModuleSpecifierToValidIdentifier(string(moduleID), core.ScriptTargetESNext, false)
+ }
+ }
+ }
+
+ *exports = append(*exports, export)
+
+ if target != nil {
+ if syntax == ExportSyntaxEquals && target.Flags&ast.SymbolFlagsNamespace != 0 {
+ *exports = slices.Grow(*exports, len(target.Exports))
+ for _, namedExport := range target.Exports {
+ export, _ := e.createExport(namedExport, moduleID, moduleFileName, syntax, file, checkerLease)
+ if export != nil {
+ export.through = name
+ *exports = append(*exports, export)
+ }
+ }
+ }
+ } else if syntax == ExportSyntaxCommonJSModuleExports {
+ expression := symbol.Declarations[0].AsExportAssignment().Expression
+ if expression.Kind == ast.KindObjectLiteralExpression {
+ // what is actually desirable here? I think it would be reasonable to only treat these as exports
+ // if *every* property is a shorthand property or identifier: identifier
+ // At least, it would be sketchy if there were any methods, computed properties...
+ *exports = slices.Grow(*exports, len(expression.AsObjectLiteralExpression().Properties.Nodes))
+ for _, prop := range expression.AsObjectLiteralExpression().Properties.Nodes {
+ if ast.IsShorthandPropertyAssignment(prop) || ast.IsPropertyAssignment(prop) && prop.AsPropertyAssignment().Name().Kind == ast.KindIdentifier {
+ export, _ := e.createExport(expression.Symbol().Members[prop.Name().Text()], moduleID, moduleFileName, syntax, file, checkerLease)
+ if export != nil {
+ export.through = name
+ *exports = append(*exports, export)
+ }
+ }
+ }
+ }
+ }
+}
+
+// createExport creates an Export for the given symbol, returning the Export and the target symbol if the export is an alias.
+func (e *symbolExtractor) createExport(symbol *ast.Symbol, moduleID ModuleID, moduleFileName string, syntax ExportSyntax, file *ast.SourceFile, checkerLease *checkerLease) (*Export, *ast.Symbol) {
+ if shouldIgnoreSymbol(symbol) {
+ return nil, nil
+ }
+
+ export := &Export{
+ ExportID: ExportID{
+ ExportName: symbol.Name,
+ ModuleID: moduleID,
+ },
+ ModuleFileName: moduleFileName,
+ Syntax: syntax,
+ Flags: symbol.CombinedLocalAndExportSymbolFlags(),
+ Path: file.Path(),
+ NodeModulesDirectory: e.nodeModulesDirectory,
+ PackageName: e.packageName,
+ }
+
+ if syntax == ExportSyntaxUMD {
+ export.ExportName = ast.InternalSymbolNameExportEquals
+ export.localName = symbol.Name
+ }
+
+ var targetSymbol *ast.Symbol
+ if symbol.Flags&ast.SymbolFlagsAlias != 0 {
+ targetSymbol = e.tryResolveSymbol(symbol, syntax, checkerLease)
+ if targetSymbol != nil {
+ var decl *ast.Node
+ if len(targetSymbol.Declarations) > 0 {
+ decl = targetSymbol.Declarations[0]
+ } else if targetSymbol.CheckFlags&ast.CheckFlagsMapped != 0 {
+ if mappedDecl := checkerLease.GetChecker().GetMappedTypeSymbolOfProperty(targetSymbol); mappedDecl != nil && len(mappedDecl.Declarations) > 0 {
+ decl = mappedDecl.Declarations[0]
+ }
+ }
+ if decl == nil {
+ // !!! consider GetImmediateAliasedSymbol to go as far as we can
+ decl = symbol.Declarations[0]
+ }
+ if decl == nil {
+ panic("no declaration for aliased symbol")
+ }
+
+ if checker := checkerLease.TryChecker(); checker != nil {
+ export.Flags = checker.GetSymbolFlags(targetSymbol)
+ export.IsTypeOnly = checker.GetTypeOnlyAliasDeclaration(symbol) != nil
+ } else {
+ export.Flags = targetSymbol.Flags
+ export.IsTypeOnly = core.Some(symbol.Declarations, ast.IsPartOfTypeOnlyImportOrExportDeclaration)
+ }
+ export.ScriptElementKind = lsutil.GetSymbolKind(checkerLease.TryChecker(), targetSymbol, decl)
+ export.ScriptElementKindModifiers = lsutil.GetSymbolModifiers(checkerLease.TryChecker(), targetSymbol)
+ moduleID := ModuleID(ast.GetSourceFileOfNode(decl).Path())
+ parent := targetSymbol.Parent
+ if parent != nil && parent.IsExternalModule() {
+ moduleID, _ = getModuleIDAndFileNameOfModuleSymbol(parent)
+ }
+ export.Target = ExportID{
+ ExportName: targetSymbol.Name,
+ ModuleID: moduleID,
+ }
+ }
+ } else {
+ export.ScriptElementKind = lsutil.GetSymbolKind(checkerLease.TryChecker(), symbol, symbol.Declarations[0])
+ export.ScriptElementKindModifiers = lsutil.GetSymbolModifiers(checkerLease.TryChecker(), symbol)
+ }
+
+ e.stats.exports.Add(1)
+ if checkerLease.TryChecker() != nil {
+ e.stats.usedChecker.Add(1)
+ }
+
+ return export, targetSymbol
+}
+
+func (e *symbolExtractor) tryResolveSymbol(symbol *ast.Symbol, syntax ExportSyntax, checkerLease *checkerLease) *ast.Symbol {
+ if !ast.IsNonLocalAlias(symbol, ast.SymbolFlagsNone) {
+ return symbol
+ }
+
+ var loc *ast.Node
+ var name string
+ switch syntax {
+ case ExportSyntaxNamed:
+ decl := ast.GetDeclarationOfKind(symbol, ast.KindExportSpecifier)
+ if decl.Parent.Parent.AsExportDeclaration().ModuleSpecifier == nil {
+ if n := core.FirstNonZero(decl.Name(), decl.PropertyName()); n.Kind == ast.KindIdentifier {
+ loc = n
+ name = n.Text()
+ }
+ }
+ // !!! check if module.exports = foo is marked as an alias
+ case ExportSyntaxEquals:
+ if symbol.Name != ast.InternalSymbolNameExportEquals {
+ break
+ }
+ fallthrough
+ case ExportSyntaxDefaultDeclaration:
+ decl := ast.GetDeclarationOfKind(symbol, ast.KindExportAssignment)
+ if decl.Expression().Kind == ast.KindIdentifier {
+ loc = decl.Expression()
+ name = loc.Text()
+ }
+ }
+
+ if loc != nil {
+ local := e.localNameResolver.Resolve(loc, name, ast.SymbolFlagsAll, nil, false, false)
+ if local != nil && !ast.IsNonLocalAlias(local, ast.SymbolFlagsNone) {
+ return local
+ }
+ }
+
+ checker := checkerLease.GetChecker()
+ if resolved := checker.GetAliasedSymbol(symbol); !checker.IsUnknownSymbol(resolved) {
+ return resolved
+ }
+ return nil
+}
+
+func shouldIgnoreSymbol(symbol *ast.Symbol) bool {
+ if symbol.Flags&ast.SymbolFlagsPrototype != 0 {
+ return true
+ }
+ return false
+}
+
+func getSyntax(symbol *ast.Symbol) ExportSyntax {
+ for _, decl := range symbol.Declarations {
+ switch decl.Kind {
+ case ast.KindExportSpecifier:
+ return ExportSyntaxNamed
+ case ast.KindExportAssignment:
+ return core.IfElse(
+ decl.AsExportAssignment().IsExportEquals,
+ ExportSyntaxEquals,
+ ExportSyntaxDefaultDeclaration,
+ )
+ case ast.KindNamespaceExportDeclaration:
+ return ExportSyntaxUMD
+ case ast.KindJSExportAssignment:
+ return ExportSyntaxCommonJSModuleExports
+ case ast.KindCommonJSExport:
+ return ExportSyntaxCommonJSExportsProperty
+ default:
+ if ast.GetCombinedModifierFlags(decl)&ast.ModifierFlagsDefault != 0 {
+ return ExportSyntaxDefaultModifier
+ } else {
+ return ExportSyntaxModifier
+ }
+ }
+ }
+ return ExportSyntaxNone
+}
+
+func isUnusableName(name string) bool {
+ return name == "" ||
+ name == "_default" ||
+ name == ast.InternalSymbolNameExportStar ||
+ name == ast.InternalSymbolNameDefault ||
+ name == ast.InternalSymbolNameExportEquals
+}
diff --git a/internal/ls/autoimport/fix.go b/internal/ls/autoimport/fix.go
new file mode 100644
index 0000000000..32e7667d63
--- /dev/null
+++ b/internal/ls/autoimport/fix.go
@@ -0,0 +1,1175 @@
+package autoimport
+
+import (
+ "cmp"
+ "context"
+ "fmt"
+ "slices"
+ "strings"
+ "unicode"
+
+ "github.com/microsoft/typescript-go/internal/ast"
+ "github.com/microsoft/typescript-go/internal/astnav"
+ "github.com/microsoft/typescript-go/internal/checker"
+ "github.com/microsoft/typescript-go/internal/collections"
+ "github.com/microsoft/typescript-go/internal/compiler"
+ "github.com/microsoft/typescript-go/internal/core"
+ "github.com/microsoft/typescript-go/internal/debug"
+ "github.com/microsoft/typescript-go/internal/diagnostics"
+ "github.com/microsoft/typescript-go/internal/format"
+ "github.com/microsoft/typescript-go/internal/locale"
+ "github.com/microsoft/typescript-go/internal/ls/change"
+ "github.com/microsoft/typescript-go/internal/ls/lsconv"
+ "github.com/microsoft/typescript-go/internal/ls/lsutil"
+ "github.com/microsoft/typescript-go/internal/ls/organizeimports"
+ "github.com/microsoft/typescript-go/internal/lsp/lsproto"
+ "github.com/microsoft/typescript-go/internal/modulespecifiers"
+ "github.com/microsoft/typescript-go/internal/scanner"
+ "github.com/microsoft/typescript-go/internal/stringutil"
+ "github.com/microsoft/typescript-go/internal/tspath"
+)
+
+type newImportBinding struct {
+ kind lsproto.ImportKind
+ propertyName string
+ name string
+ addAsTypeOnly lsproto.AddAsTypeOnly
+}
+
+type Fix struct {
+ *lsproto.AutoImportFix
+
+ ModuleSpecifierKind modulespecifiers.ResultKind
+ IsReExport bool
+ ModuleFileName string
+ TypeOnlyAliasDeclaration *ast.Declaration
+}
+
+func (f *Fix) Edits(
+ ctx context.Context,
+ file *ast.SourceFile,
+ compilerOptions *core.CompilerOptions,
+ formatOptions *format.FormatCodeSettings,
+ converters *lsconv.Converters,
+ preferences *lsutil.UserPreferences,
+) ([]*lsproto.TextEdit, string) {
+ locale := locale.FromContext(ctx)
+ tracker := change.NewTracker(ctx, compilerOptions, formatOptions, converters)
+ switch f.Kind {
+ case lsproto.AutoImportFixKindUseNamespace:
+ if f.UsagePosition == nil || f.NamespacePrefix == "" {
+ panic("namespace fix requires usage position and prefix")
+ }
+ qualified := fmt.Sprintf("%s.%s", f.NamespacePrefix, f.Name)
+ tracker.InsertText(file, *f.UsagePosition, f.NamespacePrefix+".")
+ return tracker.GetChanges()[file.FileName()], diagnostics.Change_0_to_1.Localize(locale, f.Name, qualified)
+ case lsproto.AutoImportFixKindAddToExisting:
+ if len(file.Imports()) <= int(f.ImportIndex) {
+ panic("import index out of range")
+ }
+ moduleSpecifier := file.Imports()[f.ImportIndex]
+ importNode := ast.TryGetImportFromModuleSpecifier(moduleSpecifier)
+ if importNode == nil {
+ panic("expected import declaration")
+ }
+ var importClauseOrBindingPattern *ast.Node
+ switch importNode.Kind {
+ case ast.KindImportDeclaration:
+ importClauseOrBindingPattern = importNode.ImportClause()
+ if importClauseOrBindingPattern == nil {
+ panic("expected import clause")
+ }
+ case ast.KindCallExpression:
+ if !ast.IsVariableDeclarationInitializedToRequire(importNode.Parent) {
+ panic("expected require call expression to be in variable declaration")
+ }
+ importClauseOrBindingPattern = importNode.Parent.Name()
+ if importClauseOrBindingPattern == nil || !ast.IsObjectBindingPattern(importClauseOrBindingPattern) {
+ panic("expected object binding pattern in variable declaration")
+ }
+ default:
+ panic("expected import declaration or require call expression")
+ }
+
+ defaultImport := core.IfElse(f.ImportKind == lsproto.ImportKindDefault, &newImportBinding{kind: lsproto.ImportKindDefault, name: f.Name, addAsTypeOnly: f.AddAsTypeOnly}, nil)
+ namedImports := core.IfElse(f.ImportKind == lsproto.ImportKindNamed, []*newImportBinding{{kind: lsproto.ImportKindNamed, name: f.Name, addAsTypeOnly: f.AddAsTypeOnly}}, nil)
+ addToExistingImport(tracker, file, importClauseOrBindingPattern, defaultImport, namedImports, preferences)
+ return tracker.GetChanges()[file.FileName()], diagnostics.Update_import_from_0.Localize(locale, f.ModuleSpecifier)
+ case lsproto.AutoImportFixKindAddNew:
+ var declarations []*ast.Statement
+ defaultImport := core.IfElse(f.ImportKind == lsproto.ImportKindDefault, &newImportBinding{name: f.Name, addAsTypeOnly: f.AddAsTypeOnly}, nil)
+ namedImports := core.IfElse(f.ImportKind == lsproto.ImportKindNamed, []*newImportBinding{{name: f.Name, addAsTypeOnly: f.AddAsTypeOnly}}, nil)
+ var namespaceLikeImport *newImportBinding
+ // qualification := f.qualification()
+ if f.ImportKind == lsproto.ImportKindNamespace || f.ImportKind == lsproto.ImportKindCommonJS {
+ namespaceLikeImport = &newImportBinding{kind: f.ImportKind, name: f.Name}
+ // if qualification != nil && qualification.namespacePref != "" {
+ // namespaceLikeImport.name = qualification.namespacePref
+ // }
+ }
+
+ quotePreference := lsutil.GetQuotePreference(file, preferences)
+ if f.UseRequire {
+ declarations = getNewRequires(tracker, f.ModuleSpecifier, quotePreference, defaultImport, namedImports, namespaceLikeImport, compilerOptions)
+ } else {
+ declarations = getNewImports(tracker, f.ModuleSpecifier, quotePreference, defaultImport, namedImports, namespaceLikeImport, compilerOptions, preferences)
+ }
+
+ insertImports(
+ tracker,
+ file,
+ declarations,
+ /*blankLineBetween*/ true,
+ preferences,
+ )
+ // if qualification != nil {
+ // addNamespaceQualifier(tracker, file, qualification)
+ // }
+ return tracker.GetChanges()[file.FileName()], diagnostics.Add_import_from_0.Localize(locale, f.ModuleSpecifier)
+ case lsproto.AutoImportFixKindPromoteTypeOnly:
+ promotedDeclaration := promoteFromTypeOnly(tracker, f.TypeOnlyAliasDeclaration, compilerOptions, file, preferences)
+ if promotedDeclaration.Kind == ast.KindImportSpecifier {
+ moduleSpec := getModuleSpecifierText(promotedDeclaration.Parent.Parent)
+ return tracker.GetChanges()[file.FileName()], diagnostics.Remove_type_from_import_of_0_from_1.Localize(locale, f.Name, moduleSpec)
+ }
+ moduleSpec := getModuleSpecifierText(promotedDeclaration)
+ return tracker.GetChanges()[file.FileName()], diagnostics.Remove_type_from_import_declaration_from_0.Localize(locale, moduleSpec)
+ case lsproto.AutoImportFixKindJsdocTypeImport:
+ if f.UsagePosition == nil {
+ panic("UsagePosition must be set for JSDoc type import fix")
+ }
+ quotePreference := lsutil.GetQuotePreference(file, preferences)
+ quoteChar := "\""
+ if quotePreference == lsutil.QuotePreferenceSingle {
+ quoteChar = "'"
+ }
+ importTypePrefix := fmt.Sprintf("import(%s%s%s).", quoteChar, f.ModuleSpecifier, quoteChar)
+ tracker.InsertText(file, *f.UsagePosition, importTypePrefix)
+ return tracker.GetChanges()[file.FileName()], diagnostics.Change_0_to_1.Localize(locale, f.Name, importTypePrefix+f.Name)
+ default:
+ panic("unimplemented fix edit")
+ }
+}
+
+func addToExistingImport(
+ ct *change.Tracker,
+ file *ast.SourceFile,
+ importClauseOrBindingPattern *ast.Node,
+ defaultImport *newImportBinding,
+ namedImports []*newImportBinding,
+ preferences *lsutil.UserPreferences,
+) {
+ switch importClauseOrBindingPattern.Kind {
+ case ast.KindObjectBindingPattern:
+ bindingPattern := importClauseOrBindingPattern.AsBindingPattern()
+ if defaultImport != nil {
+ addElementToBindingPattern(ct, file, bindingPattern, defaultImport.name, "default")
+ }
+ for _, namedImport := range namedImports {
+ addElementToBindingPattern(ct, file, bindingPattern, namedImport.name, "")
+ }
+ return
+ case ast.KindImportClause:
+ importClause := importClauseOrBindingPattern.AsImportClause()
+
+ // promoteFromTypeOnly = true if we need to promote the entire original clause from type only
+ promoteFromTypeOnly := importClause.IsTypeOnly() && core.Some(append(namedImports, defaultImport), func(i *newImportBinding) bool {
+ if i == nil {
+ return false
+ }
+ return i.addAsTypeOnly == lsproto.AddAsTypeOnlyNotAllowed
+ })
+
+ var existingSpecifiers []*ast.Node
+ if importClause.NamedBindings != nil && importClause.NamedBindings.Kind == ast.KindNamedImports {
+ existingSpecifiers = importClause.NamedBindings.Elements()
+ }
+
+ if defaultImport != nil {
+ debug.Assert(importClause.Name() == nil, "Cannot add a default import to an import clause that already has one")
+ ct.InsertNodeAt(file, core.TextPos(astnav.GetStartOfNode(importClause.AsNode(), file, false)), ct.NodeFactory.NewIdentifier(defaultImport.name), change.NodeOptions{Suffix: ", "})
+ }
+
+ if len(namedImports) > 0 {
+ specifierComparer, isSorted := organizeimports.GetNamedImportSpecifierComparerWithDetection(importClause.Parent, file, preferences)
+ newSpecifiers := core.Map(namedImports, func(namedImport *newImportBinding) *ast.Node {
+ var identifier *ast.Node
+ if namedImport.propertyName != "" {
+ identifier = ct.NodeFactory.NewIdentifier(namedImport.propertyName).AsIdentifier().AsNode()
+ }
+ return ct.NodeFactory.NewImportSpecifier(
+ shouldUseTypeOnly(namedImport.addAsTypeOnly, preferences),
+ identifier,
+ ct.NodeFactory.NewIdentifier(namedImport.name),
+ )
+ })
+ slices.SortFunc(newSpecifiers, specifierComparer)
+ 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 (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(specsToCompareAgainst, spec, specifierComparer)
+ ct.InsertImportSpecifierAtIndex(file, spec, importClause.NamedBindings, insertionIndex)
+ }
+ } else if len(existingSpecifiers) > 0 && isSorted.IsTrue() {
+ // Existing specifiers are sorted, so insert each new specifier at the correct position
+ for _, spec := range newSpecifiers {
+ insertionIndex := organizeimports.GetImportSpecifierInsertionIndex(existingSpecifiers, spec, specifierComparer)
+ if insertionIndex >= len(existingSpecifiers) {
+ // Insert at the end
+ ct.InsertNodeInListAfter(file, existingSpecifiers[len(existingSpecifiers)-1], spec.AsNode(), existingSpecifiers)
+ } else {
+ // Insert before the element at insertionIndex
+ ct.InsertNodeInListAfter(file, existingSpecifiers[insertionIndex], spec.AsNode(), existingSpecifiers)
+ }
+ }
+ } else if len(existingSpecifiers) > 0 {
+ // Existing specifiers may not be sorted, append to the end
+ for _, spec := range newSpecifiers {
+ ct.InsertNodeInListAfter(file, existingSpecifiers[len(existingSpecifiers)-1], spec.AsNode(), existingSpecifiers)
+ }
+ } else {
+ if len(newSpecifiers) > 0 {
+ namedImports := ct.NodeFactory.NewNamedImports(ct.NodeFactory.NewNodeList(newSpecifiers))
+ if importClause.NamedBindings != nil {
+ ct.ReplaceNode(file, importClause.NamedBindings, namedImports, nil)
+ } else {
+ if importClause.Name() == nil {
+ panic("Import clause must have either named imports or a default import")
+ }
+ ct.InsertNodeAfter(file, importClause.Name(), namedImports)
+ }
+ }
+ }
+ }
+
+ if promoteFromTypeOnly {
+ // Delete the 'type' keyword from the import clause
+ typeKeyword := getTypeKeywordOfTypeOnlyImport(importClause, file)
+ ct.Delete(file, typeKeyword)
+
+ // 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(file, ast.KindTypeKeyword, specifier)
+ }
+ }
+ }
+ }
+ default:
+ panic("Unsupported clause kind: " + importClauseOrBindingPattern.KindString() + " for addToExistingImport")
+ }
+}
+
+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,
+ file *ast.SourceFile,
+ bindingPattern *ast.BindingPattern,
+ name string,
+ propertyName string,
+) {
+ element := ct.NodeFactory.NewBindingElement(nil, nil, ct.NodeFactory.NewIdentifier(name), core.IfElse(propertyName == "", nil, ct.NodeFactory.NewIdentifier(propertyName)))
+ if len(bindingPattern.Elements.Nodes) > 0 {
+ ct.InsertNodeInListAfter(file, bindingPattern.Elements.Nodes[len(bindingPattern.Elements.Nodes)-1], element, bindingPattern.Elements.Nodes)
+ } else {
+ ct.ReplaceNode(file, bindingPattern.AsNode(), ct.NodeFactory.NewBindingPattern(ast.KindObjectBindingPattern, ct.AsNodeFactory().NewNodeList([]*ast.Node{element})), nil)
+ }
+}
+
+func getNewImports(
+ ct *change.Tracker,
+ moduleSpecifier string,
+ quotePreference lsutil.QuotePreference,
+ defaultImport *newImportBinding,
+ namedImports []*newImportBinding,
+ namespaceLikeImport *newImportBinding, // { lsproto.importKind: lsproto.ImportKind.CommonJS | lsproto.ImportKind.Namespace; }
+ compilerOptions *core.CompilerOptions,
+ preferences *lsutil.UserPreferences,
+) []*ast.Statement {
+ tokenFlags := core.IfElse(quotePreference == lsutil.QuotePreferenceSingle, ast.TokenFlagsSingleQuote, ast.TokenFlagsNone)
+ moduleSpecifierStringLiteral := ct.NodeFactory.NewStringLiteral(moduleSpecifier, tokenFlags)
+ var statements []*ast.Statement // []AnyImportSyntax
+ if defaultImport != nil || len(namedImports) > 0 {
+ // `verbatimModuleSyntax` should prefer top-level `import type` -
+ // even though it's not an error, it would add unnecessary runtime emit.
+ topLevelTypeOnly := (defaultImport == nil || needsTypeOnly(defaultImport.addAsTypeOnly)) &&
+ core.Every(namedImports, func(i *newImportBinding) bool { return needsTypeOnly(i.addAsTypeOnly) }) ||
+ (compilerOptions.VerbatimModuleSyntax.IsTrue() || preferences.PreferTypeOnlyAutoImports.IsTrue()) &&
+ (defaultImport == nil || defaultImport.addAsTypeOnly != lsproto.AddAsTypeOnlyNotAllowed) &&
+ !core.Some(namedImports, func(i *newImportBinding) bool { return i.addAsTypeOnly == lsproto.AddAsTypeOnlyNotAllowed })
+
+ var defaultImportNode *ast.Node
+ if defaultImport != nil {
+ defaultImportNode = ct.NodeFactory.NewIdentifier(defaultImport.name)
+ }
+
+ statements = append(statements, makeImport(ct, defaultImportNode, core.Map(namedImports, func(namedImport *newImportBinding) *ast.Node {
+ var namedImportPropertyName *ast.Node
+ if namedImport.propertyName != "" {
+ namedImportPropertyName = ct.NodeFactory.NewIdentifier(namedImport.propertyName)
+ }
+ return ct.NodeFactory.NewImportSpecifier(
+ !topLevelTypeOnly && shouldUseTypeOnly(namedImport.addAsTypeOnly, preferences),
+ namedImportPropertyName,
+ ct.NodeFactory.NewIdentifier(namedImport.name),
+ )
+ }), moduleSpecifierStringLiteral, topLevelTypeOnly))
+ }
+
+ if namespaceLikeImport != nil {
+ var declaration *ast.Statement
+ if namespaceLikeImport.kind == lsproto.ImportKindCommonJS {
+ declaration = ct.NodeFactory.NewImportEqualsDeclaration(
+ /*modifiers*/ nil,
+ shouldUseTypeOnly(namespaceLikeImport.addAsTypeOnly, preferences),
+ ct.NodeFactory.NewIdentifier(namespaceLikeImport.name),
+ ct.NodeFactory.NewExternalModuleReference(moduleSpecifierStringLiteral),
+ )
+ } else {
+ declaration = ct.NodeFactory.NewImportDeclaration(
+ /*modifiers*/ nil,
+ ct.NodeFactory.NewImportClause(
+ /*phaseModifier*/ core.IfElse(shouldUseTypeOnly(namespaceLikeImport.addAsTypeOnly, preferences), ast.KindTypeKeyword, ast.KindUnknown),
+ /*name*/ nil,
+ ct.NodeFactory.NewNamespaceImport(ct.NodeFactory.NewIdentifier(namespaceLikeImport.name)),
+ ),
+ moduleSpecifierStringLiteral,
+ /*attributes*/ nil,
+ )
+ }
+ statements = append(statements, declaration)
+ }
+ if len(statements) == 0 {
+ panic("No statements to insert for new imports")
+ }
+ return statements
+}
+
+func getNewRequires(
+ changeTracker *change.Tracker,
+ moduleSpecifier string,
+ quotePreference lsutil.QuotePreference,
+ defaultImport *newImportBinding,
+ namedImports []*newImportBinding,
+ namespaceLikeImport *newImportBinding,
+ compilerOptions *core.CompilerOptions,
+) []*ast.Statement {
+ quotedModuleSpecifier := changeTracker.NodeFactory.NewStringLiteral(
+ moduleSpecifier,
+ core.IfElse(quotePreference == lsutil.QuotePreferenceSingle, ast.TokenFlagsSingleQuote, ast.TokenFlagsNone),
+ )
+ var statements []*ast.Statement
+
+ // const { default: foo, bar, etc } = require('./mod');
+ if defaultImport != nil || len(namedImports) > 0 {
+ bindingElements := []*ast.Node{}
+ for _, namedImport := range namedImports {
+ var propertyName *ast.Node
+ if namedImport.propertyName != "" {
+ propertyName = changeTracker.NodeFactory.NewIdentifier(namedImport.propertyName)
+ }
+ bindingElements = append(bindingElements, changeTracker.NodeFactory.NewBindingElement(
+ /*dotDotDotToken*/ nil,
+ propertyName,
+ changeTracker.NodeFactory.NewIdentifier(namedImport.name),
+ /*initializer*/ nil,
+ ))
+ }
+ if defaultImport != nil {
+ bindingElements = append([]*ast.Node{
+ changeTracker.NodeFactory.NewBindingElement(
+ /*dotDotDotToken*/ nil,
+ changeTracker.NodeFactory.NewIdentifier("default"),
+ changeTracker.NodeFactory.NewIdentifier(defaultImport.name),
+ /*initializer*/ nil,
+ ),
+ }, bindingElements...)
+ }
+ declaration := createConstEqualsRequireDeclaration(
+ changeTracker,
+ changeTracker.NodeFactory.NewBindingPattern(
+ ast.KindObjectBindingPattern,
+ changeTracker.NodeFactory.NewNodeList(bindingElements),
+ ),
+ quotedModuleSpecifier,
+ )
+ statements = append(statements, declaration)
+ }
+
+ // const foo = require('./mod');
+ if namespaceLikeImport != nil {
+ declaration := createConstEqualsRequireDeclaration(
+ changeTracker,
+ changeTracker.NodeFactory.NewIdentifier(namespaceLikeImport.name),
+ quotedModuleSpecifier,
+ )
+ statements = append(statements, declaration)
+ }
+
+ debug.AssertIsDefined(statements)
+ return statements
+}
+
+func createConstEqualsRequireDeclaration(changeTracker *change.Tracker, name *ast.Node, quotedModuleSpecifier *ast.Node) *ast.Statement {
+ return changeTracker.NodeFactory.NewVariableStatement(
+ /*modifiers*/ nil,
+ changeTracker.NodeFactory.NewVariableDeclarationList(
+ ast.NodeFlagsConst,
+ changeTracker.NodeFactory.NewNodeList([]*ast.Node{
+ changeTracker.NodeFactory.NewVariableDeclaration(
+ name,
+ /*exclamationToken*/ nil,
+ /*type*/ nil,
+ changeTracker.NodeFactory.NewCallExpression(
+ changeTracker.NodeFactory.NewIdentifier("require"),
+ /*questionDotToken*/ nil,
+ /*typeArguments*/ nil,
+ changeTracker.NodeFactory.NewNodeList([]*ast.Node{quotedModuleSpecifier}),
+ ast.NodeFlagsNone,
+ ),
+ ),
+ }),
+ ),
+ )
+}
+
+func insertImports(ct *change.Tracker, sourceFile *ast.SourceFile, imports []*ast.Statement, blankLineBetween bool, preferences *lsutil.UserPreferences) {
+ var existingImportStatements []*ast.Statement
+
+ if imports[0].Kind == ast.KindVariableStatement {
+ existingImportStatements = core.Filter(sourceFile.Statements.Nodes, ast.IsRequireVariableStatement)
+ } else {
+ existingImportStatements = core.Filter(sourceFile.Statements.Nodes, ast.IsAnyImportSyntax)
+ }
+ comparer, isSorted := organizeimports.GetOrganizeImportsStringComparerWithDetection(existingImportStatements, preferences)
+ sortedNewImports := slices.Clone(imports)
+ slices.SortFunc(sortedNewImports, func(a, b *ast.Statement) int {
+ return organizeimports.CompareImportsOrRequireStatements(a, b, comparer)
+ })
+
+ if len(existingImportStatements) > 0 && isSorted {
+ // Existing imports are sorted, insert each new import at the correct position
+ for _, newImport := range sortedNewImports {
+ insertionIndex := organizeimports.GetImportDeclarationInsertIndex(existingImportStatements, newImport, func(a, b *ast.Statement) stringutil.Comparison {
+ return organizeimports.CompareImportsOrRequireStatements(a, b, comparer)
+ })
+ if insertionIndex == 0 {
+ // If the first import is top-of-file, insert after the leading comment which is likely the header
+ ct.InsertNodeAt(sourceFile, core.TextPos(astnav.GetStartOfNode(existingImportStatements[0], sourceFile, false)), newImport.AsNode(), change.NodeOptions{})
+ } else {
+ prevImport := existingImportStatements[insertionIndex-1]
+ ct.InsertNodeAfter(sourceFile, prevImport.AsNode(), newImport.AsNode())
+ }
+ }
+ } else if len(existingImportStatements) > 0 {
+ ct.InsertNodesAfter(sourceFile, existingImportStatements[len(existingImportStatements)-1], sortedNewImports)
+ } else {
+ ct.InsertAtTopOfFile(sourceFile, sortedNewImports, blankLineBetween)
+ }
+}
+
+func makeImport(ct *change.Tracker, defaultImport *ast.IdentifierNode, namedImports []*ast.Node, moduleSpecifier *ast.Expression, isTypeOnly bool) *ast.Statement {
+ var newNamedImports *ast.Node
+ if len(namedImports) > 0 {
+ newNamedImports = ct.NodeFactory.NewNamedImports(ct.NodeFactory.NewNodeList(namedImports))
+ }
+ var importClause *ast.Node
+ if defaultImport != nil || newNamedImports != nil {
+ importClause = ct.NodeFactory.NewImportClause(core.IfElse(isTypeOnly, ast.KindTypeKeyword, ast.KindUnknown), defaultImport, newNamedImports)
+ }
+ return ct.NodeFactory.NewImportDeclaration( /*modifiers*/ nil, importClause, moduleSpecifier, nil /*attributes*/)
+}
+
+func (v *View) GetFixes(ctx context.Context, export *Export, forJSX bool, isValidTypeOnlyUseSite bool, usagePosition *lsproto.Position) []*Fix {
+ var fixes []*Fix
+ if namespaceFix := v.tryUseExistingNamespaceImport(ctx, export, usagePosition); namespaceFix != nil {
+ fixes = append(fixes, namespaceFix)
+ }
+
+ if fix := v.tryAddToExistingImport(ctx, export, isValidTypeOnlyUseSite); fix != nil {
+ return append(fixes, fix)
+ }
+
+ // !!! getNewImportFromExistingSpecifier - even worth it?
+
+ moduleSpecifier, moduleSpecifierKind := v.GetModuleSpecifier(export, v.preferences)
+ if moduleSpecifier == "" {
+ if len(fixes) > 0 {
+ return fixes
+ }
+ return nil
+ }
+
+ // Check if we need a JSDoc import type fix (for JS files with type-only imports)
+ isJs := tspath.HasJSFileExtension(v.importingFile.FileName())
+ importedSymbolHasValueMeaning := export.Flags&ast.SymbolFlagsValue != 0 || export.IsUnresolvedAlias()
+ if !importedSymbolHasValueMeaning && isJs && usagePosition != nil {
+ // For pure types in JS files, use JSDoc import type syntax
+ return []*Fix{
+ {
+ AutoImportFix: &lsproto.AutoImportFix{
+ Kind: lsproto.AutoImportFixKindJsdocTypeImport,
+ ModuleSpecifier: moduleSpecifier,
+ Name: export.Name(),
+ UsagePosition: usagePosition,
+ },
+ ModuleSpecifierKind: moduleSpecifierKind,
+ IsReExport: export.Target.ModuleID != export.ModuleID,
+ ModuleFileName: export.ModuleFileName,
+ },
+ }
+ }
+
+ importKind := getImportKind(v.importingFile, export, v.program)
+ addAsTypeOnly := getAddAsTypeOnly(isValidTypeOnlyUseSite, export, v.program.Options())
+
+ name := export.Name()
+ startsWithUpper := unicode.IsUpper(rune(name[0]))
+ if forJSX && !startsWithUpper {
+ if export.IsRenameable() {
+ name = fmt.Sprintf("%c%s", unicode.ToUpper(rune(name[0])), name[1:])
+ } else {
+ return nil
+ }
+ }
+
+ return append(fixes, &Fix{
+ AutoImportFix: &lsproto.AutoImportFix{
+ Kind: lsproto.AutoImportFixKindAddNew,
+ ImportKind: importKind,
+ ModuleSpecifier: moduleSpecifier,
+ Name: name,
+ UseRequire: v.shouldUseRequire(),
+ AddAsTypeOnly: addAsTypeOnly,
+ },
+ ModuleSpecifierKind: moduleSpecifierKind,
+ IsReExport: export.Target.ModuleID != export.ModuleID,
+ ModuleFileName: export.ModuleFileName,
+ })
+}
+
+// getAddAsTypeOnly determines if an import should be type-only based on usage context
+func getAddAsTypeOnly(isValidTypeOnlyUseSite bool, export *Export, compilerOptions *core.CompilerOptions) lsproto.AddAsTypeOnly {
+ if !isValidTypeOnlyUseSite {
+ // Can't use a type-only import if the usage is an emitting position
+ return lsproto.AddAsTypeOnlyNotAllowed
+ }
+ if compilerOptions.VerbatimModuleSyntax.IsTrue() && (export.IsTypeOnly || export.Flags&ast.SymbolFlagsValue == 0) ||
+ export.IsTypeOnly && export.Flags&ast.SymbolFlagsValue != 0 {
+ // A type-only import is required for this symbol if under verbatimModuleSyntax and it's purely a type
+ return lsproto.AddAsTypeOnlyRequired
+ }
+ return lsproto.AddAsTypeOnlyAllowed
+}
+
+func (v *View) tryUseExistingNamespaceImport(ctx context.Context, export *Export, usagePosition *lsproto.Position) *Fix {
+ if usagePosition == nil {
+ return nil
+ }
+
+ if getImportKind(v.importingFile, export, v.program) != lsproto.ImportKindNamed {
+ return nil
+ }
+
+ existingImports := v.getExistingImports(ctx)
+ matchingDeclarations := existingImports.Get(export.ModuleID)
+ for _, existingImport := range matchingDeclarations {
+ namespacePrefix := getNamespaceLikeImportText(existingImport.node)
+ if namespacePrefix == "" || existingImport.moduleSpecifier == "" {
+ continue
+ }
+ return &Fix{
+ AutoImportFix: &lsproto.AutoImportFix{
+ Kind: lsproto.AutoImportFixKindUseNamespace,
+ Name: export.Name(),
+ ModuleSpecifier: existingImport.moduleSpecifier,
+ ImportKind: lsproto.ImportKindNamespace,
+ AddAsTypeOnly: lsproto.AddAsTypeOnlyAllowed,
+ ImportIndex: int32(existingImport.index),
+ UsagePosition: usagePosition,
+ NamespacePrefix: namespacePrefix,
+ },
+ }
+ }
+
+ return nil
+}
+
+func getNamespaceLikeImportText(declaration *ast.Node) 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:
+ return ""
+ }
+}
+
+func (v *View) tryAddToExistingImport(
+ ctx context.Context,
+ export *Export,
+ isValidTypeOnlyUseSite bool,
+) *Fix {
+ existingImports := v.getExistingImports(ctx)
+ matchingDeclarations := existingImports.Get(export.ModuleID)
+ if len(matchingDeclarations) == 0 {
+ return nil
+ }
+
+ // Can't use an es6 import for a type in JS.
+ if ast.IsSourceFileJS(v.importingFile) && export.Flags&ast.SymbolFlagsValue == 0 && !core.Every(matchingDeclarations, func(i existingImport) bool {
+ return ast.IsJSDocImportTag(i.node)
+ }) {
+ return nil
+ }
+
+ importKind := getImportKind(v.importingFile, export, v.program)
+ if importKind == lsproto.ImportKindCommonJS || importKind == lsproto.ImportKindNamespace {
+ return nil
+ }
+
+ addAsTypeOnly := getAddAsTypeOnly(isValidTypeOnlyUseSite, export, v.program.Options())
+
+ for _, existingImport := range matchingDeclarations {
+ if existingImport.node.Kind == ast.KindImportEqualsDeclaration {
+ continue
+ }
+
+ if existingImport.node.Kind == ast.KindVariableDeclaration {
+ if (importKind == lsproto.ImportKindNamed || importKind == lsproto.ImportKindDefault) && existingImport.node.Name().Kind == ast.KindObjectBindingPattern {
+ return &Fix{
+ AutoImportFix: &lsproto.AutoImportFix{
+ Kind: lsproto.AutoImportFixKindAddToExisting,
+ Name: export.Name(),
+ ImportKind: importKind,
+ ImportIndex: int32(existingImport.index),
+ ModuleSpecifier: existingImport.moduleSpecifier,
+ AddAsTypeOnly: addAsTypeOnly,
+ },
+ }
+ }
+ continue
+ }
+
+ importClauseNode := existingImport.node.ImportClause()
+ if importClauseNode == nil || !ast.IsStringLiteralLike(existingImport.node.ModuleSpecifier()) {
+ // Side-effect import (no import clause) - can't add to it
+ continue
+ }
+ importClause := importClauseNode.AsImportClause()
+
+ namedBindings := importClause.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() && !(importKind == lsproto.ImportKindNamed && namedBindings != nil) {
+ continue
+ }
+
+ if importKind == lsproto.ImportKindDefault && importClause.Name() != nil {
+ // Cannot add a default import to a declaration that already has one
+ continue
+ }
+
+ // Cannot add a named import to a declaration that has a namespace import
+ if importKind == lsproto.ImportKindNamed && namedBindings != nil && namedBindings.Kind == ast.KindNamespaceImport {
+ continue
+ }
+
+ return &Fix{
+ AutoImportFix: &lsproto.AutoImportFix{
+ Kind: lsproto.AutoImportFixKindAddToExisting,
+ Name: export.Name(),
+ ImportKind: importKind,
+ ImportIndex: int32(existingImport.index),
+ ModuleSpecifier: existingImport.moduleSpecifier,
+ AddAsTypeOnly: addAsTypeOnly,
+ },
+ }
+ }
+
+ return nil
+}
+
+func getImportKind(importingFile *ast.SourceFile, export *Export, program *compiler.Program) lsproto.ImportKind {
+ if program.Options().VerbatimModuleSyntax.IsTrue() && program.GetEmitModuleFormatOfFile(importingFile) == core.ModuleKindCommonJS {
+ return lsproto.ImportKindCommonJS
+ }
+ switch export.Syntax {
+ case ExportSyntaxDefaultModifier, ExportSyntaxDefaultDeclaration:
+ return lsproto.ImportKindDefault
+ case ExportSyntaxNamed:
+ if export.ExportName == ast.InternalSymbolNameDefault {
+ return lsproto.ImportKindDefault
+ }
+ fallthrough
+ case ExportSyntaxModifier, ExportSyntaxStar, ExportSyntaxCommonJSExportsProperty:
+ return lsproto.ImportKindNamed
+ case ExportSyntaxEquals, ExportSyntaxCommonJSModuleExports, ExportSyntaxUMD:
+ // export.Syntax will be ExportSyntaxEquals for named exports/properties of an export='s target.
+ if export.ExportName != ast.InternalSymbolNameExportEquals {
+ return lsproto.ImportKindNamed
+ }
+ // !!! cache this?
+ for _, statement := range importingFile.Statements.Nodes {
+ // `import foo` parses as an ImportEqualsDeclaration even though it could be an ImportDeclaration
+ if ast.IsImportEqualsDeclaration(statement) && !ast.NodeIsMissing(statement.AsImportEqualsDeclaration().ModuleReference) {
+ return lsproto.ImportKindCommonJS
+ }
+ }
+ // !!! this logic feels weird; we're basically trying to predict if shouldUseRequire is going to
+ // be true. The meaning of "default import" is different depending on whether we write it as
+ // a require or an es6 import. The latter, compiled to CJS, has interop built in that will
+ // avoid accessing .default, but if we write a require directly and call it a default import,
+ // we emit an unconditional .default access.
+ if importingFile.ExternalModuleIndicator != nil || !ast.IsSourceFileJS(importingFile) {
+ return lsproto.ImportKindDefault
+ }
+ return lsproto.ImportKindCommonJS
+ default:
+ panic("unhandled export syntax kind: " + export.Syntax.String())
+ }
+}
+
+type existingImport struct {
+ node *ast.Node
+ moduleSpecifier string
+ index int
+}
+
+func (v *View) getExistingImports(ctx context.Context) *collections.MultiMap[ModuleID, existingImport] {
+ if v.existingImports != nil {
+ return v.existingImports
+ }
+
+ result := collections.NewMultiMapWithSizeHint[ModuleID, existingImport](len(v.importingFile.Imports()))
+ ch, done := v.program.GetTypeChecker(ctx)
+ defer done()
+ for i, moduleSpecifier := range v.importingFile.Imports() {
+ node := ast.TryGetImportFromModuleSpecifier(moduleSpecifier)
+ if node == nil {
+ panic("error: did not expect node kind " + moduleSpecifier.Kind.String())
+ } else if ast.IsVariableDeclarationInitializedToRequire(node.Parent) {
+ if moduleSymbol := ch.ResolveExternalModuleName(moduleSpecifier); moduleSymbol != nil {
+ result.Add(core.FirstResult(getModuleIDAndFileNameOfModuleSymbol(moduleSymbol)), existingImport{node: node.Parent, moduleSpecifier: moduleSpecifier.Text(), index: i})
+ }
+ } else if node.Kind == ast.KindImportDeclaration || node.Kind == ast.KindImportEqualsDeclaration || node.Kind == ast.KindJSDocImportTag {
+ if moduleSymbol := ch.GetSymbolAtLocation(moduleSpecifier); moduleSymbol != nil {
+ result.Add(core.FirstResult(getModuleIDAndFileNameOfModuleSymbol(moduleSymbol)), existingImport{node: node, moduleSpecifier: moduleSpecifier.Text(), index: i})
+ }
+ }
+ }
+ v.existingImports = result
+ return result
+}
+
+func (v *View) shouldUseRequire() bool {
+ if v.shouldUseRequireForFixes != nil {
+ return *v.shouldUseRequireForFixes
+ }
+ shouldUseRequire := v.computeShouldUseRequire()
+ v.shouldUseRequireForFixes = &shouldUseRequire
+ return shouldUseRequire
+}
+
+func (v *View) computeShouldUseRequire() bool {
+ // 1. TypeScript files don't use require variable declarations
+ if !tspath.HasJSFileExtension(v.importingFile.FileName()) {
+ return false
+ }
+
+ // 2. If the current source file is unambiguously CJS or ESM, go with that
+ switch {
+ case v.importingFile.CommonJSModuleIndicator != nil && v.importingFile.ExternalModuleIndicator == nil:
+ return true
+ case v.importingFile.ExternalModuleIndicator != nil && v.importingFile.CommonJSModuleIndicator == nil:
+ return false
+ }
+
+ // 3. If there's a tsconfig/jsconfig, use its module setting
+ if v.program.Options().ConfigFilePath != "" {
+ return v.program.Options().GetEmitModuleKind() < core.ModuleKindES2015
+ }
+
+ // 4. In --module nodenext, assume we're not emitting JS -> JS, so use
+ // whatever syntax Node expects based on the detected module kind
+ // TODO: consider removing `impliedNodeFormatForEmit`
+ switch v.program.GetImpliedNodeFormatForEmit(v.importingFile) {
+ case core.ModuleKindCommonJS:
+ return true
+ case core.ModuleKindESNext:
+ return false
+ }
+
+ // 5. Match the first other JS file in the program that's unambiguously CJS or ESM
+ for _, otherFile := range v.program.GetSourceFiles() {
+ switch {
+ case otherFile == v.importingFile, !ast.IsSourceFileJS(otherFile), v.program.IsSourceFileFromExternalLibrary(otherFile):
+ continue
+ case otherFile.CommonJSModuleIndicator != nil && otherFile.ExternalModuleIndicator == nil:
+ return true
+ case otherFile.ExternalModuleIndicator != nil && otherFile.CommonJSModuleIndicator == nil:
+ return false
+ }
+ }
+
+ // 6. Literally nothing to go on
+ return true
+}
+
+func needsTypeOnly(addAsTypeOnly lsproto.AddAsTypeOnly) bool {
+ return addAsTypeOnly == lsproto.AddAsTypeOnlyRequired
+}
+
+func shouldUseTypeOnly(addAsTypeOnly lsproto.AddAsTypeOnly, preferences *lsutil.UserPreferences) bool {
+ return needsTypeOnly(addAsTypeOnly) || addAsTypeOnly != lsproto.AddAsTypeOnlyNotAllowed && preferences.PreferTypeOnlyAutoImports.IsTrue()
+}
+
+// CompareFixesForSorting returns negative if `a` is better than `b`.
+// Sorting with this comparator will place the best fix first.
+// After rank sorting, fixes will be sorted by arbitrary but stable criteria
+// to ensure a deterministic order.
+func (v *View) CompareFixesForSorting(a, b *Fix) int {
+ if res := v.CompareFixesForRanking(a, b); res != 0 {
+ return res
+ }
+ return v.compareModuleSpecifiersForSorting(a, b)
+}
+
+// CompareFixesForRanking returns negative if `a` is better than `b`.
+// Sorting with this comparator will place the best fix first.
+// Fixes of equal desirability will be considered equal.
+func (v *View) CompareFixesForRanking(a, b *Fix) int {
+ if res := compareFixKinds(a.Kind, b.Kind); res != 0 {
+ return res
+ }
+ return v.compareModuleSpecifiersForRanking(a, b)
+}
+
+func compareFixKinds(a, b lsproto.AutoImportFixKind) int {
+ return int(a) - int(b)
+}
+
+func (v *View) compareModuleSpecifiersForRanking(a, b *Fix) int {
+ if comparison := compareModuleSpecifierRelativity(a, b, v.preferences); comparison != 0 {
+ return comparison
+ }
+ if a.ModuleSpecifierKind == modulespecifiers.ResultKindAmbient && b.ModuleSpecifierKind == modulespecifiers.ResultKindAmbient {
+ if comparison := v.compareNodeCoreModuleSpecifiers(a.ModuleSpecifier, b.ModuleSpecifier, v.importingFile, v.program); comparison != 0 {
+ return comparison
+ }
+ }
+ if a.ModuleSpecifierKind == modulespecifiers.ResultKindRelative && b.ModuleSpecifierKind == modulespecifiers.ResultKindRelative {
+ if comparison := core.CompareBooleans(
+ isFixPossiblyReExportingImportingFile(a, v.importingFile.FileName()),
+ isFixPossiblyReExportingImportingFile(b, v.importingFile.FileName()),
+ ); comparison != 0 {
+ return comparison
+ }
+ }
+ if comparison := tspath.CompareNumberOfDirectorySeparators(a.ModuleSpecifier, b.ModuleSpecifier); comparison != 0 {
+ return comparison
+ }
+ return 0
+}
+
+func (v *View) compareModuleSpecifiersForSorting(a, b *Fix) int {
+ if res := v.compareModuleSpecifiersForRanking(a, b); res != 0 {
+ return res
+ }
+ // Sort ./foo before ../foo for equal-length specifiers
+ if strings.HasPrefix(a.ModuleSpecifier, "./") && !strings.HasPrefix(b.ModuleSpecifier, "./") {
+ return -1
+ }
+ if strings.HasPrefix(b.ModuleSpecifier, "./") && !strings.HasPrefix(a.ModuleSpecifier, "./") {
+ return 1
+ }
+ if comparison := strings.Compare(a.ModuleSpecifier, b.ModuleSpecifier); comparison != 0 {
+ return comparison
+ }
+ if comparison := cmp.Compare(a.ImportKind, b.ImportKind); comparison != 0 {
+ return comparison
+ }
+ // !!! further tie-breakers? In practice this is only called on fixes with the same name
+ return 0
+}
+
+func (v *View) compareNodeCoreModuleSpecifiers(a, b string, importingFile *ast.SourceFile, program *compiler.Program) int {
+ if strings.HasPrefix(a, "node:") && !strings.HasPrefix(b, "node:") {
+ if v.shouldUseUriStyleNodeCoreModules.IsTrue() {
+ return -1
+ } else if v.shouldUseUriStyleNodeCoreModules.IsFalse() {
+ return 1
+ }
+ return 0
+ }
+ if strings.HasPrefix(b, "node:") && !strings.HasPrefix(a, "node:") {
+ if v.shouldUseUriStyleNodeCoreModules.IsTrue() {
+ return 1
+ } else if v.shouldUseUriStyleNodeCoreModules.IsFalse() {
+ return -1
+ }
+ }
+ return 0
+}
+
+// This is a simple heuristic to try to avoid creating an import cycle with a barrel re-export.
+// E.g., do not `import { Foo } from ".."` when you could `import { Foo } from "../Foo"`.
+// This can produce false positives or negatives if re-exports cross into sibling directories
+// (e.g. `export * from "../whatever"`) or are not named "index". Technically this should do
+// a tspath.Path comparison, but it's not worth it to run a heuristic in such a hot path.
+func isFixPossiblyReExportingImportingFile(fix *Fix, importingFileName string) bool {
+ if fix.IsReExport && isIndexFileName(fix.ModuleFileName) {
+ reExportDir := tspath.GetDirectoryPath(fix.ModuleFileName)
+ return strings.HasPrefix(importingFileName, reExportDir)
+ }
+ return false
+}
+
+func isIndexFileName(fileName string) bool {
+ lastSlash := strings.LastIndexByte(fileName, '/')
+ if lastSlash < 0 || len(fileName) <= lastSlash+1 {
+ return false
+ }
+ fileName = fileName[lastSlash+1:]
+ switch fileName {
+ case "index.js", "index.jsx", "index.d.ts", "index.ts", "index.tsx":
+ return true
+ }
+ return false
+}
+
+func promoteFromTypeOnly(
+ changes *change.Tracker,
+ aliasDeclaration *ast.Declaration,
+ compilerOptions *core.CompilerOptions,
+ sourceFile *ast.SourceFile,
+ preferences *lsutil.UserPreferences,
+) *ast.Declaration {
+ // 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(), compilerOptions, sourceFile, preferences, convertExistingToTypeOnly, aliasDeclaration)
+ return spec.Parent.Parent
+ }
+
+ case ast.KindImportClause:
+ promoteImportClause(changes, aliasDeclaration.AsImportClause(), compilerOptions, sourceFile, preferences, 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(), compilerOptions, sourceFile, preferences, 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,
+ compilerOptions *core.CompilerOptions,
+ sourceFile *ast.SourceFile,
+ preferences *lsutil.UserPreferences,
+ 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
+ if compilerOptions.AllowImportingTsExtensions.IsFalse() {
+ moduleSpecifier := checker.TryGetModuleSpecifierFromDeclaration(importClause.Parent)
+ if moduleSpecifier != nil {
+ // Note: We can't check ResolvedUsingTsExtension without program, so we'll skip this optimization
+ // The fix will still work, just might not change .ts to .js extensions in all cases
+ }
+ }
+
+ // 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,
+ preferences,
+ )
+
+ // 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))
+}
+
+func getModuleSpecifierText(promotedDeclaration *ast.Node) string {
+ if promotedDeclaration.Kind == ast.KindImportEqualsDeclaration {
+ importEqualsDeclaration := promotedDeclaration.AsImportEqualsDeclaration()
+ if ast.IsExternalModuleReference(importEqualsDeclaration.ModuleReference) {
+ expr := importEqualsDeclaration.ModuleReference.Expression()
+ if expr != nil && expr.Kind == ast.KindStringLiteral {
+ return expr.Text()
+ }
+
+ }
+ return importEqualsDeclaration.ModuleReference.Text()
+ }
+ return promotedDeclaration.Parent.ModuleSpecifier().Text()
+}
+
+// returns `-1` if `a` is better than `b`
+func compareModuleSpecifierRelativity(a *Fix, b *Fix, preferences modulespecifiers.UserPreferences) int {
+ switch preferences.ImportModuleSpecifierPreference {
+ case modulespecifiers.ImportModuleSpecifierPreferenceNonRelative, modulespecifiers.ImportModuleSpecifierPreferenceProjectRelative:
+ return core.CompareBooleans(a.ModuleSpecifierKind == modulespecifiers.ResultKindRelative, b.ModuleSpecifierKind == modulespecifiers.ResultKindRelative)
+ }
+ return 0
+}
diff --git a/internal/ls/autoimport/index.go b/internal/ls/autoimport/index.go
new file mode 100644
index 0000000000..081d1b7abe
--- /dev/null
+++ b/internal/ls/autoimport/index.go
@@ -0,0 +1,149 @@
+package autoimport
+
+import (
+ "strings"
+ "unicode"
+ "unicode/utf8"
+
+ "github.com/microsoft/typescript-go/internal/core"
+)
+
+// Named is a constraint for types that can provide their name.
+type Named interface {
+ Name() string
+}
+
+// Index stores entries with an index mapping uppercase letters to entries whose name
+// starts with that letter, and lowercase letters to entries whose name contains a
+// word starting with that letter.
+type Index[T Named] struct {
+ entries []T
+ index map[rune][]int
+}
+
+func (idx *Index[T]) Find(name string, caseSensitive bool) []T {
+ if len(idx.entries) == 0 || len(name) == 0 {
+ return nil
+ }
+ firstRune := core.FirstResult(utf8.DecodeRuneInString(name))
+ if firstRune == utf8.RuneError {
+ return nil
+ }
+ firstRuneUpper := unicode.ToUpper(firstRune)
+ candidates, ok := idx.index[firstRuneUpper]
+ if !ok {
+ return nil
+ }
+
+ var results []T
+ for _, entryIndex := range candidates {
+ entry := idx.entries[entryIndex]
+ entryName := entry.Name()
+ if (caseSensitive && entryName == name) || (!caseSensitive && strings.EqualFold(entryName, name)) {
+ results = append(results, entry)
+ }
+ }
+
+ return results
+}
+
+// SearchWordPrefix returns each entry whose name contains a word beginning with
+// the first character of 'prefix', and whose name contains all characters
+// of 'prefix' in order (case-insensitive). If 'filter' is provided, only entries
+// for which filter(entry) returns true are included.
+func (idx *Index[T]) SearchWordPrefix(prefix string) []T {
+ if len(idx.entries) == 0 {
+ return nil
+ }
+
+ if len(prefix) == 0 {
+ return idx.entries
+ }
+
+ prefix = strings.ToLower(prefix)
+ firstRune, _ := utf8.DecodeRuneInString(prefix)
+ if firstRune == utf8.RuneError {
+ return nil
+ }
+
+ firstRuneUpper := unicode.ToUpper(firstRune)
+ firstRuneLower := unicode.ToLower(firstRune)
+
+ // Look up entries that have words starting with this letter
+ var wordStarts []int
+ nameStarts, _ := idx.index[firstRuneUpper]
+ if firstRuneUpper != firstRuneLower {
+ wordStarts, _ = idx.index[firstRuneLower]
+ }
+ count := len(nameStarts) + len(wordStarts)
+ if count == 0 {
+ return nil
+ }
+
+ // Filter entries by checking if they contain all characters in order
+ results := make([]T, 0, count)
+ for _, starts := range [][]int{nameStarts, wordStarts} {
+ for _, i := range starts {
+ entry := idx.entries[i]
+ if containsCharsInOrder(entry.Name(), prefix) {
+ results = append(results, entry)
+ }
+ }
+ }
+ return results
+}
+
+// containsCharsInOrder checks if str contains all characters from pattern in order (case-insensitive).
+func containsCharsInOrder(str, pattern string) bool {
+ str = strings.ToLower(str)
+ pattern = strings.ToLower(pattern)
+
+ patternIdx := 0
+ for _, ch := range str {
+ if patternIdx < len(pattern) {
+ patternRune, size := utf8.DecodeRuneInString(pattern[patternIdx:])
+ if ch == patternRune {
+ patternIdx += size
+ }
+ }
+ }
+ return patternIdx == len(pattern)
+}
+
+// insertAsWords adds a value to the index keyed by the first letter of each word in its name.
+func (idx *Index[T]) insertAsWords(value T) {
+ if idx.index == nil {
+ idx.index = make(map[rune][]int)
+ }
+
+ name := value.Name()
+ if len(name) == 0 {
+ panic("Cannot index entry with empty name")
+ }
+ entryIndex := len(idx.entries)
+ idx.entries = append(idx.entries, value)
+
+ indices := wordIndices(name)
+ seenRunes := make(map[rune]bool)
+
+ for i, start := range indices {
+ substr := name[start:]
+ firstRune, _ := utf8.DecodeRuneInString(substr)
+ if firstRune == utf8.RuneError {
+ continue
+ }
+ if i == 0 {
+ // Name start keyed by uppercase
+ firstRune = unicode.ToUpper(firstRune)
+ idx.index[firstRune] = append(idx.index[firstRune], entryIndex)
+ seenRunes[firstRune] = true // (Still set seenRunes in case first character is non-alphabetic)
+ } else {
+ // Subsequent word starts keyed by lowercase
+ firstRune = unicode.ToLower(firstRune)
+ if !seenRunes[firstRune] {
+ idx.index[firstRune] = append(idx.index[firstRune], entryIndex)
+ seenRunes[firstRune] = true
+ }
+ }
+ }
+}
diff --git a/internal/ls/autoimport/registry.go b/internal/ls/autoimport/registry.go
new file mode 100644
index 0000000000..b22b6d68f6
--- /dev/null
+++ b/internal/ls/autoimport/registry.go
@@ -0,0 +1,1059 @@
+package autoimport
+
+import (
+ "cmp"
+ "context"
+ "maps"
+ "slices"
+ "strings"
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "github.com/microsoft/typescript-go/internal/ast"
+ "github.com/microsoft/typescript-go/internal/binder"
+ "github.com/microsoft/typescript-go/internal/checker"
+ "github.com/microsoft/typescript-go/internal/collections"
+ "github.com/microsoft/typescript-go/internal/compiler"
+ "github.com/microsoft/typescript-go/internal/core"
+ "github.com/microsoft/typescript-go/internal/ls/lsconv"
+ "github.com/microsoft/typescript-go/internal/ls/lsutil"
+ "github.com/microsoft/typescript-go/internal/lsp/lsproto"
+ "github.com/microsoft/typescript-go/internal/module"
+ "github.com/microsoft/typescript-go/internal/modulespecifiers"
+ "github.com/microsoft/typescript-go/internal/packagejson"
+ "github.com/microsoft/typescript-go/internal/project/dirty"
+ "github.com/microsoft/typescript-go/internal/project/logging"
+ "github.com/microsoft/typescript-go/internal/tspath"
+ "github.com/microsoft/typescript-go/internal/vfs"
+)
+
+type newProgramStructure int
+
+const (
+ newProgramStructureFalse newProgramStructure = iota
+ newProgramStructureSameFileNames
+ newProgramStructureDifferentFileNames
+)
+
+// BucketState represents the dirty state of a bucket.
+// In general, a bucket can be used for an auto-imports request if it is clean
+// or if the only edited file is the one that was requested for auto-imports.
+// Most edits within a file will not change the imports available to that file.
+// However, two exceptions cause the bucket to be rebuilt after a change to a
+// single file:
+//
+// 1. Local files are newly added to the project by a manual import
+// 2. A node_modules dependency normally filtered out by package.json dependencies
+// is added to the project by a manual import
+//
+// Both of these cases take a bit of work to determine, but can only happen after
+// a full (non-clone) program update. When this happens, the `newProgramStructure`
+// flag is set until the next time the bucket is rebuilt, when those conditions
+// will be checked.
+type BucketState struct {
+ // dirtyFile is the file that was edited last, if any. It does not necessarily
+ // indicate that no other files have been edited, so it should be ignored if
+ // `multipleFilesDirty` is set.
+ dirtyFile tspath.Path
+ multipleFilesDirty bool
+ newProgramStructure newProgramStructure
+ // fileExcludePatterns is the value of the corresponding user preference when
+ // the bucket was built. If changed, the bucket should be rebuilt.
+ fileExcludePatterns []string
+}
+
+func (b BucketState) Dirty() bool {
+ return b.multipleFilesDirty || b.dirtyFile != "" || b.newProgramStructure > 0
+}
+
+func (b BucketState) DirtyFile() tspath.Path {
+ if b.multipleFilesDirty {
+ return ""
+ }
+ return b.dirtyFile
+}
+
+func (b BucketState) possiblyNeedsRebuildForFile(file tspath.Path, preferences *lsutil.UserPreferences) bool {
+ return b.newProgramStructure > 0 || b.hasDirtyFileBesides(file) || !core.UnorderedEqual(b.fileExcludePatterns, preferences.AutoImportFileExcludePatterns)
+}
+
+func (b BucketState) hasDirtyFileBesides(file tspath.Path) bool {
+ return b.multipleFilesDirty || b.dirtyFile != "" && b.dirtyFile != file
+}
+
+type RegistryBucket struct {
+ state BucketState
+
+ Paths collections.Set[tspath.Path]
+ // IgnoredPackageNames is only defined for project buckets. It is the set of
+ // package names that were present in the project's program, and not included
+ // in a node_modules bucket, and ultimately not included in the project bucket
+ // because they were only imported transitively. If an updated program's
+ // ResolvedPackageNames contains one of these, the bucket should be rebuilt
+ // because that package will be included.
+ IgnoredPackageNames *collections.Set[string]
+ // PackageNames is only defined for node_modules buckets. It is the full set of
+ // package directory names in the node_modules directory (but not necessarily
+ // inclued in the bucket).
+ PackageNames *collections.Set[string]
+ // DependencyNames is only defined for node_modules buckets. It is the set of
+ // package names that will be included in the bucket if present in the directory,
+ // computed from package.json dependencies. If nil, all packages are included
+ // because at least one open file has access to this node_modules directory without
+ // being filtered by a package.json.
+ DependencyNames *collections.Set[string]
+ // AmbientModuleNames is only defined for node_modules buckets. It is the set of
+ // ambient module names found while extracting exports in the bucket.
+ AmbientModuleNames map[string][]string
+ // Entrypoints is only defined for node_modules buckets. Keys are package entrypoint
+ // file paths, and values describe the ways of importing the package that would resolve
+ // to that file.
+ Entrypoints map[tspath.Path][]*module.ResolvedEntrypoint
+ Index *Index[*Export]
+}
+
+func newRegistryBucket() *RegistryBucket {
+ return &RegistryBucket{
+ state: BucketState{
+ multipleFilesDirty: true,
+ newProgramStructure: newProgramStructureDifferentFileNames,
+ },
+ }
+}
+
+func (b *RegistryBucket) Clone() *RegistryBucket {
+ return &RegistryBucket{
+ state: b.state,
+ Paths: b.Paths,
+ IgnoredPackageNames: b.IgnoredPackageNames,
+ PackageNames: b.PackageNames,
+ DependencyNames: b.DependencyNames,
+ AmbientModuleNames: b.AmbientModuleNames,
+ Entrypoints: b.Entrypoints,
+ Index: b.Index,
+ }
+}
+
+// markFileDirty should only be called within a Change call on the dirty map.
+// Buckets are considered immutable once in a finalized registry.
+func (b *RegistryBucket) markFileDirty(file tspath.Path) {
+ if b.state.hasDirtyFileBesides(file) {
+ b.state.multipleFilesDirty = true
+ } else {
+ b.state.dirtyFile = file
+ }
+}
+
+type directory struct {
+ name string
+ packageJson *packagejson.InfoCacheEntry
+ hasNodeModules bool
+}
+
+func (d *directory) Clone() *directory {
+ return &directory{
+ name: d.name,
+ packageJson: d.packageJson,
+ hasNodeModules: d.hasNodeModules,
+ }
+}
+
+type Registry struct {
+ toPath func(fileName string) tspath.Path
+ userPreferences *lsutil.UserPreferences
+
+ // exports map[tspath.Path][]*RawExport
+ directories map[tspath.Path]*directory
+
+ nodeModules map[tspath.Path]*RegistryBucket
+ projects map[tspath.Path]*RegistryBucket
+
+ // specifierCache maps from importing file to target file to specifier.
+ specifierCache map[tspath.Path]*collections.SyncMap[tspath.Path, string]
+}
+
+func NewRegistry(toPath func(fileName string) tspath.Path) *Registry {
+ return &Registry{
+ toPath: toPath,
+ directories: make(map[tspath.Path]*directory),
+ }
+}
+
+func (r *Registry) IsPreparedForImportingFile(fileName string, projectPath tspath.Path, preferences *lsutil.UserPreferences) bool {
+ if r == nil {
+ return false
+ }
+ projectBucket, ok := r.projects[projectPath]
+ if !ok {
+ panic("project bucket missing")
+ }
+ path := r.toPath(fileName)
+ if projectBucket.state.possiblyNeedsRebuildForFile(path, preferences) {
+ return false
+ }
+
+ dirPath := path.GetDirectoryPath()
+ for {
+ if dirBucket, ok := r.nodeModules[dirPath]; ok {
+ if dirBucket.state.possiblyNeedsRebuildForFile(path, preferences) {
+ return false
+ }
+ }
+ parent := dirPath.GetDirectoryPath()
+ if parent == dirPath {
+ break
+ }
+ dirPath = parent
+ }
+ return true
+}
+
+func (r *Registry) NodeModulesDirectories() map[tspath.Path]string {
+ dirs := make(map[tspath.Path]string)
+ for dirPath, dir := range r.directories {
+ if dir.hasNodeModules {
+ dirs[tspath.Path(tspath.CombinePaths(string(dirPath), "node_modules"))] = tspath.CombinePaths(dir.name, "node_modules")
+ }
+ }
+ return dirs
+}
+
+func (r *Registry) Clone(ctx context.Context, change RegistryChange, host RegistryCloneHost, logger *logging.LogTree) (*Registry, error) {
+ start := time.Now()
+ if logger != nil {
+ logger = logger.Fork("Building autoimport registry")
+ }
+ builder := newRegistryBuilder(r, host)
+ if change.UserPreferences != nil {
+ builder.userPreferences = change.UserPreferences
+ if !core.UnorderedEqual(builder.userPreferences.AutoImportSpecifierExcludeRegexes, r.userPreferences.AutoImportSpecifierExcludeRegexes) {
+ builder.specifierCache.Clear()
+ }
+ }
+ builder.updateBucketAndDirectoryExistence(change, logger)
+ builder.markBucketsDirty(change, logger)
+ if change.RequestedFile != "" {
+ builder.updateIndexes(ctx, change, logger)
+ }
+ if logger != nil {
+ logger.Logf("Built autoimport registry in %v", time.Since(start))
+ }
+ registry := builder.Build()
+ builder.host.Dispose()
+ return registry, nil
+}
+
+type BucketStats struct {
+ Path tspath.Path
+ ExportCount int
+ FileCount int
+ State BucketState
+ DependencyNames *collections.Set[string]
+ PackageNames *collections.Set[string]
+}
+
+type CacheStats struct {
+ ProjectBuckets []BucketStats
+ NodeModulesBuckets []BucketStats
+}
+
+func (r *Registry) GetCacheStats() *CacheStats {
+ stats := &CacheStats{}
+
+ for path, bucket := range r.projects {
+ exportCount := 0
+ if bucket.Index != nil {
+ exportCount = len(bucket.Index.entries)
+ }
+ stats.ProjectBuckets = append(stats.ProjectBuckets, BucketStats{
+ Path: path,
+ ExportCount: exportCount,
+ FileCount: bucket.Paths.Len(),
+ State: bucket.state,
+ DependencyNames: bucket.DependencyNames,
+ PackageNames: bucket.PackageNames,
+ })
+ }
+
+ for path, bucket := range r.nodeModules {
+ exportCount := 0
+ if bucket.Index != nil {
+ exportCount = len(bucket.Index.entries)
+ }
+ stats.NodeModulesBuckets = append(stats.NodeModulesBuckets, BucketStats{
+ Path: path,
+ ExportCount: exportCount,
+ FileCount: bucket.Paths.Len(),
+ State: bucket.state,
+ DependencyNames: bucket.DependencyNames,
+ PackageNames: bucket.PackageNames,
+ })
+ }
+
+ slices.SortFunc(stats.ProjectBuckets, func(a, b BucketStats) int {
+ return cmp.Compare(a.Path, b.Path)
+ })
+ slices.SortFunc(stats.NodeModulesBuckets, func(a, b BucketStats) int {
+ return cmp.Compare(a.Path, b.Path)
+ })
+
+ return stats
+}
+
+type RegistryChange struct {
+ RequestedFile tspath.Path
+ OpenFiles map[tspath.Path]string
+ Changed collections.Set[lsproto.DocumentUri]
+ Created collections.Set[lsproto.DocumentUri]
+ Deleted collections.Set[lsproto.DocumentUri]
+ // RebuiltPrograms maps from project path to:
+ // - true: the program was rebuilt with a different set of file names
+ // - false: the program was rebuilt but the set of file names is unchanged
+ RebuiltPrograms map[tspath.Path]bool
+ UserPreferences *lsutil.UserPreferences
+}
+
+type RegistryCloneHost interface {
+ module.ResolutionHost
+ FS() vfs.FS
+ GetDefaultProject(path tspath.Path) (tspath.Path, *compiler.Program)
+ GetProgramForProject(projectPath tspath.Path) *compiler.Program
+ GetPackageJson(fileName string) *packagejson.InfoCacheEntry
+ GetSourceFile(fileName string, path tspath.Path) *ast.SourceFile
+ Dispose()
+}
+
+type registryBuilder struct {
+ host RegistryCloneHost
+ resolver *module.Resolver
+ base *Registry
+
+ userPreferences *lsutil.UserPreferences
+ directories *dirty.Map[tspath.Path, *directory]
+ nodeModules *dirty.Map[tspath.Path, *RegistryBucket]
+ projects *dirty.Map[tspath.Path, *RegistryBucket]
+ specifierCache *dirty.MapBuilder[tspath.Path, *collections.SyncMap[tspath.Path, string], *collections.SyncMap[tspath.Path, string]]
+}
+
+func newRegistryBuilder(registry *Registry, host RegistryCloneHost) *registryBuilder {
+ return ®istryBuilder{
+ host: host,
+ resolver: module.NewResolver(host, core.EmptyCompilerOptions, "", ""),
+ base: registry,
+
+ userPreferences: registry.userPreferences.OrDefault(),
+ directories: dirty.NewMap(registry.directories),
+ nodeModules: dirty.NewMap(registry.nodeModules),
+ projects: dirty.NewMap(registry.projects),
+ specifierCache: dirty.NewMapBuilder(registry.specifierCache, core.Identity, core.Identity),
+ }
+}
+
+func (b *registryBuilder) Build() *Registry {
+ return &Registry{
+ toPath: b.base.toPath,
+ userPreferences: b.userPreferences,
+ directories: core.FirstResult(b.directories.Finalize()),
+ nodeModules: core.FirstResult(b.nodeModules.Finalize()),
+ projects: core.FirstResult(b.projects.Finalize()),
+ specifierCache: core.FirstResult(b.specifierCache.Build()),
+ }
+}
+
+func (b *registryBuilder) updateBucketAndDirectoryExistence(change RegistryChange, logger *logging.LogTree) {
+ start := time.Now()
+ neededProjects := make(map[tspath.Path]struct{})
+ neededDirectories := make(map[tspath.Path]string)
+ for path, fileName := range change.OpenFiles {
+ neededProjects[core.FirstResult(b.host.GetDefaultProject(path))] = struct{}{}
+ if strings.HasPrefix(fileName, "^/") {
+ continue
+ }
+ dir := fileName
+ dirPath := path
+ for {
+ dir = tspath.GetDirectoryPath(dir)
+ lastDirPath := dirPath
+ dirPath = dirPath.GetDirectoryPath()
+ if dirPath == lastDirPath {
+ break
+ }
+ if _, ok := neededDirectories[dirPath]; ok {
+ break
+ }
+ neededDirectories[dirPath] = dir
+ }
+
+ if !b.specifierCache.Has(path) {
+ b.specifierCache.Set(path, &collections.SyncMap[tspath.Path, string]{})
+ }
+ }
+
+ for path := range b.base.specifierCache {
+ if _, ok := change.OpenFiles[path]; !ok {
+ b.specifierCache.Delete(path)
+ }
+ }
+
+ var addedProjects, removedProjects []tspath.Path
+ core.DiffMapsFunc(
+ b.base.projects,
+ neededProjects,
+ func(_ *RegistryBucket, _ struct{}) bool {
+ panic("never called because onChanged is nil")
+ },
+ func(projectPath tspath.Path, _ struct{}) {
+ // Need and don't have
+ b.projects.Add(projectPath, newRegistryBucket())
+ addedProjects = append(addedProjects, projectPath)
+ },
+ func(projectPath tspath.Path, _ *RegistryBucket) {
+ // Have and don't need
+ b.projects.Delete(projectPath)
+ removedProjects = append(removedProjects, projectPath)
+ },
+ nil,
+ )
+ if logger != nil {
+ for _, projectPath := range addedProjects {
+ logger.Logf("Added project: %s", projectPath)
+ }
+ for _, projectPath := range removedProjects {
+ logger.Logf("Removed project: %s", projectPath)
+ }
+ }
+
+ updateDirectory := func(dirPath tspath.Path, dirName string, packageJsonChanged bool) {
+ packageJsonFileName := tspath.CombinePaths(dirName, "package.json")
+ hasNodeModules := b.host.FS().DirectoryExists(tspath.CombinePaths(dirName, "node_modules"))
+ if entry, ok := b.directories.Get(dirPath); ok {
+ entry.ChangeIf(func(dir *directory) bool {
+ return packageJsonChanged || dir.hasNodeModules != hasNodeModules
+ }, func(dir *directory) {
+ dir.packageJson = b.host.GetPackageJson(packageJsonFileName)
+ dir.hasNodeModules = hasNodeModules
+ })
+ } else {
+ b.directories.Add(dirPath, &directory{
+ name: dirName,
+ packageJson: b.host.GetPackageJson(packageJsonFileName),
+ hasNodeModules: hasNodeModules,
+ })
+ }
+
+ if packageJsonChanged {
+ // package.json changes affecting node_modules are handled by comparing dependencies in updateIndexes
+ return
+ }
+
+ if hasNodeModules {
+ if _, ok := b.nodeModules.Get(dirPath); !ok {
+ b.nodeModules.Add(dirPath, newRegistryBucket())
+ }
+ } else {
+ b.nodeModules.TryDelete(dirPath)
+ }
+ }
+
+ var addedNodeModulesDirs, removedNodeModulesDirs []tspath.Path
+ core.DiffMapsFunc(
+ b.base.directories,
+ neededDirectories,
+ func(dir *directory, dirName string) bool {
+ packageJsonUri := lsconv.FileNameToDocumentURI(tspath.CombinePaths(dirName, "package.json"))
+ return !change.Changed.Has(packageJsonUri) && !change.Deleted.Has(packageJsonUri) && !change.Created.Has(packageJsonUri)
+ },
+ func(dirPath tspath.Path, dirName string) {
+ // Need and don't have
+ hadNodeModules := b.base.nodeModules[dirPath] != nil
+ updateDirectory(dirPath, dirName, false)
+ if logger != nil {
+ logger.Logf("Added directory: %s", dirPath)
+ }
+ if _, hasNow := b.nodeModules.Get(dirPath); hasNow && !hadNodeModules {
+ addedNodeModulesDirs = append(addedNodeModulesDirs, dirPath)
+ }
+ },
+ func(dirPath tspath.Path, dir *directory) {
+ // Have and don't need
+ hadNodeModules := b.base.nodeModules[dirPath] != nil
+ b.directories.Delete(dirPath)
+ b.nodeModules.TryDelete(dirPath)
+ if logger != nil {
+ logger.Logf("Removed directory: %s", dirPath)
+ }
+ if hadNodeModules {
+ removedNodeModulesDirs = append(removedNodeModulesDirs, dirPath)
+ }
+ },
+ func(dirPath tspath.Path, dir *directory, dirName string) {
+ // package.json may have changed
+ updateDirectory(dirPath, dirName, true)
+ if logger != nil {
+ logger.Logf("Changed directory: %s", dirPath)
+ }
+ },
+ )
+ if logger != nil {
+ for _, dirPath := range addedNodeModulesDirs {
+ logger.Logf("Added node_modules bucket: %s", dirPath)
+ }
+ for _, dirPath := range removedNodeModulesDirs {
+ logger.Logf("Removed node_modules bucket: %s", dirPath)
+ }
+ logger.Logf("Updated buckets and directories in %v", time.Since(start))
+ }
+}
+
+func (b *registryBuilder) markBucketsDirty(change RegistryChange, logger *logging.LogTree) {
+ // Mark new program structures
+ for projectPath, newFileNames := range change.RebuiltPrograms {
+ if bucket, ok := b.projects.Get(projectPath); ok {
+ bucket.Change(func(bucket *RegistryBucket) {
+ bucket.state.newProgramStructure = core.IfElse(newFileNames, newProgramStructureDifferentFileNames, newProgramStructureSameFileNames)
+ })
+ }
+ }
+
+ // Mark files dirty, bailing out if all buckets already have multiple files dirty
+ cleanNodeModulesBuckets := make(map[tspath.Path]struct{})
+ cleanProjectBuckets := make(map[tspath.Path]struct{})
+ b.nodeModules.Range(func(entry *dirty.MapEntry[tspath.Path, *RegistryBucket]) bool {
+ if !entry.Value().state.multipleFilesDirty {
+ cleanNodeModulesBuckets[entry.Key()] = struct{}{}
+ }
+ return true
+ })
+ b.projects.Range(func(entry *dirty.MapEntry[tspath.Path, *RegistryBucket]) bool {
+ if !entry.Value().state.multipleFilesDirty {
+ cleanProjectBuckets[entry.Key()] = struct{}{}
+ }
+ return true
+ })
+
+ markFilesDirty := func(uris map[lsproto.DocumentUri]struct{}) {
+ if len(cleanNodeModulesBuckets) == 0 && len(cleanProjectBuckets) == 0 {
+ return
+ }
+ for uri := range uris {
+ path := b.base.toPath(uri.FileName())
+ if len(cleanNodeModulesBuckets) > 0 {
+ // For node_modules, mark the bucket dirty if anything changes in the directory
+ if nodeModulesIndex := strings.Index(string(path), "/node_modules/"); nodeModulesIndex != -1 {
+ dirPath := path[:nodeModulesIndex]
+ if _, ok := cleanNodeModulesBuckets[dirPath]; ok {
+ entry := core.FirstResult(b.nodeModules.Get(dirPath))
+ entry.Change(func(bucket *RegistryBucket) { bucket.markFileDirty(path) })
+ if !entry.Value().state.multipleFilesDirty {
+ delete(cleanNodeModulesBuckets, dirPath)
+ }
+ }
+ }
+ }
+ // For projects, mark the bucket dirty if the bucket contains the file directly.
+ // Any other significant change, like a created failed lookup location, is
+ // handled by newProgramStructure.
+ for projectDirPath := range cleanProjectBuckets {
+ entry, _ := b.projects.Get(projectDirPath)
+ if entry.Value().Paths.Has(path) {
+ entry.Change(func(bucket *RegistryBucket) { bucket.markFileDirty(path) })
+ if !entry.Value().state.multipleFilesDirty {
+ delete(cleanProjectBuckets, projectDirPath)
+ }
+ }
+ }
+ }
+ }
+
+ markFilesDirty(change.Created.Keys())
+ markFilesDirty(change.Deleted.Keys())
+ markFilesDirty(change.Changed.Keys())
+}
+
+func (b *registryBuilder) updateIndexes(ctx context.Context, change RegistryChange, logger *logging.LogTree) {
+ type task struct {
+ entry *dirty.MapEntry[tspath.Path, *RegistryBucket]
+ dependencyNames *collections.Set[string]
+ result *bucketBuildResult
+ err error
+ }
+
+ projectPath, _ := b.host.GetDefaultProject(change.RequestedFile)
+ if projectPath == "" {
+ return
+ }
+
+ var tasks []*task
+ var wg sync.WaitGroup
+
+ tspath.ForEachAncestorDirectoryPath(change.RequestedFile, func(dirPath tspath.Path) (any, bool) {
+ if nodeModulesBucket, ok := b.nodeModules.Get(dirPath); ok {
+ dirName := core.FirstResult(b.directories.Get(dirPath)).Value().name
+ dependencies := b.computeDependenciesForNodeModulesDirectory(change, dirName, dirPath)
+ if nodeModulesBucket.Value().state.hasDirtyFileBesides(change.RequestedFile) || !nodeModulesBucket.Value().DependencyNames.Equals(dependencies) {
+ task := &task{entry: nodeModulesBucket, dependencyNames: dependencies}
+ tasks = append(tasks, task)
+ wg.Go(func() {
+ result, err := b.buildNodeModulesBucket(ctx, dependencies, dirName, dirPath, logger.Fork("Building node_modules bucket "+dirName))
+ task.result = result
+ task.err = err
+ })
+ }
+ }
+ return nil, false
+ })
+
+ nodeModulesContainsDependency := func(nodeModulesDir tspath.Path, packageName string) bool {
+ for _, task := range tasks {
+ if task.entry.Key() == nodeModulesDir {
+ return task.dependencyNames == nil || task.dependencyNames.Has(packageName)
+ }
+ }
+ if bucket, ok := b.base.nodeModules[nodeModulesDir]; ok {
+ return bucket.DependencyNames == nil || bucket.DependencyNames.Has(packageName)
+ }
+ return false
+ }
+
+ if project, ok := b.projects.Get(projectPath); ok {
+ program := b.host.GetProgramForProject(projectPath)
+ resolvedPackageNames := core.Memoize(func() *collections.Set[string] {
+ return getResolvedPackageNames(ctx, program)
+ })
+ shouldRebuild := project.Value().state.hasDirtyFileBesides(change.RequestedFile)
+ if !shouldRebuild && project.Value().state.newProgramStructure > 0 {
+ // Exceptions from BucketState comment - check if new program's resolved package names include any
+ // previously ignored, or if there are new non-node_modules files.
+ // If not, we can skip rebuilding the project bucket.
+ if project.Value().IgnoredPackageNames.Intersects(resolvedPackageNames()) || hasNewNonNodeModulesFiles(program, project.Value()) {
+ shouldRebuild = true
+ } else {
+ project.Change(func(b *RegistryBucket) { b.state.newProgramStructure = newProgramStructureFalse })
+ }
+ }
+ if shouldRebuild {
+ task := &task{entry: project}
+ tasks = append(tasks, task)
+ wg.Go(func() {
+ index, err := b.buildProjectBucket(
+ ctx,
+ projectPath,
+ resolvedPackageNames(),
+ nodeModulesContainsDependency,
+ logger.Fork("Building project bucket "+string(projectPath)),
+ )
+ task.result = index
+ task.err = err
+ })
+ }
+ }
+
+ start := time.Now()
+ wg.Wait()
+
+ for _, t := range tasks {
+ if t.err != nil {
+ continue
+ }
+ t.entry.Replace(t.result.bucket)
+ }
+
+ // If we failed to resolve any alias exports by ending up at a non-relative module specifier
+ // that didn't resolve to another package, it's probably an ambient module declared in another package.
+ // We recorded these failures, along with the name of every ambient module declared elsewhere, so we
+ // can do a second pass on the failed files, this time including the ambient modules declarations that
+ // were missing the first time. Example: node_modules/fs-extra/index.d.ts is simply `export * from "fs"`,
+ // but when trying to resolve the `export *`, we don't know where "fs" is declared. The aliasResolver
+ // tries to find packages named "fs" on the file system, but after failing, records "fs" as a failure
+ // for fs-extra/index.d.ts. Meanwhile, if we also processed node_modules/@types/node/fs.d.ts, we
+ // recorded that file as declaring the ambient module "fs". In the second pass, we combine those two
+ // files and reprocess fs-extra/index.d.ts, this time finding "fs" declared in @types/node.
+ secondPassStart := time.Now()
+ var secondPassFileCount int
+ for _, t := range tasks {
+ if t.err != nil {
+ continue
+ }
+ if t.result.possibleFailedAmbientModuleLookupTargets == nil {
+ continue
+ }
+ rootFiles := make(map[string]*ast.SourceFile)
+ for target := range t.result.possibleFailedAmbientModuleLookupTargets.Keys() {
+ for _, fileName := range b.resolveAmbientModuleName(target, t.entry.Key()) {
+ if _, exists := rootFiles[fileName]; exists {
+ continue
+ }
+ rootFiles[fileName] = b.host.GetSourceFile(fileName, b.base.toPath(fileName))
+ secondPassFileCount++
+ }
+ }
+ if len(rootFiles) > 0 {
+ aliasResolver := newAliasResolver(slices.Collect(maps.Values(rootFiles)), b.host, b.resolver, b.base.toPath, func(source ast.HasFileName, moduleName string) {
+ // no-op
+ })
+ ch, _ := checker.NewChecker(aliasResolver)
+ t.result.possibleFailedAmbientModuleLookupSources.Range(func(path tspath.Path, source *failedAmbientModuleLookupSource) bool {
+ sourceFile := aliasResolver.GetSourceFile(source.fileName)
+ extractor := b.newExportExtractor(t.entry.Key(), source.packageName, ch)
+ fileExports := extractor.extractFromFile(sourceFile)
+ t.result.bucket.Paths.Add(path)
+ for _, exp := range fileExports {
+ t.result.bucket.Index.insertAsWords(exp)
+ }
+ return true
+ })
+ }
+ }
+
+ if logger != nil && len(tasks) > 0 {
+ if secondPassFileCount > 0 {
+ logger.Logf("%d files required second pass, took %v", secondPassFileCount, time.Since(secondPassStart))
+ }
+ logger.Logf("Built %d indexes in %v", len(tasks), time.Since(start))
+ }
+}
+
+func hasNewNonNodeModulesFiles(program *compiler.Program, bucket *RegistryBucket) bool {
+ if bucket.state.newProgramStructure != newProgramStructureDifferentFileNames {
+ return false
+ }
+ for _, file := range program.GetSourceFiles() {
+ if strings.Contains(file.FileName(), "/node_modules/") || isIgnoredFile(program, file) {
+ continue
+ }
+ if !bucket.Paths.Has(file.Path()) {
+ return true
+ }
+ }
+ return false
+}
+
+func isIgnoredFile(program *compiler.Program, file *ast.SourceFile) bool {
+ return program.IsSourceFileDefaultLibrary(file.Path()) || program.IsGlobalTypingsFile(file.FileName())
+}
+
+type failedAmbientModuleLookupSource struct {
+ mu sync.Mutex
+ fileName string
+ packageName string
+}
+
+type bucketBuildResult struct {
+ bucket *RegistryBucket
+ // File path to filename and package name
+ possibleFailedAmbientModuleLookupSources *collections.SyncMap[tspath.Path, *failedAmbientModuleLookupSource]
+ // Likely ambient module name
+ possibleFailedAmbientModuleLookupTargets *collections.SyncSet[string]
+}
+
+func (b *registryBuilder) buildProjectBucket(
+ ctx context.Context,
+ projectPath tspath.Path,
+ resolvedPackageNames *collections.Set[string],
+ nodeModulesContainsDependency func(nodeModulesDir tspath.Path, packageName string) bool,
+ logger *logging.LogTree,
+) (*bucketBuildResult, error) {
+ if ctx.Err() != nil {
+ return nil, ctx.Err()
+ }
+
+ start := time.Now()
+ var mu sync.Mutex
+ fileExcludePatterns := b.userPreferences.ParsedAutoImportFileExcludePatterns(b.host.FS().UseCaseSensitiveFileNames())
+ result := &bucketBuildResult{bucket: &RegistryBucket{}}
+ program := b.host.GetProgramForProject(projectPath)
+ getChecker, closePool, checkerCount := createCheckerPool(program)
+ defer closePool()
+ exports := make(map[tspath.Path][]*Export)
+ var wg sync.WaitGroup
+ var ignoredPackageNames collections.Set[string]
+ var skippedFileCount int
+ var combinedStats extractorStats
+
+outer:
+ for _, file := range program.GetSourceFiles() {
+ if isIgnoredFile(program, file) {
+ continue
+ }
+ for _, excludePattern := range fileExcludePatterns {
+ if matched, _ := excludePattern.MatchString(file.FileName()); matched {
+ skippedFileCount++
+ continue outer
+ }
+ }
+ if packageName := modulespecifiers.GetPackageNameFromDirectory(file.FileName()); packageName != "" {
+ // Only process this file if it is not going to be processed as part of a node_modules bucket
+ // *and* if it was imported directly (not transitively) by a project file (i.e., this is part
+ // of a package not listed in package.json, but imported anyway).
+ pathComponents := tspath.GetPathComponents(string(file.Path()), "")
+ nodeModulesDir := tspath.GetPathFromPathComponents(pathComponents[:slices.Index(pathComponents, "node_modules")])
+ if nodeModulesContainsDependency(tspath.Path(nodeModulesDir), packageName) {
+ continue
+ }
+ if !resolvedPackageNames.Has(packageName) {
+ ignoredPackageNames.Add(packageName)
+ continue
+ }
+ }
+ wg.Go(func() {
+ if ctx.Err() == nil {
+ checker, done := getChecker()
+ defer done()
+ extractor := b.newExportExtractor("", "", checker)
+ fileExports := extractor.extractFromFile(file)
+ mu.Lock()
+ exports[file.Path()] = fileExports
+ mu.Unlock()
+ stats := extractor.Stats()
+ combinedStats.exports.Add(stats.exports.Load())
+ combinedStats.usedChecker.Add(stats.usedChecker.Load())
+ }
+ })
+ }
+
+ wg.Wait()
+
+ indexStart := time.Now()
+ idx := &Index[*Export]{}
+ for path, fileExports := range exports {
+ result.bucket.Paths.Add(path)
+ for _, exp := range fileExports {
+ idx.insertAsWords(exp)
+ }
+ }
+
+ result.bucket.Index = idx
+ result.bucket.IgnoredPackageNames = &ignoredPackageNames
+ result.bucket.state.fileExcludePatterns = b.userPreferences.AutoImportFileExcludePatterns
+
+ if logger != nil {
+ logger.Logf("Extracted exports: %v (%d exports, %d used checker, %d created checkers)", indexStart.Sub(start), combinedStats.exports.Load(), combinedStats.usedChecker.Load(), checkerCount())
+ if skippedFileCount > 0 {
+ logger.Logf("Skipped %d files due to exclude patterns", skippedFileCount)
+ }
+ logger.Logf("Built index: %v", time.Since(indexStart))
+ logger.Logf("Bucket total: %v", time.Since(start))
+ }
+ return result, nil
+}
+
+func (b *registryBuilder) computeDependenciesForNodeModulesDirectory(change RegistryChange, dirName string, dirPath tspath.Path) *collections.Set[string] {
+ // If any open files are in scope of this directory but not in scope of any package.json,
+ // we need to add all packages in this node_modules directory.
+ for path := range change.OpenFiles {
+ if dirPath.ContainsPath(path) && b.getNearestAncestorDirectoryWithValidPackageJson(path) == nil {
+ return nil
+ }
+ }
+
+ // Get all package.jsons that have this node_modules directory in their spine
+ dependencies := &collections.Set[string]{}
+ b.directories.Range(func(entry *dirty.MapEntry[tspath.Path, *directory]) bool {
+ if entry.Value().packageJson.Exists() && dirPath.ContainsPath(entry.Key()) {
+ entry.Value().packageJson.Contents.RangeDependencies(func(name, _, field string) bool {
+ if field == "dependencies" || field == "peerDendencies" {
+ dependencies.Add(module.GetPackageNameFromTypesPackageName(name))
+ }
+ return true
+ })
+ }
+ return true
+ })
+ return dependencies
+}
+
+func (b *registryBuilder) buildNodeModulesBucket(
+ ctx context.Context,
+ dependencies *collections.Set[string],
+ dirName string,
+ dirPath tspath.Path,
+ logger *logging.LogTree,
+) (*bucketBuildResult, error) {
+ if ctx.Err() != nil {
+ return nil, ctx.Err()
+ }
+
+ start := time.Now()
+ fileExcludePatterns := b.userPreferences.ParsedAutoImportFileExcludePatterns(b.host.FS().UseCaseSensitiveFileNames())
+ directoryPackageNames, err := getPackageNamesInNodeModules(tspath.CombinePaths(dirName, "node_modules"), b.host.FS())
+ if err != nil {
+ return nil, err
+ }
+
+ extractorStart := time.Now()
+ packageNames := core.Coalesce(dependencies, directoryPackageNames)
+
+ var exportsMu sync.Mutex
+ exports := make(map[tspath.Path][]*Export)
+ ambientModuleNames := make(map[string][]string)
+
+ var entrypointsMu sync.Mutex
+ var entrypoints []*module.ResolvedEntrypoints
+ var skippedEntrypointsCount int32
+ var combinedStats extractorStats
+ var possibleFailedAmbientModuleLookupTargets collections.SyncSet[string]
+ var possibleFailedAmbientModuleLookupSources collections.SyncMap[tspath.Path, *failedAmbientModuleLookupSource]
+
+ createAliasResolver := func(packageName string, entrypoints []*module.ResolvedEntrypoint) *aliasResolver {
+ rootFiles := make([]*ast.SourceFile, len(entrypoints))
+ var wg sync.WaitGroup
+ for i, entrypoint := range entrypoints {
+ wg.Go(func() {
+ file := b.host.GetSourceFile(entrypoint.ResolvedFileName, b.base.toPath(entrypoint.ResolvedFileName))
+ binder.BindSourceFile(file)
+ rootFiles[i] = file
+ })
+ }
+ wg.Wait()
+
+ rootFiles = slices.DeleteFunc(rootFiles, func(f *ast.SourceFile) bool {
+ return f == nil
+ })
+
+ return newAliasResolver(rootFiles, b.host, b.resolver, b.base.toPath, func(source ast.HasFileName, moduleName string) {
+ possibleFailedAmbientModuleLookupTargets.Add(moduleName)
+ possibleFailedAmbientModuleLookupSources.LoadOrStore(source.Path(), &failedAmbientModuleLookupSource{
+ fileName: source.FileName(),
+ })
+ })
+ }
+
+ indexStart := time.Now()
+ var wg sync.WaitGroup
+ for packageName := range packageNames.Keys() {
+ wg.Go(func() {
+ if ctx.Err() != nil {
+ return
+ }
+
+ typesPackageName := module.GetTypesPackageName(packageName)
+ var packageJson *packagejson.InfoCacheEntry
+ packageJson = b.host.GetPackageJson(tspath.CombinePaths(dirName, "node_modules", packageName, "package.json"))
+ if !packageJson.DirectoryExists {
+ packageJson = b.host.GetPackageJson(tspath.CombinePaths(dirName, "node_modules", typesPackageName, "package.json"))
+ }
+ packageEntrypoints := b.resolver.GetEntrypointsFromPackageJsonInfo(packageJson, packageName)
+ if packageEntrypoints == nil {
+ return
+ }
+ if len(fileExcludePatterns) > 0 {
+ count := int32(len(packageEntrypoints.Entrypoints))
+ packageEntrypoints.Entrypoints = slices.DeleteFunc(packageEntrypoints.Entrypoints, func(entrypoint *module.ResolvedEntrypoint) bool {
+ for _, excludePattern := range fileExcludePatterns {
+ if matched, _ := excludePattern.MatchString(entrypoint.ResolvedFileName); matched {
+ return true
+ }
+ }
+ return false
+ })
+ atomic.AddInt32(&skippedEntrypointsCount, count-int32(len(packageEntrypoints.Entrypoints)))
+ }
+ if len(packageEntrypoints.Entrypoints) == 0 {
+ return
+ }
+
+ entrypointsMu.Lock()
+ entrypoints = append(entrypoints, packageEntrypoints)
+ entrypointsMu.Unlock()
+
+ aliasResolver := createAliasResolver(packageName, packageEntrypoints.Entrypoints)
+ checker, _ := checker.NewChecker(aliasResolver)
+ extractor := b.newExportExtractor(dirPath, packageName, checker)
+ seenFiles := collections.NewSetWithSizeHint[tspath.Path](len(packageEntrypoints.Entrypoints))
+ for _, entrypoint := range aliasResolver.rootFiles {
+ if !seenFiles.AddIfAbsent(entrypoint.Path()) {
+ continue
+ }
+
+ if ctx.Err() != nil {
+ return
+ }
+
+ fileExports := extractor.extractFromFile(entrypoint)
+ exportsMu.Lock()
+ for _, name := range entrypoint.AmbientModuleNames {
+ ambientModuleNames[name] = append(ambientModuleNames[name], entrypoint.FileName())
+ }
+ if source, ok := possibleFailedAmbientModuleLookupSources.Load(entrypoint.Path()); !ok {
+ // If we failed to resolve any ambient modules from this file, we'll try the
+ // whole file again later, so don't add anything now.
+ exports[entrypoint.Path()] = fileExports
+ } else {
+ // Record the package name so we can use it later during the second pass
+ source.mu.Lock()
+ source.packageName = packageName
+ source.mu.Unlock()
+ }
+ exportsMu.Unlock()
+ }
+ if logger != nil {
+ stats := extractor.Stats()
+ combinedStats.exports.Add(stats.exports.Load())
+ combinedStats.usedChecker.Add(stats.usedChecker.Load())
+ }
+ })
+ }
+
+ wg.Wait()
+
+ result := &bucketBuildResult{
+ bucket: &RegistryBucket{
+ Index: &Index[*Export]{},
+ DependencyNames: dependencies,
+ PackageNames: directoryPackageNames,
+ AmbientModuleNames: ambientModuleNames,
+ Paths: *collections.NewSetWithSizeHint[tspath.Path](len(exports)),
+ Entrypoints: make(map[tspath.Path][]*module.ResolvedEntrypoint, len(exports)),
+ state: BucketState{
+ fileExcludePatterns: b.userPreferences.AutoImportFileExcludePatterns,
+ },
+ },
+ possibleFailedAmbientModuleLookupSources: &possibleFailedAmbientModuleLookupSources,
+ possibleFailedAmbientModuleLookupTargets: &possibleFailedAmbientModuleLookupTargets,
+ }
+ for path, fileExports := range exports {
+ result.bucket.Paths.Add(path)
+ for _, exp := range fileExports {
+ result.bucket.Index.insertAsWords(exp)
+ }
+ }
+ for _, entrypointSet := range entrypoints {
+ for _, entrypoint := range entrypointSet.Entrypoints {
+ path := b.base.toPath(entrypoint.ResolvedFileName)
+ result.bucket.Entrypoints[path] = append(result.bucket.Entrypoints[path], entrypoint)
+ }
+ }
+
+ if logger != nil {
+ logger.Logf("Determined dependencies and package names: %v", extractorStart.Sub(start))
+ logger.Logf("Extracted exports: %v (%d exports, %d used checker)", indexStart.Sub(extractorStart), combinedStats.exports.Load(), combinedStats.usedChecker.Load())
+ if skippedEntrypointsCount > 0 {
+ logger.Logf("Skipped %d entrypoints due to exclude patterns", skippedEntrypointsCount)
+ }
+ logger.Logf("Built index: %v", time.Since(indexStart))
+ logger.Logf("Bucket total: %v", time.Since(start))
+ }
+
+ return result, ctx.Err()
+}
+
+func (b *registryBuilder) getNearestAncestorDirectoryWithValidPackageJson(filePath tspath.Path) *directory {
+ return core.FirstResult(tspath.ForEachAncestorDirectoryPath(filePath.GetDirectoryPath(), func(dirPath tspath.Path) (result *directory, stop bool) {
+ if dirEntry, ok := b.directories.Get(dirPath); ok && dirEntry.Value().packageJson.Exists() && dirEntry.Value().packageJson.Contents.Parseable {
+ return dirEntry.Value(), true
+ }
+ return nil, false
+ }))
+}
+
+func (b *registryBuilder) resolveAmbientModuleName(moduleName string, fromPath tspath.Path) []string {
+ return core.FirstResult(tspath.ForEachAncestorDirectoryPath(fromPath, func(dirPath tspath.Path) (result []string, stop bool) {
+ if bucket, ok := b.nodeModules.Get(dirPath); ok {
+ if fileNames, ok := bucket.Value().AmbientModuleNames[moduleName]; ok {
+ return fileNames, true
+ }
+ }
+ return nil, false
+ }))
+}
diff --git a/internal/ls/autoimport/registry_test.go b/internal/ls/autoimport/registry_test.go
new file mode 100644
index 0000000000..840f3e4dda
--- /dev/null
+++ b/internal/ls/autoimport/registry_test.go
@@ -0,0 +1,312 @@
+package autoimport_test
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/microsoft/typescript-go/internal/collections"
+ "github.com/microsoft/typescript-go/internal/ls/autoimport"
+ "github.com/microsoft/typescript-go/internal/lsp/lsproto"
+ "github.com/microsoft/typescript-go/internal/project"
+ "github.com/microsoft/typescript-go/internal/testutil/autoimporttestutil"
+ "github.com/microsoft/typescript-go/internal/testutil/projecttestutil"
+ "github.com/microsoft/typescript-go/internal/tspath"
+ "gotest.tools/v3/assert"
+)
+
+func TestRegistryLifecycle(t *testing.T) {
+ t.Parallel()
+ t.Run("builds project and node_modules buckets", func(t *testing.T) {
+ t.Parallel()
+ fixture := autoimporttestutil.SetupLifecycleSession(t, lifecycleProjectRoot, 1)
+ session := fixture.Session()
+ project := fixture.SingleProject()
+ mainFile := project.File(0)
+ session.DidOpenFile(context.Background(), mainFile.URI(), 1, mainFile.Content(), lsproto.LanguageKindTypeScript)
+
+ stats := autoImportStats(t, session)
+ projectBucket := singleBucket(t, stats.ProjectBuckets)
+ nodeModulesBucket := singleBucket(t, stats.NodeModulesBuckets)
+ assert.Equal(t, true, projectBucket.State.Dirty())
+ assert.Equal(t, 0, projectBucket.FileCount)
+ assert.Equal(t, true, nodeModulesBucket.State.Dirty())
+ assert.Equal(t, 0, nodeModulesBucket.FileCount)
+
+ _, err := session.GetLanguageServiceWithAutoImports(context.Background(), mainFile.URI())
+ assert.NilError(t, err)
+
+ stats = autoImportStats(t, session)
+ projectBucket = singleBucket(t, stats.ProjectBuckets)
+ nodeModulesBucket = singleBucket(t, stats.NodeModulesBuckets)
+ assert.Equal(t, false, projectBucket.State.Dirty())
+ assert.Assert(t, projectBucket.ExportCount > 0)
+ assert.Equal(t, false, nodeModulesBucket.State.Dirty())
+ assert.Assert(t, nodeModulesBucket.ExportCount > 0)
+ })
+
+ t.Run("bucket does not rebuild on same-file change", func(t *testing.T) {
+ t.Parallel()
+ fixture := autoimporttestutil.SetupLifecycleSession(t, lifecycleProjectRoot, 2)
+ session := fixture.Session()
+ utils := fixture.Utils()
+ project := fixture.SingleProject()
+ mainFile := project.File(0)
+ secondaryFile := project.File(1)
+ session.DidOpenFile(context.Background(), mainFile.URI(), 1, mainFile.Content(), lsproto.LanguageKindTypeScript)
+ session.DidOpenFile(context.Background(), secondaryFile.URI(), 1, secondaryFile.Content(), lsproto.LanguageKindTypeScript)
+ _, err := session.GetLanguageServiceWithAutoImports(context.Background(), mainFile.URI())
+ assert.NilError(t, err)
+
+ updatedContent := mainFile.Content() + "// change\n"
+ session.DidChangeFile(context.Background(), mainFile.URI(), 2, []lsproto.TextDocumentContentChangePartialOrWholeDocument{
+ {WholeDocument: &lsproto.TextDocumentContentChangeWholeDocument{Text: updatedContent}},
+ })
+
+ _, err = session.GetLanguageService(context.Background(), mainFile.URI())
+ assert.NilError(t, err)
+
+ stats := autoImportStats(t, session)
+ projectBucket := singleBucket(t, stats.ProjectBuckets)
+ nodeModulesBucket := singleBucket(t, stats.NodeModulesBuckets)
+ assert.Equal(t, projectBucket.State.Dirty(), true)
+ assert.Equal(t, projectBucket.State.DirtyFile(), utils.ToPath(mainFile.FileName()))
+ assert.Equal(t, nodeModulesBucket.State.Dirty(), false)
+ assert.Equal(t, nodeModulesBucket.State.DirtyFile(), tspath.Path(""))
+
+ // Bucket should not recompute when requesting same file changed
+ _, err = session.GetLanguageServiceWithAutoImports(context.Background(), mainFile.URI())
+ assert.NilError(t, err)
+ stats = autoImportStats(t, session)
+ projectBucket = singleBucket(t, stats.ProjectBuckets)
+ assert.Equal(t, projectBucket.State.Dirty(), true)
+ assert.Equal(t, projectBucket.State.DirtyFile(), utils.ToPath(mainFile.FileName()))
+
+ // Bucket should recompute when other file has changed
+ session.DidChangeFile(context.Background(), secondaryFile.URI(), 1, []lsproto.TextDocumentContentChangePartialOrWholeDocument{
+ {WholeDocument: &lsproto.TextDocumentContentChangeWholeDocument{Text: "// new content"}},
+ })
+ _, err = session.GetLanguageServiceWithAutoImports(context.Background(), mainFile.URI())
+ assert.NilError(t, err)
+ stats = autoImportStats(t, session)
+ projectBucket = singleBucket(t, stats.ProjectBuckets)
+ assert.Equal(t, projectBucket.State.Dirty(), false)
+ })
+
+ t.Run("bucket updates on same-file change when new files added to the program", func(t *testing.T) {
+ t.Parallel()
+ projectRoot := "/home/src/explicit-files-project"
+ files := map[string]any{
+ projectRoot + "/tsconfig.json": `{
+ "compilerOptions": {
+ "module": "esnext",
+ "target": "esnext",
+ "strict": true
+ },
+ "files": ["index.ts"]
+ }`,
+ projectRoot + "/index.ts": "",
+ projectRoot + "/utils.ts": `export const foo = 1;
+export const bar = 2;`,
+ }
+ session, _ := projecttestutil.Setup(files)
+ t.Cleanup(session.Close)
+
+ ctx := context.Background()
+ indexURI := lsproto.DocumentUri("file://" + projectRoot + "/index.ts")
+
+ // Open the index.ts file
+ session.DidOpenFile(ctx, indexURI, 1, "", lsproto.LanguageKindTypeScript)
+ _, err := session.GetLanguageServiceWithAutoImports(ctx, indexURI)
+ assert.NilError(t, err)
+ stats := autoImportStats(t, session)
+ projectBucket := singleBucket(t, stats.ProjectBuckets)
+ assert.Equal(t, 1, projectBucket.FileCount)
+
+ // Edit index.ts to import foo from utils.ts
+ newContent := `import { foo } from "./utils";`
+ session.DidChangeFile(ctx, indexURI, 2, []lsproto.TextDocumentContentChangePartialOrWholeDocument{
+ {WholeDocument: &lsproto.TextDocumentContentChangeWholeDocument{Text: newContent}},
+ })
+
+ // Bucket should be rebuilt because new files were added
+ _, err = session.GetLanguageServiceWithAutoImports(ctx, indexURI)
+ assert.NilError(t, err)
+ stats = autoImportStats(t, session)
+ projectBucket = singleBucket(t, stats.ProjectBuckets)
+ assert.Equal(t, 2, projectBucket.FileCount)
+ })
+
+ t.Run("package.json dependency changes invalidate node_modules buckets", func(t *testing.T) {
+ t.Parallel()
+ fixture := autoimporttestutil.SetupLifecycleSession(t, lifecycleProjectRoot, 1)
+ session := fixture.Session()
+ sessionUtils := fixture.Utils()
+ project := fixture.SingleProject()
+ mainFile := project.File(0)
+ nodePackage := project.NodeModules()[0]
+ packageJSON := project.PackageJSONFile()
+ ctx := context.Background()
+
+ session.DidOpenFile(ctx, mainFile.URI(), 1, mainFile.Content(), lsproto.LanguageKindTypeScript)
+ _, err := session.GetLanguageServiceWithAutoImports(ctx, mainFile.URI())
+ assert.NilError(t, err)
+ stats := autoImportStats(t, session)
+ nodeModulesBucket := singleBucket(t, stats.NodeModulesBuckets)
+ assert.Equal(t, nodeModulesBucket.State.Dirty(), false)
+
+ fs := sessionUtils.FS()
+ updatePackageJSON := func(content string) {
+ assert.NilError(t, fs.WriteFile(packageJSON.FileName(), content, false))
+ session.DidChangeWatchedFiles(ctx, []*lsproto.FileEvent{
+ {Type: lsproto.FileChangeTypeChanged, Uri: packageJSON.URI()},
+ })
+ }
+
+ sameDepsContent := fmt.Sprintf("{\n \"name\": \"local-project-stable\",\n \"dependencies\": {\n \"%s\": \"*\"\n }\n}\n", nodePackage.Name)
+ updatePackageJSON(sameDepsContent)
+ _, err = session.GetLanguageService(ctx, mainFile.URI())
+ assert.NilError(t, err)
+ stats = autoImportStats(t, session)
+ nodeModulesBucket = singleBucket(t, stats.NodeModulesBuckets)
+ assert.Equal(t, nodeModulesBucket.State.Dirty(), false)
+
+ differentDepsContent := fmt.Sprintf("{\n \"name\": \"local-project-stable\",\n \"dependencies\": {\n \"%s\": \"*\",\n \"newpkg\": \"*\"\n }\n}\n", nodePackage.Name)
+ updatePackageJSON(differentDepsContent)
+ _, err = session.GetLanguageServiceWithAutoImports(ctx, mainFile.URI())
+ assert.NilError(t, err)
+ stats = autoImportStats(t, session)
+ assert.Check(t, singleBucket(t, stats.NodeModulesBuckets).DependencyNames.Has("newpkg"))
+ })
+
+ t.Run("node_modules buckets get deleted when no open files can reference them", func(t *testing.T) {
+ t.Parallel()
+ fixture := autoimporttestutil.SetupMonorepoLifecycleSession(t, autoimporttestutil.MonorepoSetupConfig{
+ Root: monorepoProjectRoot,
+ MonorepoPackageTemplate: autoimporttestutil.MonorepoPackageTemplate{
+ Name: "monorepo",
+ NodeModuleNames: []string{"pkg-root"},
+ },
+ Packages: []autoimporttestutil.MonorepoPackageConfig{
+ {FileCount: 1, MonorepoPackageTemplate: autoimporttestutil.MonorepoPackageTemplate{Name: "package-a", NodeModuleNames: []string{"pkg-a"}}},
+ {FileCount: 1, MonorepoPackageTemplate: autoimporttestutil.MonorepoPackageTemplate{Name: "package-b", NodeModuleNames: []string{"pkg-b"}}},
+ },
+ })
+ session := fixture.Session()
+ monorepo := fixture.Monorepo()
+ pkgA := monorepo.Package(0)
+ pkgB := monorepo.Package(1)
+ fileA := pkgA.File(0)
+ fileB := pkgB.File(0)
+ ctx := context.Background()
+
+ // Open file in package-a, should create buckets for root and package-a node_modules
+ session.DidOpenFile(ctx, fileA.URI(), 1, fileA.Content(), lsproto.LanguageKindTypeScript)
+ _, err := session.GetLanguageServiceWithAutoImports(ctx, fileA.URI())
+ assert.NilError(t, err)
+
+ // Open file in package-b, should also create buckets for package-b
+ session.DidOpenFile(ctx, fileB.URI(), 1, fileB.Content(), lsproto.LanguageKindTypeScript)
+ _, err = session.GetLanguageServiceWithAutoImports(ctx, fileB.URI())
+ assert.NilError(t, err)
+ stats := autoImportStats(t, session)
+ assert.Equal(t, len(stats.NodeModulesBuckets), 3)
+ assert.Equal(t, len(stats.ProjectBuckets), 2)
+
+ // Close file in package-a, package-a's node_modules bucket and project bucket should be removed
+ session.DidCloseFile(ctx, fileA.URI())
+ _, err = session.GetLanguageServiceWithAutoImports(ctx, fileB.URI())
+ assert.NilError(t, err)
+ stats = autoImportStats(t, session)
+ assert.Equal(t, len(stats.NodeModulesBuckets), 2)
+ assert.Equal(t, len(stats.ProjectBuckets), 1)
+ })
+
+ t.Run("node_modules bucket dependency selection changes with open files", func(t *testing.T) {
+ t.Parallel()
+ monorepoRoot := "/home/src/monorepo"
+ packageADir := tspath.CombinePaths(monorepoRoot, "packages", "a")
+ monorepoIndex := tspath.CombinePaths(monorepoRoot, "index.js")
+ packageAIndex := tspath.CombinePaths(packageADir, "index.js")
+
+ fixture := autoimporttestutil.SetupMonorepoLifecycleSession(t, autoimporttestutil.MonorepoSetupConfig{
+ Root: monorepoRoot,
+ MonorepoPackageTemplate: autoimporttestutil.MonorepoPackageTemplate{
+ Name: "monorepo",
+ NodeModuleNames: []string{"pkg1", "pkg2", "pkg3"},
+ DependencyNames: []string{"pkg1"},
+ },
+ Packages: []autoimporttestutil.MonorepoPackageConfig{
+ {
+ FileCount: 0,
+ MonorepoPackageTemplate: autoimporttestutil.MonorepoPackageTemplate{
+ Name: "a",
+ DependencyNames: []string{"pkg1", "pkg2"},
+ },
+ },
+ },
+ ExtraFiles: []autoimporttestutil.TextFileSpec{
+ {Path: monorepoIndex, Content: "export const monorepoIndex = 1;\n"},
+ {Path: packageAIndex, Content: "export const pkgA = 2;\n"},
+ },
+ })
+ session := fixture.Session()
+ monorepoHandle := fixture.ExtraFile(monorepoIndex)
+ packageAHandle := fixture.ExtraFile(packageAIndex)
+
+ ctx := context.Background()
+
+ // Open monorepo root file: expect dependencies restricted to pkg1
+ session.DidOpenFile(ctx, monorepoHandle.URI(), 1, monorepoHandle.Content(), lsproto.LanguageKindJavaScript)
+ _, err := session.GetLanguageServiceWithAutoImports(ctx, monorepoHandle.URI())
+ assert.NilError(t, err)
+ stats := autoImportStats(t, session)
+ assert.Assert(t, singleBucket(t, stats.NodeModulesBuckets).DependencyNames.Equals(collections.NewSetFromItems("pkg1")))
+
+ // Open package-a file: pkg2 should be added to existing bucket
+ session.DidOpenFile(ctx, packageAHandle.URI(), 1, packageAHandle.Content(), lsproto.LanguageKindJavaScript)
+ _, err = session.GetLanguageServiceWithAutoImports(ctx, packageAHandle.URI())
+ assert.NilError(t, err)
+ stats = autoImportStats(t, session)
+ assert.Assert(t, singleBucket(t, stats.NodeModulesBuckets).DependencyNames.Equals(collections.NewSetFromItems("pkg1", "pkg2")))
+
+ // Close package-a file; only monorepo bucket should remain
+ session.DidCloseFile(ctx, packageAHandle.URI())
+ _, err = session.GetLanguageServiceWithAutoImports(ctx, monorepoHandle.URI())
+ assert.NilError(t, err)
+ stats = autoImportStats(t, session)
+ assert.Assert(t, singleBucket(t, stats.NodeModulesBuckets).DependencyNames.Equals(collections.NewSetFromItems("pkg1")))
+
+ // Close monorepo file; no node_modules buckets should remain
+ session.DidCloseFile(ctx, monorepoHandle.URI())
+ session.DidOpenFile(ctx, "untitled:Untitled-1", 0, "", lsproto.LanguageKindTypeScript)
+ _, err = session.GetLanguageService(ctx, "untitled:Untitled-1")
+ assert.NilError(t, err)
+ stats = autoImportStats(t, session)
+ assert.Equal(t, len(stats.NodeModulesBuckets), 0)
+ })
+}
+
+const (
+ lifecycleProjectRoot = "/home/src/autoimport-lifecycle"
+ monorepoProjectRoot = "/home/src/autoimport-monorepo"
+)
+
+func autoImportStats(t *testing.T, session *project.Session) *autoimport.CacheStats {
+ t.Helper()
+ snapshot, release := session.Snapshot()
+ defer release()
+ registry := snapshot.AutoImportRegistry()
+ if registry == nil {
+ t.Fatal("auto import registry not initialized")
+ }
+ return registry.GetCacheStats()
+}
+
+func singleBucket(t *testing.T, buckets []autoimport.BucketStats) autoimport.BucketStats {
+ t.Helper()
+ if len(buckets) != 1 {
+ t.Fatalf("expected 1 bucket, got %d", len(buckets))
+ }
+ return buckets[0]
+}
diff --git a/internal/ls/autoimport/specifiers.go b/internal/ls/autoimport/specifiers.go
new file mode 100644
index 0000000000..f53ad3cbb6
--- /dev/null
+++ b/internal/ls/autoimport/specifiers.go
@@ -0,0 +1,75 @@
+package autoimport
+
+import (
+ "strings"
+
+ "github.com/microsoft/typescript-go/internal/modulespecifiers"
+)
+
+func (v *View) GetModuleSpecifier(
+ export *Export,
+ userPreferences modulespecifiers.UserPreferences,
+) (string, modulespecifiers.ResultKind) {
+ // Ambient module
+ if modulespecifiers.PathIsBareSpecifier(string(export.ModuleID)) {
+ specifier := string(export.ModuleID)
+ if modulespecifiers.IsExcludedByRegex(specifier, userPreferences.AutoImportSpecifierExcludeRegexes) {
+ return "", modulespecifiers.ResultKindNone
+ }
+ return string(export.ModuleID), modulespecifiers.ResultKindAmbient
+ }
+
+ if export.NodeModulesDirectory != "" {
+ if entrypoints, ok := v.registry.nodeModules[export.NodeModulesDirectory].Entrypoints[export.Path]; ok {
+ for _, entrypoint := range entrypoints {
+ if entrypoint.IncludeConditions.IsSubsetOf(v.conditions) && !v.conditions.Intersects(entrypoint.ExcludeConditions) {
+ specifier := modulespecifiers.ProcessEntrypointEnding(
+ entrypoint,
+ userPreferences,
+ v.program,
+ v.program.Options(),
+ v.importingFile,
+ v.getAllowedEndings(),
+ )
+
+ if !modulespecifiers.IsExcludedByRegex(specifier, userPreferences.AutoImportSpecifierExcludeRegexes) {
+ return specifier, modulespecifiers.ResultKindNodeModules
+ }
+ }
+ }
+ return "", modulespecifiers.ResultKindNone
+ }
+ }
+
+ cache := v.registry.specifierCache[v.importingFile.Path()]
+ if export.NodeModulesDirectory == "" {
+ if specifier, ok := cache.Load(export.Path); ok {
+ if specifier == "" {
+ return "", modulespecifiers.ResultKindNone
+ }
+ return specifier, modulespecifiers.ResultKindRelative
+ }
+ }
+
+ specifiers, kind := modulespecifiers.GetModuleSpecifiersForFileWithInfo(
+ v.importingFile,
+ export.ModuleFileName,
+ v.program.Options(),
+ v.program,
+ userPreferences,
+ modulespecifiers.ModuleSpecifierOptions{},
+ true,
+ )
+ // !!! unsure when this could return multiple specifiers combined with the
+ // new node_modules code. Possibly with local symlinks, which should be
+ // very rare.
+ for _, specifier := range specifiers {
+ if strings.Contains(specifier, "/node_modules/") {
+ continue
+ }
+ cache.Store(export.Path, specifier)
+ return specifier, kind
+ }
+ cache.Store(export.Path, "")
+ return "", modulespecifiers.ResultKindNone
+}
diff --git a/internal/ls/autoimport/util.go b/internal/ls/autoimport/util.go
new file mode 100644
index 0000000000..7b02be30d8
--- /dev/null
+++ b/internal/ls/autoimport/util.go
@@ -0,0 +1,174 @@
+package autoimport
+
+import (
+ "context"
+ "runtime"
+ "sync/atomic"
+ "unicode"
+ "unicode/utf8"
+
+ "github.com/microsoft/typescript-go/internal/ast"
+ "github.com/microsoft/typescript-go/internal/checker"
+ "github.com/microsoft/typescript-go/internal/collections"
+ "github.com/microsoft/typescript-go/internal/compiler"
+ "github.com/microsoft/typescript-go/internal/core"
+ "github.com/microsoft/typescript-go/internal/module"
+ "github.com/microsoft/typescript-go/internal/modulespecifiers"
+ "github.com/microsoft/typescript-go/internal/tspath"
+ "github.com/microsoft/typescript-go/internal/vfs"
+)
+
+func getModuleIDAndFileNameOfModuleSymbol(symbol *ast.Symbol) (ModuleID, string) {
+ if !symbol.IsExternalModule() {
+ panic("symbol is not an external module")
+ }
+ decl := ast.GetNonAugmentationDeclaration(symbol)
+ if decl == nil {
+ panic("module symbol has no non-augmentation declaration")
+ }
+ if decl.Kind == ast.KindSourceFile {
+ return ModuleID(decl.AsSourceFile().Path()), decl.AsSourceFile().FileName()
+ }
+ if ast.IsModuleWithStringLiteralName(decl) {
+ return ModuleID(decl.Name().Text()), ""
+ }
+ panic("could not determine module ID of module symbol")
+}
+
+// wordIndices splits an identifier into its constituent words based on camelCase and snake_case conventions
+// by returning the starting byte indices of each word. The first index is always 0.
+// - CamelCase
+// ^ ^
+// - snake_case
+// ^ ^
+// - ParseURL
+// ^ ^
+// - __proto__
+// ^
+func wordIndices(s string) []int {
+ var indices []int
+ for byteIndex, runeValue := range s {
+ if byteIndex == 0 {
+ indices = append(indices, byteIndex)
+ continue
+ }
+ if runeValue == '_' {
+ if byteIndex+1 < len(s) && s[byteIndex+1] != '_' {
+ indices = append(indices, byteIndex+1)
+ }
+ continue
+ }
+ if unicode.IsUpper(runeValue) && (unicode.IsLower(core.FirstResult(utf8.DecodeLastRuneInString(s[:byteIndex]))) || (byteIndex+1 < len(s) && unicode.IsLower(core.FirstResult(utf8.DecodeRuneInString(s[byteIndex+1:]))))) {
+ indices = append(indices, byteIndex)
+ }
+ }
+ return indices
+}
+
+func getPackageNamesInNodeModules(nodeModulesDir string, fs vfs.FS) (*collections.Set[string], error) {
+ packageNames := &collections.Set[string]{}
+ if tspath.GetBaseFileName(nodeModulesDir) != "node_modules" {
+ panic("nodeModulesDir is not a node_modules directory")
+ }
+ if !fs.DirectoryExists(nodeModulesDir) {
+ return nil, vfs.ErrNotExist
+ }
+ entries := fs.GetAccessibleEntries(nodeModulesDir)
+ for _, baseName := range entries.Directories {
+ if baseName[0] == '.' {
+ continue
+ }
+ if baseName[0] == '@' {
+ scopedDirPath := tspath.CombinePaths(nodeModulesDir, baseName)
+ for _, scopedPackageDirName := range fs.GetAccessibleEntries(scopedDirPath).Directories {
+ scopedBaseName := tspath.GetBaseFileName(scopedPackageDirName)
+ if baseName == "@types" {
+ packageNames.Add(module.GetPackageNameFromTypesPackageName(tspath.CombinePaths("@types", scopedBaseName)))
+ } else {
+ packageNames.Add(tspath.CombinePaths(baseName, scopedBaseName))
+ }
+ }
+ continue
+ }
+ packageNames.Add(baseName)
+ }
+ return packageNames, nil
+}
+
+func getDefaultLikeExportNameFromDeclaration(symbol *ast.Symbol) string {
+ for _, d := range symbol.Declarations {
+ // "export default" in this case. See `ExportAssignment`for more details.
+ if ast.IsExportAssignment(d) {
+ if innerExpression := ast.SkipOuterExpressions(d.Expression(), ast.OEKAll); ast.IsIdentifier(innerExpression) {
+ return innerExpression.Text()
+ }
+ continue
+ }
+ // "export { ~ as default }"
+ if ast.IsExportSpecifier(d) && d.Symbol().Flags == ast.SymbolFlagsAlias && d.PropertyName() != nil {
+ if d.PropertyName().Kind == ast.KindIdentifier {
+ return d.PropertyName().Text()
+ }
+ continue
+ }
+ // GH#52694
+ if name := ast.GetNameOfDeclaration(d); name != nil && name.Kind == ast.KindIdentifier {
+ return name.Text()
+ }
+ if symbol.Parent != nil && !checker.IsExternalModuleSymbol(symbol.Parent) {
+ return symbol.Parent.Name
+ }
+ }
+ return ""
+}
+
+func getResolvedPackageNames(ctx context.Context, program *compiler.Program) *collections.Set[string] {
+ resolvedPackageNames := program.ResolvedPackageNames().Clone()
+ unresolvedPackageNames := program.UnresolvedPackageNames()
+ if unresolvedPackageNames.Len() > 0 {
+ checker, done := program.GetTypeChecker(ctx)
+ defer done()
+ for name := range unresolvedPackageNames.Keys() {
+ if symbol := checker.TryFindAmbientModule(name); symbol != nil {
+ declaringFile := ast.GetSourceFileOfModule(symbol)
+ if packageName := modulespecifiers.GetPackageNameFromDirectory(declaringFile.FileName()); packageName != "" {
+ resolvedPackageNames.Add(packageName)
+ }
+ }
+ }
+ }
+ return resolvedPackageNames
+}
+
+func createCheckerPool(program checker.Program) (getChecker func() (*checker.Checker, func()), closePool func(), getCreatedCount func() int32) {
+ maxSize := int32(runtime.GOMAXPROCS(0))
+ pool := make(chan *checker.Checker, maxSize)
+ var created atomic.Int32
+
+ return func() (*checker.Checker, func()) {
+ // Try to get an existing checker
+ select {
+ case ch := <-pool:
+ return ch, func() { pool <- ch }
+ default:
+ break
+ }
+ // Try to create a new one if under limit
+ for {
+ current := created.Load()
+ if current >= maxSize {
+ // At limit, wait for one to become available
+ ch := <-pool
+ return ch, func() { pool <- ch }
+ }
+ if created.CompareAndSwap(current, current+1) {
+ ch := core.FirstResult(checker.NewChecker(program))
+ return ch, func() { pool <- ch }
+ }
+ }
+ }, func() {
+ close(pool)
+ }, func() int32 {
+ return created.Load()
+ }
+}
diff --git a/internal/ls/autoimport/util_test.go b/internal/ls/autoimport/util_test.go
new file mode 100644
index 0000000000..1dde71efd1
--- /dev/null
+++ b/internal/ls/autoimport/util_test.go
@@ -0,0 +1,90 @@
+package autoimport
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestWordIndices(t *testing.T) {
+ t.Parallel()
+ tests := []struct {
+ input string
+ expectedWords []string
+ }{
+ // Basic camelCase
+ {
+ input: "camelCase",
+ expectedWords: []string{"camelCase", "Case"},
+ },
+ // snake_case
+ {
+ input: "snake_case",
+ expectedWords: []string{"snake_case", "case"},
+ },
+ // ParseURL - uppercase sequence followed by lowercase
+ {
+ input: "ParseURL",
+ expectedWords: []string{"ParseURL", "URL"},
+ },
+ // XMLHttpRequest - multiple uppercase sequences
+ {
+ input: "XMLHttpRequest",
+ expectedWords: []string{"XMLHttpRequest", "HttpRequest", "Request"},
+ },
+ // Single word lowercase
+ {
+ input: "hello",
+ expectedWords: []string{"hello"},
+ },
+ // Single word uppercase
+ {
+ input: "HELLO",
+ expectedWords: []string{"HELLO"},
+ },
+ // Mixed with numbers
+ {
+ input: "parseHTML5Parser",
+ expectedWords: []string{"parseHTML5Parser", "HTML5Parser", "Parser"},
+ },
+ // Underscore variations
+ {
+ input: "__proto__",
+ expectedWords: []string{"__proto__", "proto__"},
+ },
+ {
+ input: "_private_member",
+ expectedWords: []string{"_private_member", "member"},
+ },
+ // Single character
+ {
+ input: "a",
+ expectedWords: []string{"a"},
+ },
+ {
+ input: "A",
+ expectedWords: []string{"A"},
+ },
+ // Consecutive underscores
+ {
+ input: "test__double__underscore",
+ expectedWords: []string{"test__double__underscore", "double__underscore", "underscore"},
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.input, func(t *testing.T) {
+ t.Parallel()
+ indices := wordIndices(tt.input)
+
+ // Convert indices to actual word slices for comparison
+ var actualWords []string
+ for _, idx := range indices {
+ actualWords = append(actualWords, tt.input[idx:])
+ }
+
+ if !reflect.DeepEqual(actualWords, tt.expectedWords) {
+ t.Errorf("wordIndices(%q) produced words %v, want %v", tt.input, actualWords, tt.expectedWords)
+ }
+ })
+ }
+}
diff --git a/internal/ls/autoimport/view.go b/internal/ls/autoimport/view.go
new file mode 100644
index 0000000000..46f5e8a900
--- /dev/null
+++ b/internal/ls/autoimport/view.go
@@ -0,0 +1,206 @@
+package autoimport
+
+import (
+ "context"
+ "slices"
+ "strings"
+ "unicode"
+
+ "github.com/microsoft/typescript-go/internal/ast"
+ "github.com/microsoft/typescript-go/internal/collections"
+ "github.com/microsoft/typescript-go/internal/compiler"
+ "github.com/microsoft/typescript-go/internal/core"
+ "github.com/microsoft/typescript-go/internal/ls/lsutil"
+ "github.com/microsoft/typescript-go/internal/module"
+ "github.com/microsoft/typescript-go/internal/modulespecifiers"
+ "github.com/microsoft/typescript-go/internal/tspath"
+)
+
+type View struct {
+ registry *Registry
+ importingFile *ast.SourceFile
+ program *compiler.Program
+ preferences modulespecifiers.UserPreferences
+ projectKey tspath.Path
+
+ allowedEndings []modulespecifiers.ModuleSpecifierEnding
+ conditions *collections.Set[string]
+ shouldUseUriStyleNodeCoreModules core.Tristate
+ existingImports *collections.MultiMap[ModuleID, existingImport]
+ shouldUseRequireForFixes *bool
+}
+
+func NewView(registry *Registry, importingFile *ast.SourceFile, projectKey tspath.Path, program *compiler.Program, preferences modulespecifiers.UserPreferences) *View {
+ return &View{
+ registry: registry,
+ importingFile: importingFile,
+ program: program,
+ projectKey: projectKey,
+ preferences: preferences,
+ conditions: collections.NewSetFromItems(
+ module.GetConditions(program.Options(),
+ program.GetDefaultResolutionModeForFile(importingFile))...,
+ ),
+ shouldUseUriStyleNodeCoreModules: lsutil.ShouldUseUriStyleNodeCoreModules(importingFile, program),
+ }
+}
+
+func (v *View) getAllowedEndings() []modulespecifiers.ModuleSpecifierEnding {
+ if v.allowedEndings == nil {
+ resolutionMode := v.program.GetDefaultResolutionModeForFile(v.importingFile)
+ v.allowedEndings = modulespecifiers.GetAllowedEndingsInPreferredOrder(
+ v.preferences,
+ v.program,
+ v.program.Options(),
+ v.importingFile,
+ "",
+ resolutionMode,
+ )
+ }
+ return v.allowedEndings
+}
+
+type QueryKind int
+
+const (
+ QueryKindWordPrefix QueryKind = iota
+ QueryKindExactMatch
+ QueryKindCaseInsensitiveMatch
+)
+
+func (v *View) Search(query string, kind QueryKind) []*Export {
+ var results []*Export
+ search := func(bucket *RegistryBucket) []*Export {
+ switch kind {
+ case QueryKindWordPrefix:
+ return bucket.Index.SearchWordPrefix(query)
+ case QueryKindExactMatch:
+ return bucket.Index.Find(query, true)
+ case QueryKindCaseInsensitiveMatch:
+ return bucket.Index.Find(query, false)
+ default:
+ panic("unreachable")
+ }
+ }
+
+ if bucket, ok := v.registry.projects[v.projectKey]; ok {
+ exports := search(bucket)
+ results = slices.Grow(results, len(exports))
+ for _, e := range exports {
+ if string(e.ModuleID) == string(v.importingFile.Path()) {
+ // Don't auto-import from the importing file itself
+ continue
+ }
+ results = append(results, e)
+ }
+ }
+
+ var excludePackages *collections.Set[string]
+ tspath.ForEachAncestorDirectoryPath(v.importingFile.Path().GetDirectoryPath(), func(dirPath tspath.Path) (result any, stop bool) {
+ if nodeModulesBucket, ok := v.registry.nodeModules[dirPath]; ok {
+ exports := search(nodeModulesBucket)
+ if excludePackages.Len() > 0 {
+ results = slices.Grow(results, len(exports))
+ for _, e := range exports {
+ if !excludePackages.Has(e.PackageName) {
+ results = append(results, e)
+ }
+ }
+ } else {
+ results = append(results, exports...)
+ }
+
+ // As we go up the directory tree, exclude packages found in lower node_modules
+ excludePackages = excludePackages.UnionedWith(nodeModulesBucket.PackageNames)
+ }
+ return nil, false
+ })
+ return results
+}
+
+type FixAndExport struct {
+ Fix *Fix
+ Export *Export
+}
+
+func (v *View) GetCompletions(ctx context.Context, prefix string, forJSX bool, isTypeOnlyLocation bool) []*FixAndExport {
+ results := v.Search(prefix, QueryKindWordPrefix)
+
+ type exportGroupKey struct {
+ target ExportID
+ name string
+ ambientModuleOrPackageName string
+ }
+ grouped := make(map[exportGroupKey][]*Export, len(results))
+outer:
+ for _, e := range results {
+ name := e.Name()
+ if forJSX && !(unicode.IsUpper(rune(name[0])) || e.IsRenameable()) {
+ continue
+ }
+ target := e.ExportID
+ if e.Target != (ExportID{}) {
+ target = e.Target
+ }
+ key := exportGroupKey{
+ target: target,
+ name: name,
+ ambientModuleOrPackageName: core.FirstNonZero(e.AmbientModuleName(), e.PackageName),
+ }
+ if e.PackageName == "@types/node" || strings.Contains(string(e.Path), "/node_modules/@types/node/") {
+ if _, ok := core.UnprefixedNodeCoreModules[key.ambientModuleOrPackageName]; ok {
+ // Group URI-style and non-URI style node core modules together so the ranking logic
+ // is allowed to drop one if an explicit preference is detected.
+ key.ambientModuleOrPackageName = "node:" + key.ambientModuleOrPackageName
+ }
+ }
+ if existing, ok := grouped[key]; ok {
+ for i, ex := range existing {
+ if e.ExportID == ex.ExportID {
+ grouped[key] = slices.Replace(existing, i, i+1, &Export{
+ ExportID: e.ExportID,
+ ModuleFileName: e.ModuleFileName,
+ Syntax: min(e.Syntax, ex.Syntax),
+ Flags: e.Flags | ex.Flags,
+ ScriptElementKind: min(e.ScriptElementKind, ex.ScriptElementKind),
+ ScriptElementKindModifiers: *e.ScriptElementKindModifiers.UnionedWith(&ex.ScriptElementKindModifiers),
+ localName: e.localName,
+ Target: e.Target,
+ Path: e.Path,
+ NodeModulesDirectory: e.NodeModulesDirectory,
+ })
+ continue outer
+ }
+ }
+ }
+ grouped[key] = append(grouped[key], e)
+ }
+
+ fixes := make([]*FixAndExport, 0, len(results))
+ compareFixes := func(a, b *FixAndExport) int {
+ return v.CompareFixesForRanking(a.Fix, b.Fix)
+ }
+
+ for _, exps := range grouped {
+ fixesForGroup := make([]*FixAndExport, 0, len(exps))
+ for _, e := range exps {
+ for _, fix := range v.GetFixes(ctx, e, forJSX, isTypeOnlyLocation, nil) {
+ fixesForGroup = append(fixesForGroup, &FixAndExport{
+ Fix: fix,
+ Export: e,
+ })
+ }
+ }
+ fixes = append(fixes, core.MinAllFunc(fixesForGroup, compareFixes)...)
+ }
+
+ // The client will do additional sorting by SortText and Label, so we don't
+ // need to consider the name in our sorting here; we only need to produce a
+ // stable relative ordering between completions that the client will consider
+ // equivalent.
+ slices.SortFunc(fixes, func(a, b *FixAndExport) int {
+ return v.CompareFixesForSorting(a.Fix, b.Fix)
+ })
+
+ return fixes
+}
diff --git a/internal/ls/autoimportfixes.go b/internal/ls/autoimportfixes.go
deleted file mode 100644
index 7383474f07..0000000000
--- a/internal/ls/autoimportfixes.go
+++ /dev/null
@@ -1,368 +0,0 @@
-package ls
-
-import (
- "slices"
-
- "github.com/microsoft/typescript-go/internal/ast"
- "github.com/microsoft/typescript-go/internal/astnav"
- "github.com/microsoft/typescript-go/internal/core"
- "github.com/microsoft/typescript-go/internal/debug"
- "github.com/microsoft/typescript-go/internal/ls/change"
- "github.com/microsoft/typescript-go/internal/ls/lsutil"
- "github.com/microsoft/typescript-go/internal/ls/organizeimports"
- "github.com/microsoft/typescript-go/internal/stringutil"
-)
-
-type Import struct {
- name string
- kind ImportKind // ImportKindCommonJS | ImportKindNamespace
- addAsTypeOnly AddAsTypeOnly
- propertyName string // Use when needing to generate an `ImportSpecifier with a `propertyName`; the name preceding "as" keyword (propertyName = "" when "as" is absent)
-}
-
-func addNamespaceQualifier(ct *change.Tracker, sourceFile *ast.SourceFile, qualification *Qualification) {
- ct.InsertText(sourceFile, qualification.usagePosition, qualification.namespacePrefix+".")
-}
-
-func (ls *LanguageService) doAddExistingFix(
- ct *change.Tracker,
- sourceFile *ast.SourceFile,
- clause *ast.Node, // ImportClause | ObjectBindingPattern,
- defaultImport *Import,
- namedImports []*Import,
- // removeExistingImportSpecifiers *core.Set[ImportSpecifier | BindingElement] // !!! remove imports not implemented
-) {
- switch clause.Kind {
- case ast.KindObjectBindingPattern:
- if clause.Kind == ast.KindObjectBindingPattern {
- // bindingPattern := clause.AsBindingPattern()
- // !!! adding *and* removing imports not implemented
- // if (removeExistingImportSpecifiers && core.Some(bindingPattern.Elements, func(e *ast.Node) bool {
- // return removeExistingImportSpecifiers.Has(e)
- // })) {
- // If we're both adding and removing elements, just replace and reprint the whole
- // node. The change tracker doesn't understand all the operations and can insert or
- // leave behind stray commas.
- // ct.replaceNode(
- // sourceFile,
- // bindingPattern,
- // ct.NodeFactory.NewObjectBindingPattern([
- // ...bindingPattern.Elements.Filter(func(e *ast.Node) bool {
- // return !removeExistingImportSpecifiers.Has(e)
- // }),
- // ...defaultImport ? [ct.NodeFactory.createBindingElement(/*dotDotDotToken*/ nil, /*propertyName*/ "default", defaultImport.name)] : emptyArray,
- // ...namedImports.map(i => ct.NodeFactory.createBindingElement(/*dotDotDotToken*/ nil, i.propertyName, i.name)),
- // ]),
- // )
- // return
- // }
- if defaultImport != nil {
- addElementToBindingPattern(ct, sourceFile, clause, defaultImport.name, ptrTo("default"))
- }
- for _, specifier := range namedImports {
- addElementToBindingPattern(ct, sourceFile, clause, specifier.name, &specifier.propertyName)
- }
- return
- }
- case ast.KindImportClause:
-
- importClause := clause.AsImportClause()
-
- // promoteFromTypeOnly = true if we need to promote the entire original clause from type only
- promoteFromTypeOnly := importClause.IsTypeOnly() && core.Some(append(namedImports, defaultImport), func(i *Import) bool {
- if i == nil {
- return false
- }
- return i.addAsTypeOnly == AddAsTypeOnlyNotAllowed
- })
-
- existingSpecifiers := []*ast.Node{} // []*ast.ImportSpecifier
- if importClause.NamedBindings != nil && importClause.NamedBindings.Kind == ast.KindNamedImports {
- existingSpecifiers = importClause.NamedBindings.Elements()
- }
-
- if defaultImport != nil {
- debug.Assert(clause.Name() == nil, "Cannot add a default import to an import clause that already has one")
- ct.InsertNodeAt(sourceFile, core.TextPos(astnav.GetStartOfNode(clause, sourceFile, false)), ct.NodeFactory.NewIdentifier(defaultImport.name), change.NodeOptions{Suffix: ", "})
- }
-
- if len(namedImports) > 0 {
- specifierComparer, isSorted := organizeimports.GetNamedImportSpecifierComparerWithDetection(importClause.Parent, sourceFile, ls.UserPreferences())
- newSpecifiers := core.Map(namedImports, func(namedImport *Import) *ast.Node {
- var identifier *ast.Node
- if namedImport.propertyName != "" {
- identifier = ct.NodeFactory.NewIdentifier(namedImport.propertyName).AsIdentifier().AsNode()
- }
- return ct.NodeFactory.NewImportSpecifier(
- (!importClause.IsTypeOnly() || promoteFromTypeOnly) && shouldUseTypeOnly(namedImport.addAsTypeOnly, ls.UserPreferences()),
- identifier,
- ct.NodeFactory.NewIdentifier(namedImport.name),
- )
- })
- slices.SortFunc(newSpecifiers, specifierComparer)
-
- // !!! remove imports not implemented
- // if (removeExistingImportSpecifiers) {
- // // If we're both adding and removing specifiers, just replace and reprint the whole
- // // node. The change tracker doesn't understand all the operations and can insert or
- // // leave behind stray commas.
- // ct.replaceNode(
- // sourceFile,
- // importClause.NamedBindings,
- // ct.NodeFactory.updateNamedImports(
- // importClause.NamedBindings.AsNamedImports(),
- // append(core.Filter(existingSpecifiers, func (s *ast.ImportSpecifier) bool {return !removeExistingImportSpecifiers.Has(s)}), newSpecifiers...), // !!! sort with specifierComparer
- // ),
- // );
- //
- 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 (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(specsToCompareAgainst, spec, specifierComparer)
- ct.InsertImportSpecifierAtIndex(sourceFile, spec, importClause.NamedBindings, insertionIndex)
- }
- } else if len(existingSpecifiers) > 0 && isSorted.IsTrue() {
- // Existing specifiers are sorted, so insert each new specifier at the correct position
- for _, spec := range newSpecifiers {
- insertionIndex := organizeimports.GetImportSpecifierInsertionIndex(existingSpecifiers, spec, specifierComparer)
- if insertionIndex >= len(existingSpecifiers) {
- // Insert at the end
- ct.InsertNodeInListAfter(sourceFile, existingSpecifiers[len(existingSpecifiers)-1], spec.AsNode(), existingSpecifiers)
- } else {
- // Insert before the element at insertionIndex
- ct.InsertNodeInListAfter(sourceFile, existingSpecifiers[insertionIndex], spec.AsNode(), existingSpecifiers)
- }
- }
- } else if len(existingSpecifiers) > 0 {
- // Existing specifiers may not be sorted, append to the end
- for _, spec := range newSpecifiers {
- ct.InsertNodeInListAfter(sourceFile, existingSpecifiers[len(existingSpecifiers)-1], spec.AsNode(), existingSpecifiers)
- }
- } else {
- if len(newSpecifiers) > 0 {
- namedImports := ct.NodeFactory.NewNamedImports(ct.NodeFactory.NewNodeList(newSpecifiers))
- if importClause.NamedBindings != nil {
- ct.ReplaceNode(sourceFile, importClause.NamedBindings, namedImports, nil)
- } else {
- if clause.Name() == nil {
- panic("Import clause must have either named imports or a default import")
- }
- ct.InsertNodeAfter(sourceFile, clause.Name(), namedImports)
- }
- }
- }
- }
-
- if promoteFromTypeOnly {
- // Delete the 'type' keyword from the import clause
- typeKeyword := getTypeKeywordOfTypeOnlyImport(importClause, sourceFile)
- ct.Delete(sourceFile, typeKeyword)
-
- // 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 {
- ct.InsertNodeInListAfter(sourceFile, bindingPattern.Elements()[len(bindingPattern.Elements())-1], element, nil)
- } else {
- ct.ReplaceNode(sourceFile, bindingPattern, ct.NodeFactory.NewBindingPattern(
- ast.KindObjectBindingPattern,
- ct.NodeFactory.NewNodeList([]*ast.Node{element}),
- ), nil)
- }
-}
-
-func newBindingElementFromNameAndPropertyName(ct *change.Tracker, name string, propertyName *string) *ast.Node {
- var newPropertyNameIdentifier *ast.Node
- if propertyName != nil {
- newPropertyNameIdentifier = ct.NodeFactory.NewIdentifier(*propertyName)
- }
- return ct.NodeFactory.NewBindingElement(
- nil, /*dotDotDotToken*/
- newPropertyNameIdentifier,
- ct.NodeFactory.NewIdentifier(name),
- nil, /* initializer */
- )
-}
-
-func (ls *LanguageService) insertImports(ct *change.Tracker, sourceFile *ast.SourceFile, imports []*ast.Statement, blankLineBetween bool) {
- var existingImportStatements []*ast.Statement
-
- if imports[0].Kind == ast.KindVariableStatement {
- existingImportStatements = core.Filter(sourceFile.Statements.Nodes, ast.IsRequireVariableStatement)
- } else {
- existingImportStatements = core.Filter(sourceFile.Statements.Nodes, ast.IsAnyImportSyntax)
- }
- comparer, isSorted := organizeimports.GetOrganizeImportsStringComparerWithDetection(existingImportStatements, ls.UserPreferences())
- sortedNewImports := slices.Clone(imports)
- slices.SortFunc(sortedNewImports, func(a, b *ast.Statement) int {
- return organizeimports.CompareImportsOrRequireStatements(a, b, comparer)
- })
- // !!! FutureSourceFile
- // if !isFullSourceFile(sourceFile) {
- // for _, newImport := range sortedNewImports {
- // // Insert one at a time to send correct original source file for accurate text reuse
- // // when some imports are cloned from existing ones in other files.
- // ct.insertStatementsInNewFile(sourceFile.fileName, []*ast.Node{newImport}, ast.GetSourceFileOfNode(getOriginalNode(newImport)))
- // }
- // return;
- // }
-
- if len(existingImportStatements) > 0 && isSorted {
- // Existing imports are sorted, insert each new import at the correct position
- for _, newImport := range sortedNewImports {
- insertionIndex := organizeimports.GetImportDeclarationInsertIndex(existingImportStatements, newImport, func(a, b *ast.Statement) stringutil.Comparison {
- return organizeimports.CompareImportsOrRequireStatements(a, b, comparer)
- })
- if insertionIndex == 0 {
- // If the first import is top-of-file, insert after the leading comment which is likely the header
- ct.InsertNodeAt(sourceFile, core.TextPos(astnav.GetStartOfNode(existingImportStatements[0], sourceFile, false)), newImport.AsNode(), change.NodeOptions{})
- } else {
- prevImport := existingImportStatements[insertionIndex-1]
- ct.InsertNodeAfter(sourceFile, prevImport.AsNode(), newImport.AsNode())
- }
- }
- } else if len(existingImportStatements) > 0 {
- ct.InsertNodesAfter(sourceFile, existingImportStatements[len(existingImportStatements)-1], sortedNewImports)
- } else {
- ct.InsertAtTopOfFile(sourceFile, sortedNewImports, blankLineBetween)
- }
-}
-
-func makeImport(ct *change.Tracker, defaultImport *ast.IdentifierNode, namedImports []*ast.Node, moduleSpecifier *ast.Expression, isTypeOnly bool) *ast.Statement {
- var newNamedImports *ast.Node
- if len(namedImports) > 0 {
- newNamedImports = ct.NodeFactory.NewNamedImports(ct.NodeFactory.NewNodeList(namedImports))
- }
- var importClause *ast.Node
- if defaultImport != nil || newNamedImports != nil {
- importClause = ct.NodeFactory.NewImportClause(core.IfElse(isTypeOnly, ast.KindTypeKeyword, ast.KindUnknown), defaultImport, newNamedImports)
- }
- return ct.NodeFactory.NewImportDeclaration( /*modifiers*/ nil, importClause, moduleSpecifier, nil /*attributes*/)
-}
-
-func (ls *LanguageService) getNewImports(
- ct *change.Tracker,
- moduleSpecifier string,
- quotePreference quotePreference,
- defaultImport *Import,
- namedImports []*Import,
- namespaceLikeImport *Import, // { importKind: ImportKind.CommonJS | ImportKind.Namespace; }
- compilerOptions *core.CompilerOptions,
-) []*ast.Statement {
- moduleSpecifierStringLiteral := ct.NodeFactory.NewStringLiteral(
- moduleSpecifier,
- core.IfElse(quotePreference == quotePreferenceSingle, ast.TokenFlagsSingleQuote, ast.TokenFlagsNone),
- )
- var statements []*ast.Statement // []AnyImportSyntax
- if defaultImport != nil || len(namedImports) > 0 {
- // `verbatimModuleSyntax` should prefer top-level `import type` -
- // even though it's not an error, it would add unnecessary runtime emit.
- topLevelTypeOnly := (defaultImport == nil || needsTypeOnly(defaultImport.addAsTypeOnly)) &&
- core.Every(namedImports, func(i *Import) bool { return needsTypeOnly(i.addAsTypeOnly) }) ||
- (compilerOptions.VerbatimModuleSyntax.IsTrue() || ls.UserPreferences().PreferTypeOnlyAutoImports == core.TSTrue) &&
- (defaultImport == nil || defaultImport.addAsTypeOnly != AddAsTypeOnlyNotAllowed) &&
- !core.Some(namedImports, func(i *Import) bool { return i.addAsTypeOnly == AddAsTypeOnlyNotAllowed })
-
- var defaultImportNode *ast.Node
- if defaultImport != nil {
- defaultImportNode = ct.NodeFactory.NewIdentifier(defaultImport.name)
- }
-
- statements = append(statements, makeImport(ct, defaultImportNode, core.Map(namedImports, func(namedImport *Import) *ast.Node {
- var namedImportPropertyName *ast.Node
- if namedImport.propertyName != "" {
- namedImportPropertyName = ct.NodeFactory.NewIdentifier(namedImport.propertyName)
- }
- return ct.NodeFactory.NewImportSpecifier(
- !topLevelTypeOnly && shouldUseTypeOnly(namedImport.addAsTypeOnly, ls.UserPreferences()),
- namedImportPropertyName,
- ct.NodeFactory.NewIdentifier(namedImport.name),
- )
- }), moduleSpecifierStringLiteral, topLevelTypeOnly))
- }
-
- if namespaceLikeImport != nil {
- var declaration *ast.Statement
- if namespaceLikeImport.kind == ImportKindCommonJS {
- declaration = ct.NodeFactory.NewImportEqualsDeclaration(
- /*modifiers*/ nil,
- shouldUseTypeOnly(namespaceLikeImport.addAsTypeOnly, ls.UserPreferences()),
- ct.NodeFactory.NewIdentifier(namespaceLikeImport.name),
- ct.NodeFactory.NewExternalModuleReference(moduleSpecifierStringLiteral),
- )
- } else {
- declaration = ct.NodeFactory.NewImportDeclaration(
- /*modifiers*/ nil,
- ct.NodeFactory.NewImportClause(
- /*phaseModifier*/ core.IfElse(shouldUseTypeOnly(namespaceLikeImport.addAsTypeOnly, ls.UserPreferences()), ast.KindTypeKeyword, ast.KindUnknown),
- /*name*/ nil,
- ct.NodeFactory.NewNamespaceImport(ct.NodeFactory.NewIdentifier(namespaceLikeImport.name)),
- ),
- moduleSpecifierStringLiteral,
- /*attributes*/ nil,
- )
- }
- statements = append(statements, declaration)
- }
- if len(statements) == 0 {
- panic("No statements to insert for new imports")
- }
- return statements
-}
-
-func needsTypeOnly(addAsTypeOnly AddAsTypeOnly) bool {
- return addAsTypeOnly == AddAsTypeOnlyRequired
-}
-
-func shouldUseTypeOnly(addAsTypeOnly AddAsTypeOnly, preferences *lsutil.UserPreferences) bool {
- return needsTypeOnly(addAsTypeOnly) || addAsTypeOnly != AddAsTypeOnlyNotAllowed && preferences.PreferTypeOnlyAutoImports == core.TSTrue
-}
diff --git a/internal/ls/autoimports.go b/internal/ls/autoimports.go
deleted file mode 100644
index c88f426880..0000000000
--- a/internal/ls/autoimports.go
+++ /dev/null
@@ -1,1703 +0,0 @@
-package ls
-
-import (
- "context"
- "fmt"
- "strings"
-
- "github.com/dlclark/regexp2"
- "github.com/microsoft/typescript-go/internal/ast"
- "github.com/microsoft/typescript-go/internal/astnav"
- "github.com/microsoft/typescript-go/internal/binder"
- "github.com/microsoft/typescript-go/internal/checker"
- "github.com/microsoft/typescript-go/internal/collections"
- "github.com/microsoft/typescript-go/internal/compiler"
- "github.com/microsoft/typescript-go/internal/core"
- "github.com/microsoft/typescript-go/internal/debug"
- "github.com/microsoft/typescript-go/internal/diagnostics"
- "github.com/microsoft/typescript-go/internal/locale"
- "github.com/microsoft/typescript-go/internal/ls/change"
- "github.com/microsoft/typescript-go/internal/ls/lsutil"
- "github.com/microsoft/typescript-go/internal/lsp/lsproto"
- "github.com/microsoft/typescript-go/internal/module"
- "github.com/microsoft/typescript-go/internal/modulespecifiers"
- "github.com/microsoft/typescript-go/internal/packagejson"
- "github.com/microsoft/typescript-go/internal/stringutil"
- "github.com/microsoft/typescript-go/internal/tspath"
- "github.com/microsoft/typescript-go/internal/vfs"
-)
-
-type SymbolExportInfo struct {
- symbol *ast.Symbol
- moduleSymbol *ast.Symbol
- moduleFileName string
- exportKind ExportKind
- targetFlags ast.SymbolFlags
- isFromPackageJson bool
-}
-
-type symbolExportEntry struct {
- symbol *ast.Symbol
- moduleSymbol *ast.Symbol
-}
-
-func newExportInfoMapKey(importedName string, symbol *ast.Symbol, ambientModuleNameKey string, ch *checker.Checker) lsproto.ExportInfoMapKey {
- return lsproto.ExportInfoMapKey{
- SymbolName: importedName,
- SymbolId: uint64(ast.GetSymbolId(ch.SkipAlias(symbol))),
- AmbientModuleName: ambientModuleNameKey,
- }
-}
-
-type CachedSymbolExportInfo struct {
- // Used to rehydrate `symbol` and `moduleSymbol` when transient
- id int
- symbolTableKey string
- symbolName string
- capitalizedSymbolName string
- moduleName string
- moduleFile *ast.SourceFile // may be nil
- packageName string
-
- symbol *ast.Symbol // may be nil
- moduleSymbol *ast.Symbol // may be nil
- moduleFileName string // may be ""
- targetFlags ast.SymbolFlags
- exportKind ExportKind
- isFromPackageJson bool
-}
-
-type ExportInfoMap struct {
- exportInfo collections.OrderedMap[lsproto.ExportInfoMapKey, []*CachedSymbolExportInfo]
- symbols map[int]symbolExportEntry
- exportInfoId int
- usableByFileName tspath.Path
- packages map[string]string
-
- globalTypingsCacheLocation string
-
- // !!! releaseSymbols func()
- // !!! onFileChanged func(oldSourceFile *ast.SourceFile, newSourceFile *ast.SourceFile, typeAcquisitionEnabled bool) bool
-}
-
-func (e *ExportInfoMap) clear() {
- e.symbols = map[int]symbolExportEntry{}
- e.exportInfo = collections.OrderedMap[lsproto.ExportInfoMapKey, []*CachedSymbolExportInfo]{}
- e.usableByFileName = ""
-}
-
-func (e *ExportInfoMap) get(importingFile tspath.Path, ch *checker.Checker, key lsproto.ExportInfoMapKey) []*SymbolExportInfo {
- if e.usableByFileName != importingFile {
- return nil
- }
- return core.Map(e.exportInfo.GetOrZero(key), func(info *CachedSymbolExportInfo) *SymbolExportInfo { return e.rehydrateCachedInfo(ch, info) })
-}
-
-func (e *ExportInfoMap) add(
- importingFile tspath.Path,
- symbol *ast.Symbol,
- symbolTableKey string,
- moduleSymbol *ast.Symbol,
- moduleFile *ast.SourceFile,
- exportKind ExportKind,
- isFromPackageJson bool,
- ch *checker.Checker,
- symbolNameMatch func(string) bool,
- flagMatch func(ast.SymbolFlags) bool,
-) {
- if importingFile != e.usableByFileName {
- e.clear()
- e.usableByFileName = importingFile
- }
-
- packageName := ""
- if moduleFile != nil {
- if nodeModulesPathParts := modulespecifiers.GetNodeModulePathParts(moduleFile.FileName()); nodeModulesPathParts != nil {
- topLevelNodeModulesIndex := nodeModulesPathParts.TopLevelNodeModulesIndex
- topLevelPackageNameIndex := nodeModulesPathParts.TopLevelPackageNameIndex
- packageRootIndex := nodeModulesPathParts.PackageRootIndex
- packageName = module.UnmangleScopedPackageName(modulespecifiers.GetPackageNameFromTypesPackageName(moduleFile.FileName()[topLevelPackageNameIndex+1 : packageRootIndex]))
- if strings.HasPrefix(string(importingFile), string(moduleFile.Path())[0:topLevelNodeModulesIndex]) {
- nodeModulesPath := moduleFile.FileName()[0 : topLevelPackageNameIndex+1]
- if prevDeepestNodeModulesPath, ok := e.packages[packageName]; ok {
- prevDeepestNodeModulesIndex := strings.Index(prevDeepestNodeModulesPath, "/node_modules/")
- if topLevelNodeModulesIndex > prevDeepestNodeModulesIndex {
- e.packages[packageName] = nodeModulesPath
- }
- } else {
- e.packages[packageName] = nodeModulesPath
- }
- }
- }
- }
-
- isDefault := exportKind == ExportKindDefault
- namedSymbol := symbol
- if isDefault {
- if s := binder.GetLocalSymbolForExportDefault(symbol); s != nil {
- namedSymbol = s
- }
- }
- // 1. A named export must be imported by its key in `moduleSymbol.exports` or `moduleSymbol.members`.
- // 2. A re-export merged with an export from a module augmentation can result in `symbol`
- // being an external module symbol; the name it is re-exported by will be `symbolTableKey`
- // (which comes from the keys of `moduleSymbol.exports`.)
- // 3. Otherwise, we have a default/namespace import that can be imported by any name, and
- // `symbolTableKey` will be something undesirable like `export=` or `default`, so we try to
- // get a better name.
- names := []string{}
- if exportKind == ExportKindNamed || checker.IsExternalModuleSymbol(namedSymbol) {
- names = append(names, symbolTableKey)
- } else {
- names = getNamesForExportedSymbol(namedSymbol, ch, core.ScriptTargetNone)
- }
-
- symbolName := names[0]
- if symbolNameMatch != nil && !symbolNameMatch(symbolName) {
- return
- }
-
- capitalizedSymbolName := ""
- if len(names) > 1 {
- capitalizedSymbolName = names[1]
- }
-
- moduleName := stringutil.StripQuotes(moduleSymbol.Name)
- e.exportInfoId++
- id := e.exportInfoId
- target := ch.SkipAlias(symbol)
-
- if flagMatch != nil && !flagMatch(target.Flags) {
- return
- }
-
- var storedSymbol, storedModuleSymbol *ast.Symbol
-
- if symbol.Flags&ast.SymbolFlagsTransient == 0 {
- storedSymbol = symbol
- }
- if moduleSymbol.Flags&ast.SymbolFlagsTransient == 0 {
- storedModuleSymbol = moduleSymbol
- }
-
- if storedSymbol == nil || storedModuleSymbol == nil {
- e.symbols[id] = symbolExportEntry{storedSymbol, storedModuleSymbol}
- }
-
- moduleKey := ""
- if !tspath.IsExternalModuleNameRelative(moduleName) {
- moduleKey = moduleName
- }
-
- moduleFileName := ""
- if moduleFile != nil {
- moduleFileName = moduleFile.FileName()
- }
- key := newExportInfoMapKey(symbolName, symbol, moduleKey, ch)
- infos := e.exportInfo.GetOrZero(key)
- infos = append(infos, &CachedSymbolExportInfo{
- id: id,
- symbolTableKey: symbolTableKey,
- symbolName: symbolName,
- capitalizedSymbolName: capitalizedSymbolName,
- moduleName: moduleName,
- moduleFile: moduleFile,
- moduleFileName: moduleFileName,
- packageName: packageName,
-
- symbol: storedSymbol,
- moduleSymbol: storedModuleSymbol,
- exportKind: exportKind,
- targetFlags: target.Flags,
- isFromPackageJson: isFromPackageJson,
- })
- e.exportInfo.Set(key, infos)
-}
-
-func (e *ExportInfoMap) search(
- ch *checker.Checker,
- importingFile tspath.Path,
- preferCapitalized bool,
- matches func(name string, targetFlags ast.SymbolFlags) bool,
- action func(info []*SymbolExportInfo, symbolName string, isFromAmbientModule bool, key lsproto.ExportInfoMapKey) []*SymbolExportInfo,
-) []*SymbolExportInfo {
- if importingFile != e.usableByFileName {
- return nil
- }
- for key, info := range e.exportInfo.Entries() {
- symbolName, ambientModuleName := key.SymbolName, key.AmbientModuleName
- if preferCapitalized && info[0].capitalizedSymbolName != "" {
- symbolName = info[0].capitalizedSymbolName
- }
- if matches(symbolName, info[0].targetFlags) {
- rehydrated := core.Map(info, func(info *CachedSymbolExportInfo) *SymbolExportInfo {
- return e.rehydrateCachedInfo(ch, info)
- })
- filtered := core.FilterIndex(rehydrated, func(r *SymbolExportInfo, i int, _ []*SymbolExportInfo) bool {
- return e.isNotShadowedByDeeperNodeModulesPackage(r, info[i].packageName)
- })
- if len(filtered) > 0 {
- if res := action(filtered, symbolName, ambientModuleName != "", key); res != nil {
- return res
- }
- }
- }
- }
- return nil
-}
-
-func (e *ExportInfoMap) isNotShadowedByDeeperNodeModulesPackage(info *SymbolExportInfo, packageName string) bool {
- if packageName == "" || info.moduleFileName == "" {
- return true
- }
- if e.globalTypingsCacheLocation != "" && strings.HasPrefix(info.moduleFileName, e.globalTypingsCacheLocation) {
- return true
- }
- packageDeepestNodeModulesPath, ok := e.packages[packageName]
- return !ok || strings.HasPrefix(info.moduleFileName, packageDeepestNodeModulesPath)
-}
-
-func (e *ExportInfoMap) rehydrateCachedInfo(ch *checker.Checker, info *CachedSymbolExportInfo) *SymbolExportInfo {
- if info.symbol != nil && info.moduleSymbol != nil {
- return &SymbolExportInfo{
- symbol: info.symbol,
- moduleSymbol: info.moduleSymbol,
- moduleFileName: info.moduleFileName,
- exportKind: info.exportKind,
- targetFlags: info.targetFlags,
- isFromPackageJson: info.isFromPackageJson,
- }
- }
- cached := e.symbols[info.id]
- cachedSymbol, cachedModuleSymbol := cached.symbol, cached.moduleSymbol
- if cachedSymbol != nil && cachedModuleSymbol != nil {
- return &SymbolExportInfo{
- symbol: cachedSymbol,
- moduleSymbol: cachedModuleSymbol,
- moduleFileName: info.moduleFileName,
- exportKind: info.exportKind,
- targetFlags: info.targetFlags,
- isFromPackageJson: info.isFromPackageJson,
- }
- }
-
- moduleSymbol := core.Coalesce(info.moduleSymbol, cachedModuleSymbol)
- if moduleSymbol == nil {
- if info.moduleFile != nil {
- moduleSymbol = ch.GetMergedSymbol(info.moduleFile.Symbol)
- } else {
- moduleSymbol = ch.TryFindAmbientModule(info.moduleName)
- }
- }
- if moduleSymbol == nil {
- panic(fmt.Sprintf("Could not find module symbol for %s in exportInfoMap", info.moduleName))
- }
- symbol := core.Coalesce(info.symbol, cachedSymbol)
- if symbol == nil {
- if info.exportKind == ExportKindExportEquals {
- symbol = ch.ResolveExternalModuleSymbol(moduleSymbol)
- } else {
- symbol = ch.TryGetMemberInModuleExportsAndProperties(info.symbolTableKey, moduleSymbol)
- }
- }
-
- if symbol == nil {
- panic(fmt.Sprintf("Could not find symbol '%s' by key '%s' in module %s", info.symbolName, info.symbolTableKey, moduleSymbol.Name))
- }
- e.symbols[info.id] = symbolExportEntry{symbol, moduleSymbol}
- return &SymbolExportInfo{
- symbol,
- moduleSymbol,
- info.moduleFileName,
- info.exportKind,
- info.targetFlags,
- info.isFromPackageJson,
- }
-}
-
-func getNamesForExportedSymbol(defaultExport *ast.Symbol, ch *checker.Checker, scriptTarget core.ScriptTarget) []string {
- var names []string
- forEachNameOfDefaultExport(defaultExport, ch, scriptTarget, func(name, capitalizedName string) string {
- if capitalizedName != "" {
- names = []string{name, capitalizedName}
- } else {
- names = []string{name}
- }
- return name
- })
- return names
-}
-
-type packageJsonImportFilter struct {
- allowsImportingAmbientModule func(moduleSymbol *ast.Symbol, host modulespecifiers.ModuleSpecifierGenerationHost) bool
- getSourceFileInfo func(sourceFile *ast.SourceFile, host modulespecifiers.ModuleSpecifierGenerationHost) packageJsonFilterResult
- /**
- * Use for a specific module specifier that has already been resolved.
- * Use `allowsImportingAmbientModule` or `allowsImportingSourceFile` to resolve
- * the best module specifier for a given module _and_ determine if it's importable.
- */
- allowsImportingSpecifier func(moduleSpecifier string) bool
-}
-
-type packageJsonFilterResult struct {
- importable bool
- packageName string
-}
-
-func (l *LanguageService) getImportCompletionAction(
- ctx context.Context,
- ch *checker.Checker,
- targetSymbol *ast.Symbol,
- moduleSymbol *ast.Symbol,
- sourceFile *ast.SourceFile,
- position int,
- exportMapKey lsproto.ExportInfoMapKey,
- 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.getExportInfoMap(ctx, ch, sourceFile, exportMapKey)
- if len(exportInfos) == 0 {
- panic("Some exportInfo should match the specified exportMapKey")
- }
-
- isValidTypeOnlyUseSite := ast.IsValidTypeOnlyAliasUseSite(astnav.GetTokenAtPosition(sourceFile, position))
- fix := l.getImportFixForSymbol(ch, sourceFile, exportInfos, position, ptrTo(isValidTypeOnlyUseSite))
- if fix == nil {
- lineAndChar := l.converters.PositionToLineAndCharacter(sourceFile, core.TextPos(position))
- panic(fmt.Sprintf("expected importFix at %s: (%v,%v)", sourceFile.FileName(), lineAndChar.Line, lineAndChar.Character))
- }
- return fix.moduleSpecifier, l.codeActionForFix(ctx, sourceFile, symbolName, fix /*includeSymbolNameInDescription*/, false)
-}
-
-func NewExportInfoMap(globalsTypingCacheLocation string) *ExportInfoMap {
- return &ExportInfoMap{
- packages: map[string]string{},
- symbols: map[int]symbolExportEntry{},
- exportInfo: collections.OrderedMap[lsproto.ExportInfoMapKey, []*CachedSymbolExportInfo]{},
- globalTypingsCacheLocation: globalsTypingCacheLocation,
- }
-}
-
-func (l *LanguageService) isImportable(
- fromFile *ast.SourceFile,
- toFile *ast.SourceFile,
- toModule *ast.Symbol,
- packageJsonFilter *packageJsonImportFilter,
- // moduleSpecifierResolutionHost ModuleSpecifierResolutionHost,
- // moduleSpecifierCache ModuleSpecifierCache,
-) bool {
- // !!! moduleSpecifierResolutionHost := l.GetModuleSpecifierResolutionHost()
- moduleSpecifierResolutionHost := l.GetProgram()
-
- // Ambient module
- if toFile == nil {
- moduleName := stringutil.StripQuotes(toModule.Name)
- if _, ok := core.NodeCoreModules()[moduleName]; ok {
- if useNodePrefix := lsutil.ShouldUseUriStyleNodeCoreModules(fromFile, l.GetProgram()); useNodePrefix {
- return useNodePrefix == strings.HasPrefix(moduleName, "node:")
- }
- }
- return packageJsonFilter == nil ||
- packageJsonFilter.allowsImportingAmbientModule(toModule, moduleSpecifierResolutionHost) ||
- fileContainsPackageImport(fromFile, moduleName)
- }
-
- if fromFile == toFile {
- return false
- }
-
- // !!! moduleSpecifierCache
- // cachedResult := moduleSpecifierCache?.get(fromFile.path, toFile.path, preferences, {})
- // if cachedResult?.isBlockedByPackageJsonDependencies != nil {
- // return !cachedResult.isBlockedByPackageJsonDependencies || cachedResult.packageName != nil && fileContainsPackageImport(fromFile, cachedResult.packageName)
- // }
-
- fromPath := fromFile.FileName()
- useCaseSensitiveFileNames := moduleSpecifierResolutionHost.UseCaseSensitiveFileNames()
- globalTypingsCache := l.GetProgram().GetGlobalTypingsCacheLocation()
- modulePaths := modulespecifiers.GetEachFileNameOfModule(
- fromPath,
- toFile.FileName(),
- moduleSpecifierResolutionHost,
- /*preferSymlinks*/ false,
- )
- hasImportablePath := false
- for _, module := range modulePaths {
- file := l.GetProgram().GetSourceFile(module.FileName)
-
- // Determine to import using toPath only if toPath is what we were looking at
- // or there doesnt exist the file in the program by the symlink
- if file == nil || file != toFile {
- continue
- }
-
- // If it's in a `node_modules` but is not reachable from here via a global import, don't bother.
- toNodeModules := tspath.ForEachAncestorDirectoryStoppingAtGlobalCache(
- globalTypingsCache,
- module.FileName,
- func(ancestor string) (string, bool) {
- if tspath.GetBaseFileName(ancestor) == "node_modules" {
- return ancestor, true
- } else {
- return "", false
- }
- },
- )
- toNodeModulesParent := ""
- if toNodeModules != "" {
- toNodeModulesParent = tspath.GetDirectoryPath(tspath.GetCanonicalFileName(toNodeModules, useCaseSensitiveFileNames))
- }
- hasImportablePath = toNodeModulesParent != "" ||
- strings.HasPrefix(tspath.GetCanonicalFileName(fromPath, useCaseSensitiveFileNames), toNodeModulesParent) ||
- (globalTypingsCache != "" && strings.HasPrefix(tspath.GetCanonicalFileName(globalTypingsCache, useCaseSensitiveFileNames), toNodeModulesParent))
- if hasImportablePath {
- break
- }
- }
-
- if packageJsonFilter != nil {
- if hasImportablePath {
- importInfo := packageJsonFilter.getSourceFileInfo(toFile, moduleSpecifierResolutionHost)
- // moduleSpecifierCache?.setBlockedByPackageJsonDependencies(fromFile.path, toFile.path, preferences, {}, importInfo?.packageName, !importInfo?.importable)
- return importInfo.importable || hasImportablePath && importInfo.packageName != "" && fileContainsPackageImport(fromFile, importInfo.packageName)
- }
- return false
- }
-
- return hasImportablePath
-}
-
-func fileContainsPackageImport(sourceFile *ast.SourceFile, packageName string) bool {
- return core.Some(sourceFile.Imports(), func(i *ast.Node) bool {
- text := i.Text()
- return text == packageName || strings.HasPrefix(text, packageName+"/")
- })
-}
-
-func isImportableSymbol(symbol *ast.Symbol, ch *checker.Checker) bool {
- return !ch.IsUndefinedSymbol(symbol) && !ch.IsUnknownSymbol(symbol) && !checker.IsKnownSymbol(symbol) && !checker.IsPrivateIdentifierSymbol(symbol)
-}
-
-func getDefaultLikeExportInfo(moduleSymbol *ast.Symbol, ch *checker.Checker) *ExportInfo {
- exportEquals := ch.ResolveExternalModuleSymbol(moduleSymbol)
- if exportEquals != moduleSymbol {
- if defaultExport := ch.TryGetMemberInModuleExports(ast.InternalSymbolNameDefault, exportEquals); defaultExport != nil {
- return &ExportInfo{defaultExport, ExportKindDefault}
- }
- return &ExportInfo{exportEquals, ExportKindExportEquals}
- }
- if defaultExport := ch.TryGetMemberInModuleExports(ast.InternalSymbolNameDefault, moduleSymbol); defaultExport != nil {
- return &ExportInfo{defaultExport, ExportKindDefault}
- }
- return nil
-}
-
-type importSpecifierResolverForCompletions struct {
- *ast.SourceFile // importingFile
- *lsutil.UserPreferences
- l *LanguageService
- filter *packageJsonImportFilter
-}
-
-func (r *importSpecifierResolverForCompletions) packageJsonImportFilter() *packageJsonImportFilter {
- if r.filter == nil {
- r.filter = r.l.createPackageJsonImportFilter(r.SourceFile)
- }
- return r.filter
-}
-
-func (i *importSpecifierResolverForCompletions) getModuleSpecifierForBestExportInfo(
- ch *checker.Checker,
- exportInfo []*SymbolExportInfo,
- position int,
- isValidTypeOnlyUseSite bool,
-) *ImportFix {
- // !!! caching
- // used in completions, usually calculated once per `getCompletionData` call
- packageJsonImportFilter := i.packageJsonImportFilter()
- _, fixes := i.l.getImportFixes(ch, exportInfo, ptrTo(i.l.converters.PositionToLineAndCharacter(i.SourceFile, core.TextPos(position))), ptrTo(isValidTypeOnlyUseSite), ptrTo(false), i.SourceFile, false /* fromCacheOnly */)
- return i.l.getBestFix(fixes, i.SourceFile, packageJsonImportFilter.allowsImportingSpecifier)
-}
-
-func (l *LanguageService) getImportFixForSymbol(
- ch *checker.Checker,
- sourceFile *ast.SourceFile,
- exportInfos []*SymbolExportInfo,
- position int,
- isValidTypeOnlySite *bool,
-) *ImportFix {
- if isValidTypeOnlySite == nil {
- isValidTypeOnlySite = ptrTo(ast.IsValidTypeOnlyAliasUseSite(astnav.GetTokenAtPosition(sourceFile, position)))
- }
- 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)
-}
-
-func (l *LanguageService) getBestFix(fixes []*ImportFix, sourceFile *ast.SourceFile, allowsImportingSpecifier func(moduleSpecifier string) bool) *ImportFix {
- if len(fixes) == 0 {
- return nil
- }
-
- // These will always be placed first if available, and are better than other kinds
- if fixes[0].kind == ImportFixKindUseNamespace || fixes[0].kind == ImportFixKindAddToExisting {
- return fixes[0]
- }
-
- best := fixes[0]
- for _, fix := range fixes {
- // Takes true branch of conditional if `fix` is better than `best`
- if l.compareModuleSpecifiers(
- fix,
- best,
- sourceFile,
- allowsImportingSpecifier,
- func(fileName string) tspath.Path {
- return tspath.ToPath(fileName, l.GetProgram().GetCurrentDirectory(), l.GetProgram().UseCaseSensitiveFileNames())
- },
- ) < 0 {
- best = fix
- }
- }
-
- return best
-}
-
-// returns `-1` if `a` is better than `b`
-//
-// note: this sorts in descending order of preference; different than convention in other cmp-like functions
-func (l *LanguageService) compareModuleSpecifiers(
- a *ImportFix, // !!! ImportFixWithModuleSpecifier
- b *ImportFix, // !!! ImportFixWithModuleSpecifier
- importingFile *ast.SourceFile, // | FutureSourceFile,
- allowsImportingSpecifier func(specifier string) bool,
- toPath func(fileName string) tspath.Path,
-) int {
- 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
-}
-
-func compareNodeCoreModuleSpecifiers(a, b string, importingFile *ast.SourceFile, program *compiler.Program) int {
- if strings.HasPrefix(a, "node:") && !strings.HasPrefix(b, "node:") {
- if lsutil.ShouldUseUriStyleNodeCoreModules(importingFile, program) {
- return -1
- }
- return 1
- }
- if strings.HasPrefix(b, "node:") && !strings.HasPrefix(a, "node:") {
- if lsutil.ShouldUseUriStyleNodeCoreModules(importingFile, program) {
- return 1
- }
- return -1
- }
- return 0
-}
-
-// This is a simple heuristic to try to avoid creating an import cycle with a barrel re-export.
-// E.g., do not `import { Foo } from ".."` when you could `import { Foo } from "../Foo"`.
-// This can produce false positives or negatives if re-exports cross into sibling directories
-// (e.g. `export * from "../whatever"`) or are not named "index".
-func isFixPossiblyReExportingImportingFile(fix *ImportFix, importingFilePath tspath.Path, toPath func(fileName string) tspath.Path) bool {
- if fix.isReExport != nil && *(fix.isReExport) &&
- fix.exportInfo != nil && fix.exportInfo.moduleFileName != "" && isIndexFileName(fix.exportInfo.moduleFileName) {
- reExportDir := toPath(tspath.GetDirectoryPath(fix.exportInfo.moduleFileName))
- return strings.HasPrefix(string(importingFilePath), string(reExportDir))
- }
- return false
-}
-
-func isIndexFileName(fileName string) bool {
- fileName = tspath.GetBaseFileName(fileName)
- if tspath.FileExtensionIsOneOf(fileName, []string{".js", ".jsx", ".d.ts", ".ts", ".tsx"}) {
- fileName = tspath.RemoveFileExtension(fileName)
- }
- return fileName == "index"
-}
-
-// returns `-1` if `a` is better than `b`
-func compareModuleSpecifierRelativity(a *ImportFix, b *ImportFix, preferences *lsutil.UserPreferences) int {
- switch preferences.ImportModuleSpecifierPreference {
- case modulespecifiers.ImportModuleSpecifierPreferenceNonRelative, modulespecifiers.ImportModuleSpecifierPreferenceProjectRelative:
- return core.CompareBooleans(a.moduleSpecifierKind == modulespecifiers.ResultKindRelative, b.moduleSpecifierKind == modulespecifiers.ResultKindRelative)
- }
- return 0
-}
-
-func (l *LanguageService) getImportFixes(
- ch *checker.Checker,
- exportInfos []*SymbolExportInfo, // | FutureSymbolExportInfo[],
- usagePosition *lsproto.Position,
- isValidTypeOnlyUseSite *bool,
- useRequire *bool,
- sourceFile *ast.SourceFile, // | FutureSourceFile,
- // importMap *importMap,
- fromCacheOnly bool,
-) (int, []*ImportFix) {
- // if importMap == nil { && !!! isFullSourceFile(sourceFile)
- importMap := createExistingImportMap(sourceFile, l.GetProgram(), ch)
- var existingImports []*FixAddToExistingImportInfo
- if importMap != nil {
- existingImports = core.FlatMap(exportInfos, importMap.getImportsForExportInfo)
- }
- var useNamespace []*ImportFix
- if usagePosition != nil {
- if namespaceImport := tryUseExistingNamespaceImport(existingImports, *usagePosition); namespaceImport != nil {
- useNamespace = append(useNamespace, namespaceImport)
- }
- }
- if addToExisting := tryAddToExistingImport(existingImports, isValidTypeOnlyUseSite, ch, l.GetProgram().Options()); addToExisting != nil {
- // Don't bother providing an action to add a new import if we can add to an existing one.
- return 0, append(useNamespace, addToExisting)
- }
-
- result := l.getFixesForAddImport(
- ch,
- exportInfos,
- existingImports,
- sourceFile,
- usagePosition,
- *isValidTypeOnlyUseSite,
- *useRequire,
- fromCacheOnly,
- )
- computedWithoutCacheCount := 0
- // if result.computedWithoutCacheCount != nil {
- // computedWithoutCacheCount = *result.computedWithoutCacheCount
- // }
- return computedWithoutCacheCount, append(useNamespace, result...)
-}
-
-func (l *LanguageService) createPackageJsonImportFilter(fromFile *ast.SourceFile) *packageJsonImportFilter {
- // !!! The program package.json cache may not have every relevant package.json.
- // This should eventually be integrated with the session.
- var packageJsons []*packagejson.PackageJson
- dir := tspath.GetDirectoryPath(fromFile.FileName())
- for {
- packageJsonDir := l.GetProgram().GetNearestAncestorDirectoryWithPackageJson(dir)
- if packageJsonDir == "" {
- break
- }
- if packageJson := l.GetProgram().GetPackageJsonInfo(tspath.CombinePaths(packageJsonDir, "package.json")).GetContents(); packageJson != nil && packageJson.Parseable {
- packageJsons = append(packageJsons, packageJson)
- }
- dir = tspath.GetDirectoryPath(packageJsonDir)
- if dir == packageJsonDir {
- break
- }
- }
-
- var usesNodeCoreModules *bool
- ambientModuleCache := map[*ast.Symbol]bool{}
- sourceFileCache := map[*ast.SourceFile]packageJsonFilterResult{}
-
- getNodeModuleRootSpecifier := func(fullSpecifier string) string {
- components := tspath.GetPathComponents(modulespecifiers.GetPackageNameFromTypesPackageName(fullSpecifier), "")[1:]
- // Scoped packages
- if strings.HasPrefix(components[0], "@") {
- return fmt.Sprintf("%s/%s", components[0], components[1])
- }
- return components[0]
- }
-
- moduleSpecifierIsCoveredByPackageJson := func(specifier string) bool {
- packageName := getNodeModuleRootSpecifier(specifier)
- for _, packageJson := range packageJsons {
- if packageJson.HasDependency(packageName) || packageJson.HasDependency(module.GetTypesPackageName(packageName)) {
- return true
- }
- }
- return false
- }
-
- isAllowedCoreNodeModulesImport := func(moduleSpecifier string) bool {
- // If we're in JavaScript, it can be difficult to tell whether the user wants to import
- // from Node core modules or not. We can start by seeing if the user is actually using
- // any node core modules, as opposed to simply having @types/node accidentally as a
- // dependency of a dependency.
- if /*isFullSourceFile(fromFile) &&*/ ast.IsSourceFileJS(fromFile) && core.NodeCoreModules()[moduleSpecifier] {
- if usesNodeCoreModules == nil {
- usesNodeCoreModules = ptrTo(consumesNodeCoreModules(fromFile))
- }
- if *usesNodeCoreModules {
- return true
- }
- }
- return false
- }
-
- getNodeModulesPackageNameFromFileName := func(importedFileName string, moduleSpecifierResolutionHost modulespecifiers.ModuleSpecifierGenerationHost) *string {
- if !strings.Contains(importedFileName, "node_modules") {
- return nil
- }
- specifier := modulespecifiers.GetNodeModulesPackageName(
- l.program.Options(),
- fromFile,
- importedFileName,
- moduleSpecifierResolutionHost,
- l.UserPreferences().ModuleSpecifierPreferences(),
- modulespecifiers.ModuleSpecifierOptions{},
- )
- if specifier == "" {
- return nil
- }
- // Paths here are not node_modules, so we don't care about them;
- // returning anything will trigger a lookup in package.json.
- if !tspath.PathIsRelative(specifier) && !tspath.IsRootedDiskPath(specifier) {
- return ptrTo(getNodeModuleRootSpecifier(specifier))
- }
- return nil
- }
-
- allowsImportingAmbientModule := func(moduleSymbol *ast.Symbol, moduleSpecifierResolutionHost modulespecifiers.ModuleSpecifierGenerationHost) bool {
- if len(packageJsons) == 0 || moduleSymbol.ValueDeclaration == nil {
- return true
- }
-
- if cached, ok := ambientModuleCache[moduleSymbol]; ok {
- return cached
- }
-
- declaredModuleSpecifier := stringutil.StripQuotes(moduleSymbol.Name)
- if isAllowedCoreNodeModulesImport(declaredModuleSpecifier) {
- ambientModuleCache[moduleSymbol] = true
- return true
- }
-
- declaringSourceFile := ast.GetSourceFileOfNode(moduleSymbol.ValueDeclaration)
- declaringNodeModuleName := getNodeModulesPackageNameFromFileName(declaringSourceFile.FileName(), moduleSpecifierResolutionHost)
- if declaringNodeModuleName == nil {
- ambientModuleCache[moduleSymbol] = true
- return true
- }
-
- result := moduleSpecifierIsCoveredByPackageJson(*declaringNodeModuleName)
- if !result {
- result = moduleSpecifierIsCoveredByPackageJson(declaredModuleSpecifier)
- }
- ambientModuleCache[moduleSymbol] = result
- return result
- }
-
- getSourceFileInfo := func(sourceFile *ast.SourceFile, moduleSpecifierResolutionHost modulespecifiers.ModuleSpecifierGenerationHost) packageJsonFilterResult {
- result := packageJsonFilterResult{
- importable: true,
- packageName: "",
- }
-
- if len(packageJsons) == 0 {
- return result
- }
- if cached, ok := sourceFileCache[sourceFile]; ok {
- return cached
- }
-
- if packageName := getNodeModulesPackageNameFromFileName(sourceFile.FileName(), moduleSpecifierResolutionHost); packageName != nil {
- result = packageJsonFilterResult{importable: moduleSpecifierIsCoveredByPackageJson(*packageName), packageName: *packageName}
- }
- sourceFileCache[sourceFile] = result
- return result
- }
-
- allowsImportingSpecifier := func(moduleSpecifier string) bool {
- if len(packageJsons) == 0 || isAllowedCoreNodeModulesImport(moduleSpecifier) {
- return true
- }
- if tspath.PathIsRelative(moduleSpecifier) || tspath.IsRootedDiskPath(moduleSpecifier) {
- return true
- }
- return moduleSpecifierIsCoveredByPackageJson(moduleSpecifier)
- }
-
- return &packageJsonImportFilter{
- allowsImportingAmbientModule,
- getSourceFileInfo,
- allowsImportingSpecifier,
- }
-}
-
-func consumesNodeCoreModules(sourceFile *ast.SourceFile) bool {
- for _, importStatement := range sourceFile.Imports() {
- if core.NodeCoreModules()[importStatement.Text()] {
- return true
- }
- }
- return false
-}
-
-func createExistingImportMap(importingFile *ast.SourceFile, program *compiler.Program, ch *checker.Checker) *importMap {
- m := collections.MultiMap[ast.SymbolId, *ast.Statement]{}
- for _, moduleSpecifier := range importingFile.Imports() {
- i := tryGetImportFromModuleSpecifier(moduleSpecifier)
- if i == nil {
- panic("error: did not expect node kind " + moduleSpecifier.Kind.String())
- } else if ast.IsVariableDeclarationInitializedToRequire(i.Parent) {
- if moduleSymbol := ch.ResolveExternalModuleName(moduleSpecifier); moduleSymbol != nil {
- m.Add(ast.GetSymbolId(moduleSymbol), i.Parent)
- }
- } else if i.Kind == ast.KindImportDeclaration || i.Kind == ast.KindImportEqualsDeclaration || i.Kind == ast.KindJSDocImportTag {
- if moduleSymbol := ch.GetSymbolAtLocation(moduleSpecifier); moduleSymbol != nil {
- m.Add(ast.GetSymbolId(moduleSymbol), i)
- }
- }
- }
- return &importMap{importingFile: importingFile, program: program, m: m}
-}
-
-type importMap struct {
- importingFile *ast.SourceFile
- program *compiler.Program
- m collections.MultiMap[ast.SymbolId, *ast.Statement] // !!! anyImportOrRequire
-}
-
-func (i *importMap) getImportsForExportInfo(info *SymbolExportInfo /* | FutureSymbolExportInfo*/) []*FixAddToExistingImportInfo {
- matchingDeclarations := i.m.Get(ast.GetSymbolId(info.moduleSymbol))
- if len(matchingDeclarations) == 0 {
- return nil
- }
-
- // Can't use an es6 import for a type in JS.
- if ast.IsSourceFileJS(i.importingFile) && info.targetFlags&ast.SymbolFlagsValue == 0 && !core.Every(matchingDeclarations, ast.IsJSDocImportTag) {
- return nil
- }
-
- importKind := getImportKind(i.importingFile, info.exportKind, i.program, false)
- return core.Map(matchingDeclarations, func(d *ast.Statement) *FixAddToExistingImportInfo {
- return &FixAddToExistingImportInfo{declaration: d, importKind: importKind, symbol: info.symbol, targetFlags: info.targetFlags}
- })
-}
-
-func tryUseExistingNamespaceImport(existingImports []*FixAddToExistingImportInfo, position lsproto.Position) *ImportFix {
- // It is possible that multiple import statements with the same specifier exist in the file.
- // e.g.
- //
- // import * as ns from "foo";
- // import { member1, member2 } from "foo";
- //
- // member3/**/ <-- cusor here
- //
- // in this case we should provie 2 actions:
- // 1. change "member3" to "ns.member3"
- // 2. add "member3" to the second import statement's import list
- // and it is up to the user to decide which one fits best.
- for _, existingImport := range existingImports {
- if existingImport.importKind != ImportKindNamed {
- continue
- }
- namespacePrefix := getNamespaceLikeImportText(existingImport.declaration)
- moduleSpecifier := checker.TryGetModuleSpecifierFromDeclaration(existingImport.declaration)
- if namespacePrefix != "" && moduleSpecifier != nil && moduleSpecifier.Text() != "" {
- return getUseNamespaceImport(
- moduleSpecifier.Text(),
- modulespecifiers.ResultKindNone,
- namespacePrefix,
- position,
- )
- }
- }
- 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
-
- typeOnly := false
- if isValidTypeOnlyUseSite != nil {
- typeOnly = *isValidTypeOnlyUseSite
- }
-
- for _, existingImport := range existingImports {
- fix := existingImport.getAddToExistingImportFix(typeOnly, ch, compilerOptions)
- if fix == nil {
- continue
- }
- isTypeOnly := ast.IsTypeOnlyImportDeclaration(fix.importClauseOrBindingPattern)
- if (fix.addAsTypeOnly != AddAsTypeOnlyNotAllowed && isTypeOnly) || (fix.addAsTypeOnly == AddAsTypeOnlyNotAllowed && !isTypeOnly) {
- // Give preference to putting types in existing type-only imports and avoiding conversions
- // of import statements to/from type-only.
- return fix
- }
- if best == nil {
- best = fix
- }
- }
- return best
-}
-
-func (info *FixAddToExistingImportInfo) getAddToExistingImportFix(isValidTypeOnlyUseSite bool, ch *checker.Checker, compilerOptions *core.CompilerOptions) *ImportFix {
- if info.importKind == ImportKindCommonJS || info.importKind == ImportKindNamespace || info.declaration.Kind == ast.KindImportEqualsDeclaration {
- // These kinds of imports are not combinable with anything
- return nil
- }
-
- if info.declaration.Kind == ast.KindVariableDeclaration {
- if (info.importKind == ImportKindNamed || info.importKind == ImportKindDefault) && info.declaration.Name().Kind == ast.KindObjectBindingPattern {
- return getAddToExistingImport(
- info.declaration.Name(),
- info.importKind,
- info.declaration.Initializer().Arguments()[0].Text(),
- modulespecifiers.ResultKindNone,
- AddAsTypeOnlyNotAllowed,
- )
- }
- return nil
- }
-
- importClause := info.declaration.ImportClause()
- if importClause == nil || !ast.IsStringLiteralLike(info.declaration.ModuleSpecifier()) {
- return nil
- }
- 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) {
- return nil
- }
-
- // N.B. we don't have to figure out whether to use the main program checker
- // or the AutoImportProvider checker because we're adding to an existing import; the existence of
- // the import guarantees the symbol came from the main program.
- addAsTypeOnly := getAddAsTypeOnly(isValidTypeOnlyUseSite, info.symbol, info.targetFlags, ch, compilerOptions)
-
- if info.importKind == ImportKindDefault && (importClause.Name() != nil || // Cannot add a default import to a declaration that already has one
- addAsTypeOnly == AddAsTypeOnlyRequired && namedBindings != nil) { // Cannot add a default import as type-only if the import already has named bindings
-
- return nil
- }
-
- // Cannot add a named import to a declaration that has a namespace import
- if info.importKind == ImportKindNamed && namedBindings != nil && namedBindings.Kind == ast.KindNamespaceImport {
- return nil
- }
-
- return getAddToExistingImport(
- importClause.AsNode(),
- info.importKind,
- info.declaration.ModuleSpecifier().Text(),
- modulespecifiers.ResultKindNone,
- addAsTypeOnly,
- )
-}
-
-func (l *LanguageService) getFixesForAddImport(
- ch *checker.Checker,
- exportInfos []*SymbolExportInfo, // !!! | readonly FutureSymbolExportInfo[],
- existingImports []*FixAddToExistingImportInfo,
- sourceFile *ast.SourceFile, // !!! | FutureSourceFile,
- usagePosition *lsproto.Position,
- isValidTypeOnlyUseSite bool,
- useRequire bool,
- fromCacheOnly bool,
-) []*ImportFix {
- // tries to create a new import statement using an existing import specifier
- var importWithExistingSpecifier *ImportFix
-
- for _, existingImport := range existingImports {
- if fix := existingImport.getNewImportFromExistingSpecifier(isValidTypeOnlyUseSite, useRequire, ch, l.GetProgram().Options()); fix != nil {
- importWithExistingSpecifier = fix
- break
- }
- }
-
- if importWithExistingSpecifier != nil {
- return []*ImportFix{importWithExistingSpecifier}
- }
-
- return l.getNewImportFixes(ch, sourceFile, usagePosition, isValidTypeOnlyUseSite, useRequire, exportInfos, fromCacheOnly)
-}
-
-func (l *LanguageService) getNewImportFixes(
- ch *checker.Checker,
- sourceFile *ast.SourceFile, // | FutureSourceFile,
- usagePosition *lsproto.Position,
- isValidTypeOnlyUseSite bool,
- useRequire bool,
- exportInfos []*SymbolExportInfo, // !!! (SymbolExportInfo | FutureSymbolExportInfo)[],
- fromCacheOnly bool,
-) []*ImportFix /* FixAddNewImport | FixAddJsdocTypeImport */ {
- isJs := tspath.HasJSFileExtension(sourceFile.FileName())
- compilerOptions := l.GetProgram().Options()
- // !!! packagejsonAutoimportProvider
- // getChecker := createGetChecker(program, host)// memoized typechecker based on `isFromPackageJson` bool
-
- getModuleSpecifiers := func(moduleSymbol *ast.Symbol, checker *checker.Checker) ([]string, modulespecifiers.ResultKind) {
- return modulespecifiers.GetModuleSpecifiersWithInfo(moduleSymbol, checker, compilerOptions, sourceFile, l.GetProgram(), l.UserPreferences().ModuleSpecifierPreferences(), modulespecifiers.ModuleSpecifierOptions{}, true /*forAutoImport*/)
- }
- // fromCacheOnly
- // ? (exportInfo: SymbolExportInfo | FutureSymbolExportInfo) => moduleSpecifiers.tryGetModuleSpecifiersFromCache(exportInfo.moduleSymbol, sourceFile, moduleSpecifierResolutionHost, preferences)
- // : (exportInfo: SymbolExportInfo | FutureSymbolExportInfo, checker: TypeChecker) => moduleSpecifiers.getModuleSpecifiersWithCacheInfo(exportInfo.moduleSymbol, checker, compilerOptions, sourceFile, moduleSpecifierResolutionHost, preferences, /*options*/ nil, /*forAutoImport*/ true);
-
- // computedWithoutCacheCount = 0;
- var fixes []*ImportFix /* FixAddNewImport | FixAddJsdocTypeImport */
- for i, exportInfo := range exportInfos {
- moduleSpecifiers, moduleSpecifierKind := getModuleSpecifiers(exportInfo.moduleSymbol, ch)
- importedSymbolHasValueMeaning := exportInfo.targetFlags&ast.SymbolFlagsValue != 0
- addAsTypeOnly := getAddAsTypeOnly(isValidTypeOnlyUseSite, exportInfo.symbol, exportInfo.targetFlags, ch, compilerOptions)
- // computedWithoutCacheCount += computedWithoutCache ? 1 : 0;
- for _, moduleSpecifier := range moduleSpecifiers {
- if modulespecifiers.ContainsNodeModules(moduleSpecifier) {
- continue
- }
- if !importedSymbolHasValueMeaning && isJs && usagePosition != nil {
- // `position` should only be undefined at a missing jsx namespace, in which case we shouldn't be looking for pure types.
- fixes = append(fixes, getAddJsdocTypeImport(
- moduleSpecifier,
- moduleSpecifierKind,
- usagePosition,
- exportInfo,
- ptrTo(i > 0)), // isReExport
- )
- continue
- }
- importKind := getImportKind(sourceFile, exportInfo.exportKind, l.GetProgram(), false)
- var qualification *Qualification
- if usagePosition != nil && importKind == ImportKindCommonJS && exportInfo.exportKind == ExportKindNamed {
- // Compiler options are restricting our import options to a require, but we need to access
- // a named export or property of the exporting module. We need to import the entire module
- // and insert a property access, e.g. `writeFile` becomes
- //
- // import fs = require("fs"); // or const in JS
- // fs.writeFile
- exportEquals := ch.ResolveExternalModuleSymbol(exportInfo.moduleSymbol)
- var namespacePrefix *string
- if exportEquals != exportInfo.moduleSymbol {
- namespacePrefix = strPtrTo(forEachNameOfDefaultExport(
- exportEquals,
- ch,
- compilerOptions.GetEmitScriptTarget(),
- func(a, _ string) string { return a }, // Identity
- ))
- }
- if namespacePrefix == nil {
- namespacePrefix = ptrTo(moduleSymbolToValidIdentifier(
- exportInfo.moduleSymbol,
- compilerOptions.GetEmitScriptTarget(),
- /*forceCapitalize*/ false,
- ))
- }
- qualification = &Qualification{*usagePosition, *namespacePrefix}
- }
- fixes = append(fixes, getNewAddNewImport(
- moduleSpecifier,
- moduleSpecifierKind,
- importKind,
- useRequire,
- addAsTypeOnly,
- exportInfo,
- ptrTo(i > 0), // isReExport
- qualification,
- ))
- }
- }
-
- return fixes
-}
-
-func getAddAsTypeOnly(
- isValidTypeOnlyUseSite bool,
- symbol *ast.Symbol,
- targetFlags ast.SymbolFlags,
- ch *checker.Checker,
- compilerOptions *core.CompilerOptions,
-) AddAsTypeOnly {
- if !isValidTypeOnlyUseSite {
- // Can't use a type-only import if the usage is an emitting position
- return AddAsTypeOnlyNotAllowed
- }
- if symbol != nil && compilerOptions.VerbatimModuleSyntax.IsTrue() &&
- (targetFlags&ast.SymbolFlagsValue == 0 || ch.GetTypeOnlyAliasDeclaration(symbol) != nil) {
- // A type-only import is required for this symbol if under these settings if the symbol will
- // be erased, which will happen if the target symbol is purely a type or if it was exported/imported
- // as type-only already somewhere between this import and the target.
- return AddAsTypeOnlyRequired
- }
- return AddAsTypeOnlyAllowed
-}
-
-func shouldUseRequire(
- sourceFile *ast.SourceFile, // !!! | FutureSourceFile
- program *compiler.Program,
-) bool {
- // 1. TypeScript files don't use require variable declarations
- if !tspath.HasJSFileExtension(sourceFile.FileName()) {
- return false
- }
-
- // 2. If the current source file is unambiguously CJS or ESM, go with that
- switch {
- case sourceFile.CommonJSModuleIndicator != nil && sourceFile.ExternalModuleIndicator == nil:
- return true
- case sourceFile.ExternalModuleIndicator != nil && sourceFile.CommonJSModuleIndicator == nil:
- return false
- }
-
- // 3. If there's a tsconfig/jsconfig, use its module setting
- if program.Options().ConfigFilePath != "" {
- return program.Options().GetEmitModuleKind() < core.ModuleKindES2015
- }
-
- // 4. In --module nodenext, assume we're not emitting JS -> JS, so use
- // whatever syntax Node expects based on the detected module kind
- // TODO: consider removing `impliedNodeFormatForEmit`
- switch program.GetImpliedNodeFormatForEmit(sourceFile) {
- case core.ModuleKindCommonJS:
- return true
- case core.ModuleKindESNext:
- return false
- }
-
- // 5. Match the first other JS file in the program that's unambiguously CJS or ESM
- for _, otherFile := range program.GetSourceFiles() {
- switch {
- case otherFile == sourceFile, !ast.IsSourceFileJS(otherFile), program.IsSourceFileFromExternalLibrary(otherFile):
- continue
- case otherFile.CommonJSModuleIndicator != nil && otherFile.ExternalModuleIndicator == nil:
- return true
- case otherFile.ExternalModuleIndicator != nil && otherFile.CommonJSModuleIndicator == nil:
- return false
- }
- }
-
- // 6. Literally nothing to go on
- return true
-}
-
-/**
- * @param forceImportKeyword Indicates that the user has already typed `import`, so the result must start with `import`.
- * (In other words, do not allow `const x = require("...")` for JS files.)
- *
- * @internal
- */
-func getImportKind(importingFile *ast.SourceFile /*| FutureSourceFile*/, exportKind ExportKind, program *compiler.Program, forceImportKeyword bool) ImportKind {
- if program.Options().VerbatimModuleSyntax.IsTrue() && program.GetEmitModuleFormatOfFile(importingFile) == core.ModuleKindCommonJS {
- // TODO: if the exporting file is ESM under nodenext, or `forceImport` is given in a JS file, this is impossible
- return ImportKindCommonJS
- }
- switch exportKind {
- case ExportKindNamed:
- return ImportKindNamed
- case ExportKindDefault:
- return ImportKindDefault
- case ExportKindExportEquals:
- return getExportEqualsImportKind(importingFile, program.Options(), forceImportKeyword)
- case ExportKindUMD:
- return getUmdImportKind(importingFile, program, forceImportKeyword)
- case ExportKindModule:
- return ImportKindNamespace
- }
- panic("unexpected export kind: " + exportKind.String())
-}
-
-func getExportEqualsImportKind(importingFile *ast.SourceFile /* | FutureSourceFile*/, compilerOptions *core.CompilerOptions, forceImportKeyword bool) ImportKind {
- allowSyntheticDefaults := compilerOptions.GetAllowSyntheticDefaultImports()
- isJS := tspath.HasJSFileExtension(importingFile.FileName())
- // 1. 'import =' will not work in es2015+ TS files, so the decision is between a default
- // and a namespace import, based on allowSyntheticDefaultImports/esModuleInterop.
- if !isJS && compilerOptions.GetEmitModuleKind() >= core.ModuleKindES2015 {
- if allowSyntheticDefaults {
- return ImportKindDefault
- }
- return ImportKindNamespace
- }
- // 2. 'import =' will not work in JavaScript, so the decision is between a default import,
- // a namespace import, and const/require.
- if isJS {
- if importingFile.ExternalModuleIndicator != nil || forceImportKeyword {
- if allowSyntheticDefaults {
- return ImportKindDefault
- }
- return ImportKindNamespace
- }
- return ImportKindCommonJS
- }
- // 3. At this point the most correct choice is probably 'import =', but people
- // really hate that, so look to see if the importing file has any precedent
- // on how to handle it.
- for _, statement := range importingFile.Statements.Nodes {
- // `import foo` parses as an ImportEqualsDeclaration even though it could be an ImportDeclaration
- if ast.IsImportEqualsDeclaration(statement) && !ast.NodeIsMissing(statement.AsImportEqualsDeclaration().ModuleReference) {
- return ImportKindCommonJS
- }
- }
- // 4. We have no precedent to go on, so just use a default import if
- // allowSyntheticDefaultImports/esModuleInterop is enabled.
- if allowSyntheticDefaults {
- return ImportKindDefault
- }
- return ImportKindCommonJS
-}
-
-func getUmdImportKind(importingFile *ast.SourceFile /* | FutureSourceFile */, program *compiler.Program, forceImportKeyword bool) ImportKind {
- // Import a synthetic `default` if enabled.
- if program.Options().GetAllowSyntheticDefaultImports() {
- return ImportKindDefault
- }
-
- // When a synthetic `default` is unavailable, use `import..require` if the module kind supports it.
- moduleKind := program.Options().GetEmitModuleKind()
- switch moduleKind {
- case core.ModuleKindCommonJS:
- if tspath.HasJSFileExtension(importingFile.FileName()) && (importingFile.ExternalModuleIndicator != nil || forceImportKeyword) {
- return ImportKindNamespace
- }
- return ImportKindCommonJS
- case core.ModuleKindES2015, core.ModuleKindES2020, core.ModuleKindES2022, core.ModuleKindESNext, core.ModuleKindNone, core.ModuleKindPreserve:
- // Fall back to the `import * as ns` style import.
- return ImportKindNamespace
- case core.ModuleKindNode16, core.ModuleKindNode18, core.ModuleKindNode20, core.ModuleKindNodeNext:
- if program.GetImpliedNodeFormatForEmit(importingFile) == core.ModuleKindESNext {
- return ImportKindNamespace
- }
- return ImportKindCommonJS
- default:
- panic(`Unexpected moduleKind :` + moduleKind.String())
- }
-}
-
-/**
- * May call `cb` multiple times with the same name.
- * Terminates when `cb` returns a truthy value.
- */
-func forEachNameOfDefaultExport(defaultExport *ast.Symbol, ch *checker.Checker, scriptTarget core.ScriptTarget, cb func(name string, capitalizedName string) string) string {
- var chain []*ast.Symbol
- current := defaultExport
- seen := collections.Set[*ast.Symbol]{}
-
- for current != nil {
- // The predecessor to this function also looked for a name on the `localSymbol`
- // of default exports, but I think `getDefaultLikeExportNameFromDeclaration`
- // accomplishes the same thing via syntax - no tests failed when I removed it.
- fromDeclaration := getDefaultLikeExportNameFromDeclaration(current)
- if fromDeclaration != "" {
- final := cb(fromDeclaration, "")
- if final != "" {
- return final
- }
- }
-
- if current.Name != ast.InternalSymbolNameDefault && current.Name != ast.InternalSymbolNameExportEquals {
- if final := cb(current.Name, ""); final != "" {
- return final
- }
- }
-
- chain = append(chain, current)
- if !seen.AddIfAbsent(current) {
- break
- }
- if current.Flags&ast.SymbolFlagsAlias != 0 {
- current = ch.GetImmediateAliasedSymbol(current)
- } else {
- current = nil
- }
- }
-
- for _, symbol := range chain {
- if symbol.Parent != nil && checker.IsExternalModuleSymbol(symbol.Parent) {
- final := cb(
- moduleSymbolToValidIdentifier(symbol.Parent, scriptTarget /*forceCapitalize*/, false),
- moduleSymbolToValidIdentifier(symbol.Parent, scriptTarget /*forceCapitalize*/, true),
- )
- if final != "" {
- return final
- }
- }
- }
- return ""
-}
-
-func getDefaultLikeExportNameFromDeclaration(symbol *ast.Symbol) string {
- for _, d := range symbol.Declarations {
- // "export default" in this case. See `ExportAssignment`for more details.
- if ast.IsExportAssignment(d) {
- if innerExpression := ast.SkipOuterExpressions(d.Expression(), ast.OEKAll); ast.IsIdentifier(innerExpression) {
- return innerExpression.Text()
- }
- continue
- }
- // "export { ~ as default }"
- if ast.IsExportSpecifier(d) && d.Symbol().Flags == ast.SymbolFlagsAlias && d.PropertyName() != nil {
- if d.PropertyName().Kind == ast.KindIdentifier {
- return d.PropertyName().Text()
- }
- continue
- }
- // GH#52694
- if name := ast.GetNameOfDeclaration(d); name != nil && name.Kind == ast.KindIdentifier {
- return name.Text()
- }
- if symbol.Parent != nil && !checker.IsExternalModuleSymbol(symbol.Parent) {
- return symbol.Parent.Name
- }
- }
- return ""
-}
-
-func forEachExternalModuleToImportFrom(
- ch *checker.Checker,
- program *compiler.Program,
- preferences *lsutil.UserPreferences,
- // useAutoImportProvider bool,
- cb func(module *ast.Symbol, moduleFile *ast.SourceFile, checker *checker.Checker, isFromPackageJson bool),
-) {
- var excludePatterns []*regexp2.Regexp
- if preferences.AutoImportFileExcludePatterns != nil {
- excludePatterns = getIsExcludedPatterns(preferences, program.UseCaseSensitiveFileNames())
- }
-
- forEachExternalModule(
- ch,
- program.GetSourceFiles(),
- excludePatterns,
- func(module *ast.Symbol, file *ast.SourceFile) {
- cb(module, file, ch, false)
- },
- )
-
- // !!! autoImportProvider
- // if autoImportProvider := useAutoImportProvider && l.getPackageJsonAutoImportProvider(); autoImportProvider != nil {
- // // start := timestamp();
- // forEachExternalModule(autoImportProvider.getTypeChecker(), autoImportProvider.getSourceFiles(), excludePatterns, host, func (module *ast.Symbol, file *ast.SourceFile) {
- // if (file && !program.getSourceFile(file.FileName()) || !file && !checker.resolveName(module.Name, /*location*/ nil, ast.SymbolFlagsModule, /*excludeGlobals*/ false)) {
- // // The AutoImportProvider filters files already in the main program out of its *root* files,
- // // but non-root files can still be present in both programs, and already in the export info map
- // // at this point. This doesn't create any incorrect behavior, but is a waste of time and memory,
- // // so we filter them out here.
- // cb(module, file, autoImportProvide.checker, /*isFromPackageJson*/ true);
- // }
- // });
- // // host.log?.(`forEachExternalModuleToImportFrom autoImportProvider: ${timestamp() - start}`);
- // }
-}
-
-func getIsExcludedPatterns(preferences *lsutil.UserPreferences, useCaseSensitiveFileNames bool) []*regexp2.Regexp {
- if preferences.AutoImportFileExcludePatterns == nil {
- return nil
- }
- var patterns []*regexp2.Regexp
- for _, spec := range preferences.AutoImportFileExcludePatterns {
- pattern := vfs.GetSubPatternFromSpec(spec, "", vfs.UsageExclude, vfs.WildcardMatcher{})
- if pattern != "" {
- if re := vfs.GetRegexFromPattern(pattern, useCaseSensitiveFileNames); re != nil {
- patterns = append(patterns, re)
- }
- }
- }
- return patterns
-}
-
-func forEachExternalModule(
- ch *checker.Checker,
- allSourceFiles []*ast.SourceFile,
- excludePatterns []*regexp2.Regexp,
- cb func(moduleSymbol *ast.Symbol, sourceFile *ast.SourceFile),
-) {
- var isExcluded func(*ast.SourceFile) bool = func(_ *ast.SourceFile) bool { return false }
- if excludePatterns != nil {
- isExcluded = getIsExcluded(excludePatterns)
- }
-
- for _, ambient := range ch.GetAmbientModules() {
- if !strings.Contains(ambient.Name, "*") && !(excludePatterns != nil && core.Every(ambient.Declarations, func(d *ast.Node) bool {
- return isExcluded(ast.GetSourceFileOfNode(d))
- })) {
- cb(ambient, nil /*sourceFile*/)
- }
- }
- for _, sourceFile := range allSourceFiles {
- if ast.IsExternalOrCommonJSModule(sourceFile) && !isExcluded(sourceFile) {
- cb(ch.GetMergedSymbol(sourceFile.Symbol), sourceFile)
- }
- }
-}
-
-func getIsExcluded(excludePatterns []*regexp2.Regexp) func(sourceFile *ast.SourceFile) bool {
- // !!! SymlinkCache
- // const realpathsWithSymlinks = host.getSymlinkCache?.().getSymlinkedDirectoriesByRealpath();
- return func(sourceFile *ast.SourceFile) bool {
- fileName := sourceFile.FileName()
- for _, p := range excludePatterns {
- if matched, _ := p.MatchString(fileName); matched {
- return true
- }
- }
- // !! SymlinkCache
- // if (realpathsWithSymlinks?.size && pathContainsNodeModules(fileName)) {
- // let dir = getDirectoryPath(fileName);
- // return forEachAncestorDirectoryStoppingAtGlobalCache(
- // host,
- // getDirectoryPath(path),
- // dirPath => {
- // const symlinks = realpathsWithSymlinks.get(ensureTrailingDirectorySeparator(dirPath));
- // if (symlinks) {
- // return symlinks.some(s => excludePatterns.some(p => p.test(fileName.replace(dir, s))));
- // }
- // dir = getDirectoryPath(dir);
- // },
- // ) ?? false;
- // }
- return false
- }
-}
-
-// ======================== generate code actions =======================
-
-func (l *LanguageService) codeActionForFix(
- ctx context.Context,
- sourceFile *ast.SourceFile,
- symbolName string,
- fix *ImportFix,
- includeSymbolNameInDescription bool,
-) codeAction {
- tracker := change.NewTracker(ctx, l.GetProgram().Options(), l.FormatOptions(), l.converters) // !!! changetracker.with
- diag := l.codeActionForFixWorker(ctx, tracker, sourceFile, symbolName, fix, includeSymbolNameInDescription)
- changes := tracker.GetChanges()[sourceFile.FileName()]
- return codeAction{description: diag, changes: changes}
-}
-
-func (l *LanguageService) codeActionForFixWorker(
- ctx context.Context,
- changeTracker *change.Tracker,
- sourceFile *ast.SourceFile,
- symbolName string,
- fix *ImportFix,
- includeSymbolNameInDescription bool,
-) string {
- locale := locale.FromContext(ctx)
-
- switch fix.kind {
- case ImportFixKindUseNamespace:
- addNamespaceQualifier(changeTracker, sourceFile, fix.qualification())
- return diagnostics.Change_0_to_1.Localize(locale, symbolName, fmt.Sprintf("%s.%s", *fix.namespacePrefix, symbolName))
- case ImportFixKindJsdocTypeImport:
- if fix.usagePosition == nil {
- return ""
- }
- 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.Change_0_to_1.Localize(locale, 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,
- defaultImport,
- namedImports,
- )
- moduleSpecifierWithoutQuotes := stringutil.StripQuotes(fix.moduleSpecifier)
- if includeSymbolNameInDescription {
- return diagnostics.Import_0_from_1.Localize(locale, symbolName, moduleSpecifierWithoutQuotes)
- }
- return diagnostics.Update_import_from_0.Localize(locale, moduleSpecifierWithoutQuotes)
- case ImportFixKindAddNew:
- var declarations []*ast.Statement
- 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}
- if qualification != nil && qualification.namespacePrefix != "" {
- namespaceLikeImport.name = qualification.namespacePrefix
- }
- }
-
- if fix.useRequire {
- declarations = getNewRequires(changeTracker, fix.moduleSpecifier, getQuotePreference(sourceFile, l.UserPreferences()), defaultImport, namedImports, namespaceLikeImport, l.GetProgram().Options())
- } else {
- declarations = l.getNewImports(changeTracker, fix.moduleSpecifier, getQuotePreference(sourceFile, l.UserPreferences()), defaultImport, namedImports, namespaceLikeImport, l.GetProgram().Options())
- }
-
- l.insertImports(
- changeTracker,
- sourceFile,
- declarations,
- /*blankLineBetween*/ true,
- )
- if qualification != nil {
- addNamespaceQualifier(changeTracker, sourceFile, qualification)
- }
- if includeSymbolNameInDescription {
- return diagnostics.Import_0_from_1.Localize(locale, symbolName, fix.moduleSpecifier)
- }
- return diagnostics.Add_import_from_0.Localize(locale, fix.moduleSpecifier)
- case ImportFixKindPromoteTypeOnly:
- promotedDeclaration := promoteFromTypeOnly(changeTracker, fix.typeOnlyAliasDeclaration, l.GetProgram(), sourceFile, l)
- if promotedDeclaration.Kind == ast.KindImportSpecifier {
- moduleSpec := getModuleSpecifierText(promotedDeclaration.Parent.Parent)
- return diagnostics.Remove_type_from_import_of_0_from_1.Localize(locale, symbolName, moduleSpec)
- }
- moduleSpec := getModuleSpecifierText(promotedDeclaration)
- return diagnostics.Remove_type_from_import_declaration_from_0.Localize(locale, moduleSpec)
- default:
- panic(fmt.Sprintf(`Unexpected fix kind %v`, fix.kind))
- }
-}
-
-func getNewRequires(
- changeTracker *change.Tracker,
- moduleSpecifier string,
- quotePreference quotePreference,
- defaultImport *Import,
- namedImports []*Import,
- namespaceLikeImport *Import,
- compilerOptions *core.CompilerOptions,
-) []*ast.Statement {
- quotedModuleSpecifier := changeTracker.NodeFactory.NewStringLiteral(
- moduleSpecifier,
- core.IfElse(quotePreference == quotePreferenceSingle, ast.TokenFlagsSingleQuote, ast.TokenFlagsNone),
- )
- var statements []*ast.Statement
-
- // const { default: foo, bar, etc } = require('./mod');
- if defaultImport != nil || len(namedImports) > 0 {
- bindingElements := []*ast.Node{}
- for _, namedImport := range namedImports {
- var propertyName *ast.Node
- if namedImport.propertyName != "" {
- propertyName = changeTracker.NodeFactory.NewIdentifier(namedImport.propertyName)
- }
- bindingElements = append(bindingElements, changeTracker.NodeFactory.NewBindingElement(
- /*dotDotDotToken*/ nil,
- propertyName,
- changeTracker.NodeFactory.NewIdentifier(namedImport.name),
- /*initializer*/ nil,
- ))
- }
- if defaultImport != nil {
- bindingElements = append([]*ast.Node{
- changeTracker.NodeFactory.NewBindingElement(
- /*dotDotDotToken*/ nil,
- changeTracker.NodeFactory.NewIdentifier("default"),
- changeTracker.NodeFactory.NewIdentifier(defaultImport.name),
- /*initializer*/ nil,
- ),
- }, bindingElements...)
- }
- declaration := createConstEqualsRequireDeclaration(
- changeTracker,
- changeTracker.NodeFactory.NewBindingPattern(
- ast.KindObjectBindingPattern,
- changeTracker.NodeFactory.NewNodeList(bindingElements),
- ),
- quotedModuleSpecifier,
- )
- statements = append(statements, declaration)
- }
-
- // const foo = require('./mod');
- if namespaceLikeImport != nil {
- declaration := createConstEqualsRequireDeclaration(
- changeTracker,
- changeTracker.NodeFactory.NewIdentifier(namespaceLikeImport.name),
- quotedModuleSpecifier,
- )
- statements = append(statements, declaration)
- }
-
- debug.AssertIsDefined(statements)
- return statements
-}
-
-func createConstEqualsRequireDeclaration(changeTracker *change.Tracker, name *ast.Node, quotedModuleSpecifier *ast.Node) *ast.Statement {
- return changeTracker.NodeFactory.NewVariableStatement(
- /*modifiers*/ nil,
- changeTracker.NodeFactory.NewVariableDeclarationList(
- ast.NodeFlagsConst,
- changeTracker.NodeFactory.NewNodeList([]*ast.Node{
- changeTracker.NodeFactory.NewVariableDeclaration(
- name,
- /*exclamationToken*/ nil,
- /*type*/ nil,
- changeTracker.NodeFactory.NewCallExpression(
- changeTracker.NodeFactory.NewIdentifier("require"),
- /*questionDotToken*/ nil,
- /*typeArguments*/ nil,
- changeTracker.NodeFactory.NewNodeList([]*ast.Node{quotedModuleSpecifier}),
- ast.NodeFlagsNone,
- ),
- ),
- }),
- ),
- )
-}
-
-func getModuleSpecifierText(promotedDeclaration *ast.Node) string {
- if promotedDeclaration.Kind == ast.KindImportEqualsDeclaration {
- importEqualsDeclaration := promotedDeclaration.AsImportEqualsDeclaration()
- if ast.IsExternalModuleReference(importEqualsDeclaration.ModuleReference) {
- expr := importEqualsDeclaration.ModuleReference.Expression()
- if expr != nil && expr.Kind == ast.KindStringLiteral {
- return expr.Text()
- }
-
- }
- return importEqualsDeclaration.ModuleReference.Text()
- }
- return promotedDeclaration.Parent.ModuleSpecifier().Text()
-}
-
-func ptrTo[T any](v T) *T {
- return &v
-}
diff --git a/internal/ls/autoimportsexportinfo.go b/internal/ls/autoimportsexportinfo.go
deleted file mode 100644
index ab3fe4a8c3..0000000000
--- a/internal/ls/autoimportsexportinfo.go
+++ /dev/null
@@ -1,181 +0,0 @@
-package ls
-
-import (
- "context"
-
- "github.com/microsoft/typescript-go/internal/ast"
- "github.com/microsoft/typescript-go/internal/checker"
- "github.com/microsoft/typescript-go/internal/collections"
- "github.com/microsoft/typescript-go/internal/core"
- "github.com/microsoft/typescript-go/internal/lsp/lsproto"
- "github.com/microsoft/typescript-go/internal/scanner"
- "github.com/microsoft/typescript-go/internal/stringutil"
-)
-
-func (l *LanguageService) getExportInfoMap(
- ctx context.Context,
- ch *checker.Checker,
- importingFile *ast.SourceFile,
- exportMapKey lsproto.ExportInfoMapKey,
-) []*SymbolExportInfo {
- expInfoMap := NewExportInfoMap(l.GetProgram().GetGlobalTypingsCacheLocation())
- moduleCount := 0
- symbolNameMatch := func(symbolName string) bool {
- return symbolName == exportMapKey.SymbolName
- }
- forEachExternalModuleToImportFrom(
- ch,
- l.GetProgram(),
- l.UserPreferences(),
- // /*useAutoImportProvider*/ true,
- func(moduleSymbol *ast.Symbol, moduleFile *ast.SourceFile, ch *checker.Checker, isFromPackageJson bool) {
- if moduleCount = moduleCount + 1; moduleCount%100 == 0 && ctx.Err() != nil {
- return
- }
- if moduleFile == nil && stringutil.StripQuotes(moduleSymbol.Name) != exportMapKey.AmbientModuleName {
- return
- }
- seenExports := collections.Set[string]{}
- defaultInfo := getDefaultLikeExportInfo(moduleSymbol, ch)
- var exportingModuleSymbol *ast.Symbol
- if defaultInfo != nil {
- exportingModuleSymbol = defaultInfo.exportingModuleSymbol
- // Note: I think we shouldn't actually see resolved module symbols here, but weird merges
- // can cause it to happen: see 'completionsImport_mergedReExport.ts'
- if isImportableSymbol(exportingModuleSymbol, ch) {
- expInfoMap.add(
- importingFile.Path(),
- exportingModuleSymbol,
- core.IfElse(defaultInfo.exportKind == ExportKindDefault, ast.InternalSymbolNameDefault, ast.InternalSymbolNameExportEquals),
- moduleSymbol,
- moduleFile,
- defaultInfo.exportKind,
- isFromPackageJson,
- ch,
- symbolNameMatch,
- nil,
- )
- }
- }
- ch.ForEachExportAndPropertyOfModule(moduleSymbol, func(exported *ast.Symbol, key string) {
- if exported != exportingModuleSymbol && isImportableSymbol(exported, ch) && seenExports.AddIfAbsent(key) {
- expInfoMap.add(
- importingFile.Path(),
- exported,
- key,
- moduleSymbol,
- moduleFile,
- ExportKindNamed,
- isFromPackageJson,
- ch,
- symbolNameMatch,
- nil,
- )
- }
- })
- })
- return expInfoMap.get(importingFile.Path(), ch, exportMapKey)
-}
-
-func (l *LanguageService) searchExportInfosForCompletions(
- ctx context.Context,
- ch *checker.Checker,
- importingFile *ast.SourceFile,
- isForImportStatementCompletion bool,
- isRightOfOpenTag bool,
- isTypeOnlyLocation bool,
- lowerCaseTokenText string,
- action func([]*SymbolExportInfo, string, bool, lsproto.ExportInfoMapKey) []*SymbolExportInfo,
-) {
- symbolNameMatches := map[string]bool{}
- symbolNameMatch := func(symbolName string) bool {
- if !scanner.IsIdentifierText(symbolName, importingFile.LanguageVariant) {
- return false
- }
- if b, ok := symbolNameMatches[symbolName]; ok {
- return b
- }
- if isNonContextualKeyword(scanner.StringToToken(symbolName)) {
- symbolNameMatches[symbolName] = false
- return false
- }
- // Do not try to auto-import something with a lowercase first letter for a JSX tag
- firstChar := rune(symbolName[0])
- if isRightOfOpenTag && (firstChar < 'A' || firstChar > 'Z') {
- symbolNameMatches[symbolName] = false
- return false
- }
-
- symbolNameMatches[symbolName] = charactersFuzzyMatchInString(symbolName, lowerCaseTokenText)
- return symbolNameMatches[symbolName]
- }
- flagMatch := func(targetFlags ast.SymbolFlags) bool {
- if !isTypeOnlyLocation && !isForImportStatementCompletion && (targetFlags&ast.SymbolFlagsValue) == 0 {
- return false
- }
- if isTypeOnlyLocation && (targetFlags&(ast.SymbolFlagsModule|ast.SymbolFlagsType) == 0) {
- return false
- }
- return true
- }
-
- expInfoMap := NewExportInfoMap(l.GetProgram().GetGlobalTypingsCacheLocation())
- moduleCount := 0
- forEachExternalModuleToImportFrom(
- ch,
- l.GetProgram(),
- l.UserPreferences(),
- // /*useAutoImportProvider*/ true,
- func(moduleSymbol *ast.Symbol, moduleFile *ast.SourceFile, ch *checker.Checker, isFromPackageJson bool) {
- if moduleCount = moduleCount + 1; moduleCount%100 == 0 && ctx.Err() != nil {
- return
- }
- seenExports := collections.Set[string]{}
- defaultInfo := getDefaultLikeExportInfo(moduleSymbol, ch)
- // Note: I think we shouldn't actually see resolved module symbols here, but weird merges
- // can cause it to happen: see 'completionsImport_mergedReExport.ts'
- if defaultInfo != nil && isImportableSymbol(defaultInfo.exportingModuleSymbol, ch) {
- expInfoMap.add(
- importingFile.Path(),
- defaultInfo.exportingModuleSymbol,
- core.IfElse(defaultInfo.exportKind == ExportKindDefault, ast.InternalSymbolNameDefault, ast.InternalSymbolNameExportEquals),
- moduleSymbol,
- moduleFile,
- defaultInfo.exportKind,
- isFromPackageJson,
- ch,
- symbolNameMatch,
- flagMatch,
- )
- }
- var exportingModuleSymbol *ast.Symbol
- if defaultInfo != nil {
- exportingModuleSymbol = defaultInfo.exportingModuleSymbol
- }
- ch.ForEachExportAndPropertyOfModule(moduleSymbol, func(exported *ast.Symbol, key string) {
- if exported != exportingModuleSymbol && isImportableSymbol(exported, ch) && seenExports.AddIfAbsent(key) {
- expInfoMap.add(
- importingFile.Path(),
- exported,
- key,
- moduleSymbol,
- moduleFile,
- ExportKindNamed,
- isFromPackageJson,
- ch,
- symbolNameMatch,
- flagMatch,
- )
- }
- })
- })
- expInfoMap.search(
- ch,
- importingFile.Path(),
- /*preferCapitalized*/ isRightOfOpenTag,
- func(symbolName string, targetFlags ast.SymbolFlags) bool {
- return symbolNameMatch(symbolName) && flagMatch(targetFlags)
- },
- action,
- )
-}
diff --git a/internal/ls/autoimportstypes.go b/internal/ls/autoimportstypes.go
deleted file mode 100644
index 3ac6ba6417..0000000000
--- a/internal/ls/autoimportstypes.go
+++ /dev/null
@@ -1,215 +0,0 @@
-package ls
-
-import (
- "fmt"
-
- "github.com/microsoft/typescript-go/internal/ast"
- "github.com/microsoft/typescript-go/internal/checker"
- "github.com/microsoft/typescript-go/internal/core"
- "github.com/microsoft/typescript-go/internal/lsp/lsproto"
- "github.com/microsoft/typescript-go/internal/modulespecifiers"
-)
-
-//go:generate go tool golang.org/x/tools/cmd/stringer -type=ExportKind -output=autoImports_stringer_generated.go
-//go:generate go tool mvdan.cc/gofumpt -w autoImports_stringer_generated.go
-
-type ImportKind int
-
-const (
- ImportKindNamed ImportKind = 0
- ImportKindDefault ImportKind = 1
- ImportKindNamespace ImportKind = 2
- ImportKindCommonJS ImportKind = 3
-)
-
-type ExportKind int
-
-const (
- ExportKindNamed ExportKind = 0
- ExportKindDefault ExportKind = 1
- ExportKindExportEquals ExportKind = 2
- ExportKindUMD ExportKind = 3
- ExportKindModule ExportKind = 4
-)
-
-type ImportFixKind int
-
-const (
- // Sorted with the preferred fix coming first.
- ImportFixKindUseNamespace ImportFixKind = 0
- ImportFixKindJsdocTypeImport ImportFixKind = 1
- ImportFixKindAddToExisting ImportFixKind = 2
- ImportFixKindAddNew ImportFixKind = 3
- ImportFixKindPromoteTypeOnly ImportFixKind = 4
-)
-
-type AddAsTypeOnly int
-
-const (
- // These should not be combined as bitflags, but are given powers of 2 values to
- // easily detect conflicts between `NotAllowed` and `Required` by giving them a unique sum.
- // They're also ordered in terms of increasing priority for a fix-all scenario (see
- // `reduceAddAsTypeOnlyValues`).
- AddAsTypeOnlyAllowed AddAsTypeOnly = 1 << 0
- AddAsTypeOnlyRequired AddAsTypeOnly = 1 << 1
- AddAsTypeOnlyNotAllowed AddAsTypeOnly = 1 << 2
-)
-
-type ImportFix struct {
- kind ImportFixKind
- isReExport *bool
- exportInfo *SymbolExportInfo // !!! | FutureSymbolExportInfo | undefined
- moduleSpecifierKind modulespecifiers.ResultKind
- moduleSpecifier string
- usagePosition *lsproto.Position
- namespacePrefix *string
-
- importClauseOrBindingPattern *ast.Node // ImportClause | ObjectBindingPattern
- importKind ImportKind // ImportKindDefault | ImportKindNamed
- addAsTypeOnly AddAsTypeOnly
- propertyName string // !!! not implemented
-
- useRequire bool
-
- typeOnlyAliasDeclaration *ast.Declaration // TypeOnlyAliasDeclaration
-}
-
-func (i *ImportFix) qualification() *Qualification {
- switch i.kind {
- case ImportFixKindAddNew:
- if i.usagePosition == nil || strPtrIsEmpty(i.namespacePrefix) {
- return nil
- }
- fallthrough
- case ImportFixKindUseNamespace:
- return &Qualification{
- usagePosition: *i.usagePosition,
- namespacePrefix: *i.namespacePrefix,
- }
- }
- panic(fmt.Sprintf("no qualification with ImportFixKind %v", i.kind))
-}
-
-type Qualification struct {
- usagePosition lsproto.Position
- namespacePrefix string
-}
-
-func getUseNamespaceImport(
- moduleSpecifier string,
- moduleSpecifierKind modulespecifiers.ResultKind,
- namespacePrefix string,
- usagePosition lsproto.Position,
-) *ImportFix {
- return &ImportFix{
- kind: ImportFixKindUseNamespace,
- moduleSpecifierKind: moduleSpecifierKind,
- moduleSpecifier: moduleSpecifier,
-
- usagePosition: ptrTo(usagePosition),
- namespacePrefix: strPtrTo(namespacePrefix),
- }
-}
-
-func getAddJsdocTypeImport(
- moduleSpecifier string,
- moduleSpecifierKind modulespecifiers.ResultKind,
- usagePosition *lsproto.Position,
- exportInfo *SymbolExportInfo,
- isReExport *bool,
-) *ImportFix {
- return &ImportFix{
- kind: ImportFixKindJsdocTypeImport,
- isReExport: isReExport,
- exportInfo: exportInfo,
- moduleSpecifierKind: moduleSpecifierKind,
- moduleSpecifier: moduleSpecifier,
- usagePosition: usagePosition,
- }
-}
-
-func getAddToExistingImport(
- importClauseOrBindingPattern *ast.Node,
- importKind ImportKind,
- moduleSpecifier string,
- moduleSpecifierKind modulespecifiers.ResultKind,
- addAsTypeOnly AddAsTypeOnly,
-) *ImportFix {
- return &ImportFix{
- kind: ImportFixKindAddToExisting,
- moduleSpecifierKind: moduleSpecifierKind,
- moduleSpecifier: moduleSpecifier,
- importClauseOrBindingPattern: importClauseOrBindingPattern,
- importKind: importKind,
- addAsTypeOnly: addAsTypeOnly,
- }
-}
-
-func getNewAddNewImport(
- moduleSpecifier string,
- moduleSpecifierKind modulespecifiers.ResultKind,
- importKind ImportKind,
- useRequire bool,
- addAsTypeOnly AddAsTypeOnly,
- exportInfo *SymbolExportInfo, // !!! | FutureSymbolExportInfo
- isReExport *bool,
- qualification *Qualification,
-) *ImportFix {
- return &ImportFix{
- kind: ImportFixKindAddNew,
- isReExport: isReExport,
- exportInfo: exportInfo,
- moduleSpecifierKind: modulespecifiers.ResultKindNone,
- moduleSpecifier: moduleSpecifier,
- importKind: importKind,
- addAsTypeOnly: addAsTypeOnly,
- useRequire: useRequire,
- }
-}
-
-func getNewPromoteTypeOnlyImport(typeOnlyAliasDeclaration *ast.Declaration) *ImportFix {
- // !!! function stub
- return &ImportFix{
- kind: ImportFixKindPromoteTypeOnly,
- // isReExport *bool
- // exportInfo *SymbolExportInfo // !!! | FutureSymbolExportInfo | undefined
- // moduleSpecifierKind modulespecifiers.ResultKind
- // moduleSpecifier string
- typeOnlyAliasDeclaration: typeOnlyAliasDeclaration,
- }
-}
-
-/** Information needed to augment an existing import declaration. */
-// !!! after full implementation, rename to AddToExistingImportInfo
-type FixAddToExistingImportInfo struct {
- declaration *ast.Declaration
- importKind ImportKind
- targetFlags ast.SymbolFlags
- symbol *ast.Symbol
-}
-
-func (info *FixAddToExistingImportInfo) getNewImportFromExistingSpecifier(
- isValidTypeOnlyUseSite bool,
- useRequire bool,
- ch *checker.Checker,
- compilerOptions *core.CompilerOptions,
-) *ImportFix {
- moduleSpecifier := checker.TryGetModuleSpecifierFromDeclaration(info.declaration)
- if moduleSpecifier == nil || moduleSpecifier.Text() == "" {
- return nil
- }
- addAsTypeOnly := AddAsTypeOnlyNotAllowed
- if !useRequire {
- addAsTypeOnly = getAddAsTypeOnly(isValidTypeOnlyUseSite, info.symbol, info.targetFlags, ch, compilerOptions)
- }
- return getNewAddNewImport(
- moduleSpecifier.Text(),
- modulespecifiers.ResultKindNone,
- info.importKind,
- useRequire,
- addAsTypeOnly,
- nil, // exportInfo
- nil, // isReExport
- nil, // qualification
- )
-}
diff --git a/internal/ls/codeactions.go b/internal/ls/codeactions.go
index f843c49aeb..c581252bd7 100644
--- a/internal/ls/codeactions.go
+++ b/internal/ls/codeactions.go
@@ -13,9 +13,9 @@ import (
// CodeFixProvider represents a provider for a specific type of code fix
type CodeFixProvider struct {
ErrorCodes []int32
- GetCodeActions func(ctx context.Context, fixContext *CodeFixContext) []CodeAction
+ GetCodeActions func(ctx context.Context, fixContext *CodeFixContext) ([]CodeAction, error)
FixIds []string
- GetAllCodeActions func(ctx context.Context, fixContext *CodeFixContext) *CombinedCodeActions
+ GetAllCodeActions func(ctx context.Context, fixContext *CodeFixContext) (*CombinedCodeActions, error)
}
// CodeFixContext contains the context needed to generate code fixes
@@ -82,7 +82,10 @@ func (l *LanguageService) ProvideCodeActions(ctx context.Context, params *lsprot
}
// Get code actions from the provider
- providerActions := provider.GetCodeActions(ctx, fixContext)
+ providerActions, err := provider.GetCodeActions(ctx, fixContext)
+ if err != nil {
+ return lsproto.CodeActionResponse{}, err
+ }
for _, action := range providerActions {
actions = append(actions, convertToLSPCodeAction(&action, diag, params.TextDocument.Uri))
}
diff --git a/internal/ls/codeactions_importfixes.go b/internal/ls/codeactions_importfixes.go
index 49939e7cc6..7be81a91d6 100644
--- a/internal/ls/codeactions_importfixes.go
+++ b/internal/ls/codeactions_importfixes.go
@@ -1,26 +1,18 @@
package ls
import (
- "cmp"
"context"
- "fmt"
"slices"
- "strings"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/astnav"
"github.com/microsoft/typescript-go/internal/checker"
- "github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/compiler"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/diagnostics"
- "github.com/microsoft/typescript-go/internal/ls/change"
- "github.com/microsoft/typescript-go/internal/ls/lsutil"
- "github.com/microsoft/typescript-go/internal/ls/organizeimports"
+ "github.com/microsoft/typescript-go/internal/ls/autoimport"
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
- "github.com/microsoft/typescript-go/internal/outputpaths"
"github.com/microsoft/typescript-go/internal/scanner"
- "github.com/microsoft/typescript-go/internal/tspath"
)
var importFixErrorCodes = []int32{
@@ -59,56 +51,51 @@ var ImportFixProvider = &CodeFixProvider{
}
type fixInfo struct {
- fix *ImportFix
+ fix *autoimport.Fix
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 */)
+func getImportCodeActions(ctx context.Context, fixContext *CodeFixContext) ([]CodeAction, error) {
+ info, err := getFixInfos(ctx, fixContext, fixContext.ErrorCode, fixContext.Span.Pos())
+ if err != nil {
+ return nil, err
+ }
if len(info) == 0 {
- return nil
+ return nil, 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(
+ edits, description := fixInfo.fix.Edits(
ctx,
- tracker,
fixContext.SourceFile,
- fixInfo.symbolName,
- fixInfo.fix,
- fixInfo.symbolName != fixInfo.errorIdentifierText,
+ fixContext.Program.Options(),
+ fixContext.LS.FormatOptions(),
+ fixContext.LS.converters,
+ fixContext.LS.UserPreferences(),
)
- if msg != "" {
- // 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,
- Changes: edits,
- })
- }
+ actions = append(actions, CodeAction{
+ Description: description,
+ Changes: edits,
+ })
}
- return actions
+ return actions, nil
}
-func getFixInfos(ctx context.Context, fixContext *CodeFixContext, errorCode int32, pos int, useAutoImportProvider bool) []*fixInfo {
+func getFixInfos(ctx context.Context, fixContext *CodeFixContext, errorCode int32, pos int) ([]*fixInfo, error) {
symbolToken := astnav.GetTokenAtPosition(fixContext.SourceFile, pos)
+ var view *autoimport.View
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)
+ view = fixContext.LS.getCurrentAutoImportView(fixContext.SourceFile)
+ info = getFixesInfoForUMDImport(ctx, fixContext, symbolToken, view)
} else if !ast.IsIdentifier(symbolToken) {
- return nil
+ return nil, 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)
@@ -121,18 +108,26 @@ func getFixInfos(ctx context.Context, fixContext *CodeFixContext, errorCode int3
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 []*fixInfo{{fix: fix, symbolName: symbolName, errorIdentifierText: symbolToken.Text()}}, nil
}
- return nil
+ return nil, nil
} else {
- info = getFixesInfoForNonUMDImport(ctx, fixContext, symbolToken, useAutoImportProvider)
+ var err error
+ view, err = fixContext.LS.getPreparedAutoImportView(fixContext.SourceFile)
+ if err != nil {
+ return nil, err
+ }
+ info = getFixesInfoForNonUMDImport(ctx, fixContext, symbolToken, view)
}
// Sort fixes by preference
- return sortFixInfo(info, fixContext)
+ if view == nil {
+ view = fixContext.LS.getCurrentAutoImportView(fixContext.SourceFile)
+ }
+ return sortFixInfo(info, fixContext, view), nil
}
-func getFixesInfoForUMDImport(ctx context.Context, fixContext *CodeFixContext, token *ast.Node) []*fixInfo {
+func getFixesInfoForUMDImport(ctx context.Context, fixContext *CodeFixContext, token *ast.Node, view *autoimport.View) []*fixInfo {
ch, done := fixContext.Program.GetTypeChecker(ctx)
defer done()
@@ -141,43 +136,18 @@ func getFixesInfoForUMDImport(ctx context.Context, fixContext *CodeFixContext, t
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
- )
+ export := autoimport.SymbolToExport(umdSymbol, ch)
+ isValidTypeOnlyUseSite := ast.IsValidTypeOnlyAliasUseSite(token)
var result []*fixInfo
- for _, fix := range fixes {
+ for _, fix := range view.GetFixes(ctx, export, false, isValidTypeOnlyUseSite, nil) {
errorIdentifierText := ""
if ast.IsIdentifier(token) {
errorIdentifierText = token.Text()
}
result = append(result, &fixInfo{
fix: fix,
- symbolName: symbolName,
+ symbolName: umdSymbol.Name,
errorIdentifierText: errorIdentifierText,
})
}
@@ -219,63 +189,41 @@ func isUMDExportSymbol(symbol *ast.Symbol) bool {
ast.IsNamespaceExportDeclaration(symbol.Declarations[0])
}
-func getFixesInfoForNonUMDImport(ctx context.Context, fixContext *CodeFixContext, symbolToken *ast.Node, useAutoImportProvider bool) []*fixInfo {
+func getFixesInfoForNonUMDImport(ctx context.Context, fixContext *CodeFixContext, symbolToken *ast.Node, view *autoimport.View) []*fixInfo {
ch, done := fixContext.Program.GetTypeChecker(ctx)
defer done()
compilerOptions := fixContext.Program.Options()
+ isValidTypeOnlyUseSite := ast.IsValidTypeOnlyAliasUseSite(symbolToken)
symbolNames := getSymbolNamesToImport(fixContext.SourceFile, ch, symbolToken, compilerOptions)
var allInfo []*fixInfo
+ // Compute usage position for JSDoc import type fixes
+ usagePosition := fixContext.LS.converters.PositionToLineAndCharacter(fixContext.SourceFile, core.TextPos(scanner.GetTokenPosOfNode(symbolToken, fixContext.SourceFile, false)))
+
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...)
+ isJSXTagName := symbolName == symbolToken.Text() && ast.IsJsxTagName(symbolToken)
+ queryKind := autoimport.QueryKindExactMatch
+ if isJSXTagName {
+ queryKind = autoimport.QueryKindCaseInsensitiveMatch
}
- // 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
- )
+ exports := view.Search(symbolName, queryKind)
+ for _, export := range exports {
+ if isJSXTagName && !(export.Name() == symbolName || export.IsRenameable()) {
+ continue
+ }
+ fixes := view.GetFixes(ctx, export, isJSXTagName, isValidTypeOnlyUseSite, &usagePosition)
for _, fix := range fixes {
allInfo = append(allInfo, &fixInfo{
- fix: fix,
- symbolName: symbolName,
- errorIdentifierText: symbolToken.Text(),
- isJsxNamespaceFix: symbolName != symbolToken.Text(),
+ fix: fix,
+ symbolName: symbolName,
})
}
}
@@ -284,7 +232,7 @@ func getFixesInfoForNonUMDImport(ctx context.Context, fixContext *CodeFixContext
return allInfo
}
-func getTypeOnlyPromotionFix(ctx context.Context, sourceFile *ast.SourceFile, symbolToken *ast.Node, symbolName string, program *compiler.Program) *ImportFix {
+func getTypeOnlyPromotionFix(ctx context.Context, sourceFile *ast.SourceFile, symbolToken *ast.Node, symbolName string, program *compiler.Program) *autoimport.Fix {
ch, done := program.GetTypeChecker(ctx)
defer done()
@@ -300,9 +248,11 @@ func getTypeOnlyPromotionFix(ctx context.Context, sourceFile *ast.SourceFile, sy
return nil
}
- return &ImportFix{
- kind: ImportFixKindPromoteTypeOnly,
- typeOnlyAliasDeclaration: typeOnlyAliasDeclaration,
+ return &autoimport.Fix{
+ AutoImportFix: &lsproto.AutoImportFix{
+ Kind: lsproto.AutoImportFixKindPromoteTypeOnly,
+ },
+ TypeOnlyAliasDeclaration: typeOnlyAliasDeclaration,
}
}
@@ -343,89 +293,7 @@ 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,
- ls.UserPreferences(),
- 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 {
+func sortFixInfo(fixes []*fixInfo, fixContext *CodeFixContext, view *autoimport.View) []*fixInfo {
if len(fixes) == 0 {
return fixes
}
@@ -434,209 +302,16 @@ func sortFixInfo(fixes []*fixInfo, fixContext *CodeFixContext) []*fixInfo {
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
+ // 2. Fix comparison using view.CompareFixes
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 view.CompareFixesForSorting(a.fix, b.fix)
})
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, moduleSpecifier.AsStringLiteral().TokenFlags)
- changes.ReplaceNode(sourceFile, moduleSpecifier, newStringLiteral, nil)
- }
- }
- }
-
- // Handle verbatimModuleSyntax conversion
- // If convertExistingToTypeOnly is true, we need to add 'type' to other specifiers
- // in the same import declaration
- if convertExistingToTypeOnly.IsTrue() {
- namedImports := importClause.NamedBindings
- if namedImports != nil && namedImports.Kind == ast.KindNamedImports {
- namedImportsData := namedImports.AsNamedImports()
- if len(namedImportsData.Elements.Nodes) > 1 {
- // Check if the list is sorted and if we need to reorder
- _, isSorted := organizeimports.GetNamedImportSpecifierComparerWithDetection(
- importClause.Parent,
- sourceFile,
- ls.UserPreferences(),
- )
-
- // If the alias declaration is an ImportSpecifier and the list is sorted,
- // move it to index 0 (since it will be the only non-type-only import)
- if isSorted.IsFalse() == false && // isSorted !== false
- aliasDeclaration != nil &&
- aliasDeclaration.Kind == ast.KindImportSpecifier {
- // Find the index of the alias declaration
- aliasIndex := -1
- for i, element := range namedImportsData.Elements.Nodes {
- if element == aliasDeclaration {
- aliasIndex = i
- break
- }
- }
- // If not already at index 0, move it there
- if aliasIndex > 0 {
- // Delete the specifier from its current position
- changes.Delete(sourceFile, aliasDeclaration)
- // Insert it at index 0
- changes.InsertImportSpecifierAtIndex(sourceFile, aliasDeclaration, namedImports, 0)
- }
- }
-
- // Add 'type' keyword to all other import specifiers that aren't already type-only
- for _, element := range namedImportsData.Elements.Nodes {
- spec := element.AsImportSpecifier()
- // Skip the specifier being promoted (if aliasDeclaration is an ImportSpecifier)
- if aliasDeclaration != nil && aliasDeclaration.Kind == ast.KindImportSpecifier {
- if element == aliasDeclaration {
- continue
- }
- }
- // Skip if already type-only
- if !spec.IsTypeOnly {
- changes.InsertModifierBefore(sourceFile, ast.KindTypeKeyword, element)
- }
- }
- }
- }
- }
-}
-
-// deleteTypeKeyword deletes the 'type' keyword token starting at the given position,
-// including any trailing whitespace.
-func deleteTypeKeyword(changes *change.Tracker, sourceFile *ast.SourceFile, startPos int) {
- scan := scanner.GetScannerForSourceFile(sourceFile, startPos)
- if scan.Token() != ast.KindTypeKeyword {
- return
- }
- typeStart := scan.TokenStart()
- typeEnd := scan.TokenEnd()
- // Skip trailing whitespace
- text := sourceFile.Text()
- for typeEnd < len(text) && (text[typeEnd] == ' ' || text[typeEnd] == '\t') {
- typeEnd++
- }
- changes.DeleteRange(sourceFile, core.NewTextRange(typeStart, typeEnd))
-}
diff --git a/internal/ls/completions.go b/internal/ls/completions.go
index e8e7003b36..de07d41a83 100644
--- a/internal/ls/completions.go
+++ b/internal/ls/completions.go
@@ -12,7 +12,6 @@ import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/astnav"
- "github.com/microsoft/typescript-go/internal/binder"
"github.com/microsoft/typescript-go/internal/checker"
"github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/compiler"
@@ -20,15 +19,17 @@ import (
"github.com/microsoft/typescript-go/internal/debug"
"github.com/microsoft/typescript-go/internal/format"
"github.com/microsoft/typescript-go/internal/jsnum"
+ "github.com/microsoft/typescript-go/internal/ls/autoimport"
"github.com/microsoft/typescript-go/internal/ls/lsutil"
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
"github.com/microsoft/typescript-go/internal/nodebuilder"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/scanner"
"github.com/microsoft/typescript-go/internal/stringutil"
- "github.com/microsoft/typescript-go/internal/tspath"
)
+var ErrNeedsAutoImports = errors.New("completion list needs auto imports")
+
func (l *LanguageService) ProvideCompletion(
ctx context.Context,
documentURI lsproto.DocumentUri,
@@ -41,12 +42,15 @@ func (l *LanguageService) ProvideCompletion(
triggerCharacter = context.TriggerCharacter
}
position := int(l.converters.LineAndCharacterToPosition(file, LSPPosition))
- completionList := l.getCompletionsAtPosition(
+ completionList, err := l.getCompletionsAtPosition(
ctx,
file,
position,
triggerCharacter,
)
+ if err != nil {
+ return lsproto.CompletionItemsOrListOrNull{}, err
+ }
completionList = ensureItemData(file.FileName(), position, completionList)
return lsproto.CompletionItemsOrListOrNull{List: completionList}, nil
}
@@ -72,6 +76,7 @@ type completionData = any
type completionDataData struct {
symbols []*ast.Symbol
+ autoImports []*autoimport.FixAndExport
completionKind CompletionKind
isInSnippetScope bool
// Note that the presence of this alone doesn't mean that we need a conversion. Only do that if the completion is not an ordinary identifier.
@@ -199,16 +204,12 @@ type symbolOriginInfoKind int
const (
symbolOriginInfoKindThisType symbolOriginInfoKind = 1 << iota
symbolOriginInfoKindSymbolMember
- symbolOriginInfoKindExport
symbolOriginInfoKindPromise
symbolOriginInfoKindNullable
symbolOriginInfoKindTypeOnlyAlias
symbolOriginInfoKindObjectLiteralMethod
symbolOriginInfoKindIgnore
symbolOriginInfoKindComputedPropertyName
-
- symbolOriginInfoKindSymbolMemberNoExport symbolOriginInfoKind = symbolOriginInfoKindSymbolMember
- symbolOriginInfoKindSymbolMemberExport = symbolOriginInfoKindSymbolMember | symbolOriginInfoKindExport
)
type symbolOriginInfo struct {
@@ -221,8 +222,6 @@ type symbolOriginInfo struct {
func (origin *symbolOriginInfo) symbolName() string {
switch origin.data.(type) {
- case *symbolOriginInfoExport:
- return origin.data.(*symbolOriginInfoExport).symbolName
case *symbolOriginInfoComputedPropertyName:
return origin.data.(*symbolOriginInfoComputedPropertyName).symbolName
default:
@@ -230,45 +229,6 @@ func (origin *symbolOriginInfo) symbolName() string {
}
}
-func (origin *symbolOriginInfo) moduleSymbol() *ast.Symbol {
- switch origin.data.(type) {
- case *symbolOriginInfoExport:
- return origin.data.(*symbolOriginInfoExport).moduleSymbol
- default:
- panic(fmt.Sprintf("symbolOriginInfo: unknown data type for moduleSymbol(): %T", origin.data))
- }
-}
-
-func (origin *symbolOriginInfo) toCompletionEntryData() *lsproto.AutoImportData {
- debug.Assert(origin.kind&symbolOriginInfoKindExport != 0, fmt.Sprintf("completionEntryData is not generated for symbolOriginInfo of type %T", origin.data))
- var ambientModuleName string
- if origin.fileName == "" {
- ambientModuleName = stringutil.StripQuotes(origin.moduleSymbol().Name)
- }
-
- data := origin.data.(*symbolOriginInfoExport)
- return &lsproto.AutoImportData{
- ExportName: data.exportName,
- ExportMapKey: data.exportMapKey,
- ModuleSpecifier: data.moduleSpecifier,
- AmbientModuleName: ambientModuleName,
- FileName: origin.fileName,
- IsPackageJsonImport: origin.isFromPackageJson,
- }
-}
-
-type symbolOriginInfoExport struct {
- symbolName string
- moduleSymbol *ast.Symbol
- exportName string
- exportMapKey lsproto.ExportInfoMapKey
- moduleSpecifier string
-}
-
-func (s *symbolOriginInfo) asExport() *symbolOriginInfoExport {
- return s.data.(*symbolOriginInfoExport)
-}
-
type symbolOriginInfoObjectLiteralMethod struct {
insertText string
labelDetails *lsproto.CompletionItemLabelDetails
@@ -333,10 +293,10 @@ func (l *LanguageService) getCompletionsAtPosition(
file *ast.SourceFile,
position int,
triggerCharacter *string,
-) *lsproto.CompletionList {
+) (*lsproto.CompletionList, error) {
_, previousToken := getRelevantTokens(position, file)
if triggerCharacter != nil && !IsInString(file, position, previousToken) && !isValidTrigger(file, *triggerCharacter, previousToken, position) {
- return nil
+ return nil, nil
}
if triggerCharacter != nil && *triggerCharacter == " " {
@@ -344,24 +304,28 @@ func (l *LanguageService) getCompletionsAtPosition(
if l.UserPreferences().IncludeCompletionsForImportStatements.IsTrue() {
return &lsproto.CompletionList{
IsIncomplete: true,
- }
+ }, nil
}
- return nil
+ return nil, nil
}
compilerOptions := l.GetProgram().Options()
// !!! see if incomplete completion list and continue or clean
+ checker, done := l.GetProgram().GetTypeCheckerForFile(ctx, file)
+ defer done()
+
stringCompletions := l.getStringLiteralCompletions(
ctx,
file,
position,
previousToken,
+ checker,
compilerOptions,
)
if stringCompletions != nil {
- return stringCompletions
+ return stringCompletions, nil
}
if previousToken != nil && (previousToken.Kind == ast.KindBreakKeyword ||
@@ -374,15 +338,16 @@ func (l *LanguageService) getCompletionsAtPosition(
file,
position,
l.getOptionalReplacementSpan(previousToken, file),
- )
+ ), nil
}
- checker, done := l.GetProgram().GetTypeCheckerForFile(ctx, file)
- defer done()
preferences := l.UserPreferences()
- data := l.getCompletionData(ctx, checker, file, position, preferences)
+ data, err := l.getCompletionData(ctx, checker, file, position, preferences)
+ if err != nil {
+ return nil, err
+ }
if data == nil {
- return nil
+ return nil, nil
}
switch data := data.(type) {
@@ -398,7 +363,7 @@ func (l *LanguageService) getCompletionsAtPosition(
optionalReplacementSpan,
)
// !!! check if response is incomplete
- return response
+ return response, nil
case *completionDataKeyword:
optionalReplacementSpan := l.getOptionalReplacementSpan(previousToken, file)
return l.specificKeywordCompletionInfo(
@@ -408,7 +373,7 @@ func (l *LanguageService) getCompletionsAtPosition(
data.keywordCompletions,
data.isNewIdentifierLocation,
optionalReplacementSpan,
- )
+ ), nil
case *completionDataJSDocTagName:
// If the current position is a jsDoc tag name, only tag names should be provided for completion
items := getJSDocTagNameCompletions()
@@ -421,7 +386,7 @@ func (l *LanguageService) getCompletionsAtPosition(
preferences,
/*tagNameOnly*/ true,
)...)
- return l.jsDocCompletionInfo(ctx, position, file, items)
+ return l.jsDocCompletionInfo(ctx, position, file, items), nil
case *completionDataJSDocTag:
// If the current position is a jsDoc tag, only tags should be provided for completion
items := getJSDocTagCompletions()
@@ -434,9 +399,9 @@ func (l *LanguageService) getCompletionsAtPosition(
preferences,
/*tagNameOnly*/ false,
)...)
- return l.jsDocCompletionInfo(ctx, position, file, items)
+ return l.jsDocCompletionInfo(ctx, position, file, items), nil
case *completionDataJSDocParameterName:
- return l.jsDocCompletionInfo(ctx, position, file, getJSDocParameterNameCompletions(data.tag))
+ return l.jsDocCompletionInfo(ctx, position, file, getJSDocParameterNameCompletions(data.tag)), nil
default:
panic("getCompletionData() returned unexpected type: " + fmt.Sprintf("%T", data))
}
@@ -448,7 +413,7 @@ func (l *LanguageService) getCompletionData(
file *ast.SourceFile,
position int,
preferences *lsutil.UserPreferences,
-) completionData {
+) (completionData, error) {
inCheckedFile := isCheckedFile(file, l.GetProgram().Options())
currentToken := astnav.GetTokenAtPosition(file, position)
@@ -463,7 +428,7 @@ func (l *LanguageService) getCompletionData(
if file.Text()[position] == '@' {
// The current position is next to the '@' sign, when no tag name being provided yet.
// Provide a full list of tag names
- return &completionDataJSDocTagName{}
+ return &completionDataJSDocTagName{}, nil
} else {
// When completion is requested without "@", we will have check to make sure that
// there are no comments prefix the request position. We will only allow "*" and space.
@@ -490,7 +455,7 @@ func (l *LanguageService) getCompletionData(
}
}
if noCommentPrefix {
- return &completionDataJSDocTag{}
+ return &completionDataJSDocTag{}, nil
}
}
}
@@ -500,7 +465,7 @@ func (l *LanguageService) getCompletionData(
// Completion should work in the brackets
if tag := getJSDocTagAtPosition(currentToken, position); tag != nil {
if tag.TagName().Pos() <= position && position <= tag.TagName().End() {
- return &completionDataJSDocTagName{}
+ return &completionDataJSDocTagName{}, nil
}
if ast.IsJSDocImportTag(tag) {
insideJsDocImportTag = true
@@ -520,7 +485,7 @@ func (l *LanguageService) getCompletionData(
(ast.NodeIsMissing(tag.Name()) || tag.Name().Pos() <= position && position <= tag.Name().End()) {
return &completionDataJSDocParameterName{
tag: tag.AsJSDocParameterOrPropertyTag(),
- }
+ }, nil
}
}
}
@@ -528,7 +493,7 @@ func (l *LanguageService) getCompletionData(
if !insideJSDocTagTypeExpression && !insideJsDocImportTag {
// Proceed if the current position is in JSDoc tag expression; otherwise it is a normal
// comment or the plain text part of a JSDoc comment, so no completion should be available
- return nil
+ return nil, nil
}
}
@@ -566,7 +531,7 @@ func (l *LanguageService) getCompletionData(
SortText: ptrTo(string(SortTextGlobalsOrKeywords)),
}},
isNewIdentifierLocation: importStatementCompletionInfo.isNewIdentifierLocation,
- }
+ }, nil
}
keywordFilters = keywordFiltersFromSyntaxKind(importStatementCompletionInfo.keywordCompletion)
}
@@ -579,9 +544,9 @@ func (l *LanguageService) getCompletionData(
if isCompletionListBlocker(contextToken, previousToken, location, file, position, typeChecker) {
if keywordFilters != KeywordCompletionFiltersNone {
isNewIdentifierLocation, _ := computeCommitCharactersAndIsNewIdentifier(contextToken, file, position)
- return keywordCompletionData(keywordFilters, isJSOnlyLocation, isNewIdentifierLocation)
+ return keywordCompletionData(keywordFilters, isJSOnlyLocation, isNewIdentifierLocation), nil
}
- return nil
+ return nil, nil
}
parent := contextToken.Parent
@@ -601,7 +566,7 @@ func (l *LanguageService) getCompletionData(
// eg: Math.min(./**/)
// const x = function (./**/) {}
// ({./**/})
- return nil
+ return nil, nil
}
case ast.KindQualifiedName:
node = parent.AsQualifiedName().Left
@@ -617,7 +582,7 @@ func (l *LanguageService) getCompletionData(
default:
// There is nothing that precedes the dot, so this likely just a stray character
// or leading into a '...' token. Just bail out instead.
- return nil
+ return nil, nil
}
} else { // !!! else if (!importStatementCompletion)
//
@@ -695,11 +660,11 @@ func (l *LanguageService) getCompletionData(
hasUnresolvedAutoImports := false
// This also gets mutated in nested-functions after the return
var symbols []*ast.Symbol
+ var autoImports []*autoimport.FixAndExport
// Keys are indexes of `symbols`.
symbolToOriginInfoMap := map[int]*symbolOriginInfo{}
symbolToSortTextMap := map[ast.SymbolId]SortText{}
var seenPropertySymbols collections.Set[ast.SymbolId]
- importSpecifierResolver := &importSpecifierResolverForCompletions{SourceFile: file, UserPreferences: preferences, l: l}
isTypeOnlyLocation := insideJSDocTagTypeExpression || insideJsDocImportTag ||
importStatementCompletion != nil && ast.IsTypeOnlyImportOrExportDeclaration(location.Parent) ||
!isContextTokenValueLocation(contextToken) &&
@@ -757,39 +722,9 @@ func (l *LanguageService) getCompletionData(
if moduleSymbol == nil ||
!checker.IsExternalModuleSymbol(moduleSymbol) ||
typeChecker.TryGetMemberInModuleExportsAndProperties(firstAccessibleSymbol.Name, moduleSymbol) != firstAccessibleSymbol {
- symbolToOriginInfoMap[len(symbols)-1] = &symbolOriginInfo{kind: getNullableSymbolOriginInfoKind(symbolOriginInfoKindSymbolMemberNoExport, insertQuestionDot)}
+ symbolToOriginInfoMap[len(symbols)-1] = &symbolOriginInfo{kind: getNullableSymbolOriginInfoKind(symbolOriginInfoKindSymbolMember, insertQuestionDot)}
} else {
- var fileName string
- if tspath.IsExternalModuleNameRelative(stringutil.StripQuotes(moduleSymbol.Name)) {
- fileName = ast.GetSourceFileOfModule(moduleSymbol).FileName()
- }
- result := importSpecifierResolver.getModuleSpecifierForBestExportInfo(
- typeChecker,
- []*SymbolExportInfo{{
- exportKind: ExportKindNamed,
- moduleFileName: fileName,
- isFromPackageJson: false,
- moduleSymbol: moduleSymbol,
- symbol: firstAccessibleSymbol,
- targetFlags: typeChecker.SkipAlias(firstAccessibleSymbol).Flags,
- }},
- position,
- ast.IsValidTypeOnlyAliasUseSite(location),
- )
-
- if result != nil {
- symbolToOriginInfoMap[len(symbols)-1] = &symbolOriginInfo{
- kind: getNullableSymbolOriginInfoKind(symbolOriginInfoKindSymbolMemberExport, insertQuestionDot),
- isDefaultExport: false,
- fileName: fileName,
- data: &symbolOriginInfoExport{
- moduleSymbol: moduleSymbol,
- symbolName: firstAccessibleSymbol.Name,
- exportName: firstAccessibleSymbol.Name,
- moduleSpecifier: result.moduleSpecifier,
- },
- }
- }
+ // !!! auto-import symbol
}
} else if firstAccessibleSymbolId == 0 || !seenPropertySymbols.Has(firstAccessibleSymbolId) {
symbols = append(symbols, symbol)
@@ -966,10 +901,10 @@ func (l *LanguageService) getCompletionData(
}
// Aggregates relevant symbols for completion in object literals in type argument positions.
- tryGetObjectTypeLiteralInTypeArgumentCompletionSymbols := func() globalsSearch {
+ tryGetObjectTypeLiteralInTypeArgumentCompletionSymbols := func() (globalsSearch, error) {
typeLiteralNode := tryGetTypeLiteralNode(contextToken)
if typeLiteralNode == nil {
- return globalsSearchContinue
+ return globalsSearchContinue, nil
}
intersectionTypeNode := core.IfElse(
@@ -983,7 +918,7 @@ func (l *LanguageService) getCompletionData(
containerExpectedType := getConstraintOfTypeArgumentProperty(containerTypeNode, typeChecker)
if containerExpectedType == nil {
- return globalsSearchContinue
+ return globalsSearchContinue, nil
}
containerActualType := typeChecker.GetTypeFromTypeNode(containerTypeNode)
@@ -1003,18 +938,18 @@ func (l *LanguageService) getCompletionData(
completionKind = CompletionKindObjectPropertyDeclaration
isNewIdentifierLocation = true
- return globalsSearchSuccess
+ return globalsSearchSuccess, nil
}
// Aggregates relevant symbols for completion in object literals and object binding patterns.
// Relevant symbols are stored in the captured 'symbols' variable.
- tryGetObjectLikeCompletionSymbols := func() globalsSearch {
+ tryGetObjectLikeCompletionSymbols := func() (globalsSearch, error) {
if contextToken != nil && contextToken.Kind == ast.KindDotDotDotToken {
- return globalsSearchContinue
+ return globalsSearchContinue, nil
}
objectLikeContainer := tryGetObjectLikeCompletionContainer(contextToken, position, file)
if objectLikeContainer == nil {
- return globalsSearchContinue
+ return globalsSearchContinue, nil
}
// We're looking up possible property names from contextual/inferred/declared type.
@@ -1029,9 +964,9 @@ func (l *LanguageService) getCompletionData(
// Check completions for Object property value shorthand
if instantiatedType == nil {
if objectLikeContainer.Flags&ast.NodeFlagsInWithStatement != 0 {
- return globalsSearchFail
+ return globalsSearchFail, nil
}
- return globalsSearchContinue
+ return globalsSearchContinue, nil
}
completionsType := typeChecker.GetContextualType(objectLikeContainer, checker.ContextFlagsCompletions)
t := core.IfElse(completionsType != nil, completionsType, instantiatedType)
@@ -1044,7 +979,7 @@ func (l *LanguageService) getCompletionData(
if len(typeMembers) == 0 {
// Edge case: If NumberIndexType exists
if numberIndexType == nil {
- return globalsSearchContinue
+ return globalsSearchContinue, nil
}
}
} else {
@@ -1078,7 +1013,7 @@ func (l *LanguageService) getCompletionData(
if canGetType {
typeForObject := typeChecker.GetTypeAtLocation(objectLikeContainer)
if typeForObject == nil {
- return globalsSearchFail
+ return globalsSearchFail, nil
}
typeMembers = core.Filter(
typeChecker.GetPropertiesOfType(typeForObject),
@@ -1127,7 +1062,7 @@ func (l *LanguageService) getCompletionData(
}
}
- return globalsSearchSuccess
+ return globalsSearchSuccess, nil
}
shouldOfferImportCompletions := func() bool {
@@ -1144,11 +1079,10 @@ func (l *LanguageService) getCompletionData(
}
// Mutates `symbols`, `symbolToOriginInfoMap`, and `symbolToSortTextMap`
- collectAutoImports := func() {
+ collectAutoImports := func() error {
if !shouldOfferImportCompletions() {
- return
+ return nil
}
- // !!! CompletionInfoFlags
// import { type | -> token text should be blank
var lowerCaseTokenText string
@@ -1156,116 +1090,24 @@ func (l *LanguageService) getCompletionData(
lowerCaseTokenText = strings.ToLower(previousToken.Text())
}
- // !!! timestamp
- // Under `--moduleResolution nodenext` or `bundler`, we have to resolve module specifiers up front, because
- // package.json exports can mean we *can't* resolve a module specifier (that doesn't include a
- // relative path into node_modules), and we want to filter those completions out entirely.
- // Import statement completions always need specifier resolution because the module specifier is
- // part of their `insertText`, not the `codeActions` creating edits away from the cursor.
- // Finally, `autoImportSpecifierExcludeRegexes` necessitates eagerly resolving module specifiers
- // because completion items are being explcitly filtered out by module specifier.
- isValidTypeOnlyUseSite := ast.IsValidTypeOnlyAliasUseSite(location)
-
- // !!! moduleSpecifierCache := host.getModuleSpecifierCache();
- // !!! packageJsonAutoImportProvider := host.getPackageJsonAutoImportProvider();
- addSymbolToList := func(info []*SymbolExportInfo, symbolName string, isFromAmbientModule bool, exportMapKey lsproto.ExportInfoMapKey) []*SymbolExportInfo {
- // Do a relatively cheap check to bail early if all re-exports are non-importable
- // due to file location or package.json dependency filtering. For non-node16+
- // module resolution modes, getting past this point guarantees that we'll be
- // able to generate a suitable module specifier, so we can safely show a completion,
- // even if we defer computing the module specifier.
- info = core.Filter(info, func(i *SymbolExportInfo) bool {
- var toFile *ast.SourceFile
- if ast.IsSourceFile(i.moduleSymbol.ValueDeclaration) {
- toFile = i.moduleSymbol.ValueDeclaration.AsSourceFile()
- }
- return l.isImportable(
- file,
- toFile,
- i.moduleSymbol,
- importSpecifierResolver.packageJsonImportFilter(),
- )
- })
- if len(info) == 0 {
- return nil
- }
-
- // In node16+, module specifier resolution can fail due to modules being blocked
- // by package.json `exports`. If that happens, don't show a completion item.
- // N.B. We always try to resolve module specifiers here, because we have to know
- // now if it's going to fail so we can omit the completion from the list.
- result := importSpecifierResolver.getModuleSpecifierForBestExportInfo(typeChecker, info, position, isValidTypeOnlyUseSite)
- if result == nil {
- return nil
- }
-
- // If we skipped resolving module specifiers, our selection of which ExportInfo
- // to use here is arbitrary, since the info shown in the completion list derived from
- // it should be identical regardless of which one is used. During the subsequent
- // `CompletionEntryDetails` request, we'll get all the ExportInfos again and pick
- // the best one based on the module specifier it produces.
- moduleSpecifier := result.moduleSpecifier
- exportInfo := info[0]
- if result.exportInfo != nil {
- exportInfo = result.exportInfo
- }
-
- isDefaultExport := exportInfo.exportKind == ExportKindDefault
- if exportInfo.symbol == nil {
- panic("should have handled `futureExportSymbolInfo` earlier")
- }
- symbol := exportInfo.symbol
- if isDefaultExport {
- if defaultSymbol := binder.GetLocalSymbolForExportDefault(symbol); defaultSymbol != nil {
- symbol = defaultSymbol
- }
- }
-
- // pushAutoImportSymbol
- symbolId := ast.GetSymbolId(symbol)
- if symbolToSortTextMap[symbolId] == SortTextGlobalsOrKeywords {
- // If an auto-importable symbol is available as a global, don't push the auto import
- return nil
- }
- originInfo := &symbolOriginInfo{
- kind: symbolOriginInfoKindExport,
- isDefaultExport: isDefaultExport,
- isFromPackageJson: exportInfo.isFromPackageJson,
- fileName: exportInfo.moduleFileName,
- data: &symbolOriginInfoExport{
- symbolName: symbolName,
- moduleSymbol: exportInfo.moduleSymbol,
- exportName: core.IfElse(exportInfo.exportKind == ExportKindExportEquals, ast.InternalSymbolNameExportEquals, exportInfo.symbol.Name),
- exportMapKey: exportMapKey,
- moduleSpecifier: moduleSpecifier,
- },
- }
- symbolToOriginInfoMap[len(symbols)] = originInfo
- symbolToSortTextMap[symbolId] = core.IfElse(importStatementCompletion != nil, SortTextLocationPriority, SortTextAutoImportSuggestions)
- symbols = append(symbols, symbol)
- return nil
+ view, err := l.getPreparedAutoImportView(file)
+ if err != nil {
+ return err
}
- l.searchExportInfosForCompletions(ctx,
- typeChecker,
- file,
- importStatementCompletion != nil,
- isRightOfOpenTag,
- isTypeOnlyLocation,
- lowerCaseTokenText,
- addSymbolToList,
- )
- // !!! completionInfoFlags
- // !!! logging
+ autoImports = view.GetCompletions(ctx, lowerCaseTokenText, isRightOfOpenTag, isTypeOnlyLocation)
+ return nil
}
- tryGetImportCompletionSymbols := func() globalsSearch {
+ tryGetImportCompletionSymbols := func() (globalsSearch, error) {
if importStatementCompletion == nil {
- return globalsSearchContinue
+ return globalsSearchContinue, nil
}
isNewIdentifierLocation = true
- collectAutoImports()
- return globalsSearchSuccess
+ if err := collectAutoImports(); err != nil {
+ return globalsSearchFail, err
+ }
+ return globalsSearchSuccess, nil
}
// Aggregates relevant symbols for completion in import clauses and export clauses
@@ -1279,9 +1121,9 @@ func (l *LanguageService) getCompletionData(
// export { | };
//
// Relevant symbols are stored in the captured 'symbols' variable.
- tryGetImportOrExportClauseCompletionSymbols := func() globalsSearch {
+ tryGetImportOrExportClauseCompletionSymbols := func() (globalsSearch, error) {
if contextToken == nil {
- return globalsSearchContinue
+ return globalsSearchContinue, nil
}
// `import { |` or `import { a as 0, | }` or `import { type | }`
@@ -1297,7 +1139,7 @@ func (l *LanguageService) getCompletionData(
}
if namedImportsOrExports == nil {
- return globalsSearchContinue
+ return globalsSearchContinue, nil
}
// We can at least offer `type` at `import { |`
@@ -1313,15 +1155,15 @@ func (l *LanguageService) getCompletionData(
if moduleSpecifier == nil {
isNewIdentifierLocation = true
if namedImportsOrExports.Kind == ast.KindNamedImports {
- return globalsSearchFail
+ return globalsSearchFail, nil
}
- return globalsSearchContinue
+ return globalsSearchContinue, nil
}
moduleSpecifierSymbol := typeChecker.GetSymbolAtLocation(moduleSpecifier)
if moduleSpecifierSymbol == nil {
isNewIdentifierLocation = true
- return globalsSearchFail
+ return globalsSearchFail, nil
}
completionKind = CompletionKindMemberLike
@@ -1344,13 +1186,13 @@ func (l *LanguageService) getCompletionData(
// If there's nothing else to import, don't offer `type` either.
keywordFilters = KeywordCompletionFiltersNone
}
- return globalsSearchSuccess
+ return globalsSearchSuccess, nil
}
// import { x } from "foo" with { | }
- tryGetImportAttributesCompletionSymbols := func() globalsSearch {
+ tryGetImportAttributesCompletionSymbols := func() (globalsSearch, error) {
if contextToken == nil {
- return globalsSearchContinue
+ return globalsSearchContinue, nil
}
var importAttributes *ast.ImportAttributesNode
@@ -1362,7 +1204,7 @@ func (l *LanguageService) getCompletionData(
}
if importAttributes == nil {
- return globalsSearchContinue
+ return globalsSearchContinue, nil
}
var elements []*ast.Node
@@ -1379,7 +1221,7 @@ func (l *LanguageService) getCompletionData(
return !existing.Has(ast.SymbolName(symbol))
})
symbols = append(symbols, uniques...)
- return globalsSearchSuccess
+ return globalsSearchSuccess, nil
}
// Adds local declarations for completions in named exports:
@@ -1387,9 +1229,9 @@ func (l *LanguageService) getCompletionData(
// Does not check for the absence of a module specifier (`export {} from "./other"`)
// because `tryGetImportOrExportClauseCompletionSymbols` runs first and handles that,
// preventing this function from running.
- tryGetLocalNamedExportCompletionSymbols := func() globalsSearch {
+ tryGetLocalNamedExportCompletionSymbols := func() (globalsSearch, error) {
if contextToken == nil {
- return globalsSearchContinue
+ return globalsSearchContinue, nil
}
var namedExports *ast.NamedExportsNode
if contextToken.Kind == ast.KindOpenBraceToken || contextToken.Kind == ast.KindCommaToken {
@@ -1397,7 +1239,7 @@ func (l *LanguageService) getCompletionData(
}
if namedExports == nil {
- return globalsSearchContinue
+ return globalsSearchContinue, nil
}
localsContainer := ast.FindAncestor(namedExports, func(node *ast.Node) bool {
@@ -1418,12 +1260,12 @@ func (l *LanguageService) getCompletionData(
}
}
- return globalsSearchSuccess
+ return globalsSearchSuccess, nil
}
- tryGetConstructorCompletion := func() globalsSearch {
+ tryGetConstructorCompletion := func() (globalsSearch, error) {
if tryGetConstructorLikeCompletionContainer(contextToken) == nil {
- return globalsSearchContinue
+ return globalsSearchContinue, nil
}
// no members, only keywords
@@ -1432,15 +1274,15 @@ func (l *LanguageService) getCompletionData(
isNewIdentifierLocation = true
// Has keywords for constructor parameter
keywordFilters = KeywordCompletionFiltersConstructorParameterKeywords
- return globalsSearchSuccess
+ return globalsSearchSuccess, nil
}
// Aggregates relevant symbols for completion in class declaration
// Relevant symbols are stored in the captured 'symbols' variable.
- tryGetClassLikeCompletionSymbols := func() globalsSearch {
+ tryGetClassLikeCompletionSymbols := func() (globalsSearch, error) {
decl := tryGetObjectTypeDeclarationCompletionContainer(file, contextToken, location, position)
if decl == nil {
- return globalsSearchContinue
+ return globalsSearchContinue, nil
}
// We're looking up possible property names from parent type.
@@ -1457,7 +1299,7 @@ func (l *LanguageService) getCompletionData(
// If you're in an interface you don't want to repeat things from super-interface. So just stop here.
if !ast.IsClassLike(decl) {
- return globalsSearchSuccess
+ return globalsSearchSuccess, nil
}
var classElement *ast.Node
@@ -1524,18 +1366,18 @@ func (l *LanguageService) getCompletionData(
}
}
- return globalsSearchSuccess
+ return globalsSearchSuccess, nil
}
- tryGetJsxCompletionSymbols := func() globalsSearch {
+ tryGetJsxCompletionSymbols := func() (globalsSearch, error) {
jsxContainer := tryGetContainingJsxElement(contextToken, file)
if jsxContainer == nil {
- return globalsSearchContinue
+ return globalsSearchContinue, nil
}
// Cursor is inside a JSX self-closing element or opening element.
attrsType := typeChecker.GetContextualType(jsxContainer.Attributes(), checker.ContextFlagsNone)
if attrsType == nil {
- return globalsSearchContinue
+ return globalsSearchContinue, nil
}
completionsType := typeChecker.GetContextualType(jsxContainer.Attributes(), checker.ContextFlagsCompletions)
filteredSymbols, spreadMemberNames := filterJsxAttributes(
@@ -1563,10 +1405,10 @@ func (l *LanguageService) getCompletionData(
completionKind = CompletionKindMemberLike
isNewIdentifierLocation = false
- return globalsSearchSuccess
+ return globalsSearchSuccess, nil
}
- getGlobalCompletions := func() globalsSearch {
+ getGlobalCompletions := func() (globalsSearch, error) {
if tryGetFunctionLikeBodyCompletionContainer(contextToken) != nil {
keywordFilters = KeywordCompletionFiltersFunctionLikeBodyKeywords
} else {
@@ -1662,7 +1504,9 @@ func (l *LanguageService) getCompletionData(
}
}
- collectAutoImports()
+ if err := collectAutoImports(); err != nil {
+ return globalsSearchFail, err
+ }
if isTypeOnlyLocation {
if contextToken != nil && ast.IsAssertionExpression(contextToken.Parent) {
keywordFilters = KeywordCompletionFiltersTypeAssertionKeywords
@@ -1671,12 +1515,13 @@ func (l *LanguageService) getCompletionData(
}
}
- return globalsSearchSuccess
+ return globalsSearchSuccess, nil
}
- tryGetGlobalSymbols := func() bool {
+ tryGetGlobalSymbols := func() (bool, error) {
var result globalsSearch
- globalSearchFuncs := []func() globalsSearch{
+ var err error
+ globalSearchFuncs := []func() (globalsSearch, error){
tryGetObjectTypeLiteralInTypeArgumentCompletionSymbols,
tryGetObjectLikeCompletionSymbols,
tryGetImportCompletionSymbols,
@@ -1689,12 +1534,15 @@ func (l *LanguageService) getCompletionData(
getGlobalCompletions,
}
for _, globalSearchFunc := range globalSearchFuncs {
- result = globalSearchFunc()
+ result, err = globalSearchFunc()
+ if err != nil {
+ return false, err
+ }
if result != globalsSearchContinue {
break
}
}
- return result == globalsSearchSuccess
+ return result == globalsSearchSuccess, nil
}
if isRightOfDot || isRightOfQuestionDot {
@@ -1702,7 +1550,9 @@ func (l *LanguageService) getCompletionData(
} else if isRightOfOpenTag {
symbols = typeChecker.GetJsxIntrinsicTagNamesAt(location)
core.CheckEachDefined(symbols, "GetJsxIntrinsicTagNamesAt() should all be defined")
- tryGetGlobalSymbols()
+ if _, err := tryGetGlobalSymbols(); err != nil {
+ return nil, err
+ }
completionKind = CompletionKindGlobal
keywordFilters = KeywordCompletionFiltersNone
} else if isStartingCloseTag {
@@ -1717,11 +1567,14 @@ func (l *LanguageService) getCompletionData(
// For JavaScript or TypeScript, if we're not after a dot, then just try to get the
// global symbols in scope. These results should be valid for either language as
// the set of symbols that can be referenced from this location.
- if !tryGetGlobalSymbols() {
+ if ok, err := tryGetGlobalSymbols(); !ok {
+ if err != nil {
+ return nil, err
+ }
if keywordFilters != KeywordCompletionFiltersNone {
- return keywordCompletionData(keywordFilters, isJSOnlyLocation, isNewIdentifierLocation)
+ return keywordCompletionData(keywordFilters, isJSOnlyLocation, isNewIdentifierLocation), nil
}
- return nil
+ return nil, nil
}
}
@@ -1760,6 +1613,7 @@ func (l *LanguageService) getCompletionData(
return &completionDataData{
symbols: symbols,
+ autoImports: autoImports,
completionKind: completionKind,
isInSnippetScope: isInSnippetScope,
propertyAccessToConvert: propertyAccessToConvert,
@@ -1781,7 +1635,7 @@ func (l *LanguageService) getCompletionData(
importStatementCompletion: importStatementCompletion,
hasUnresolvedAutoImports: hasUnresolvedAutoImports,
defaultCommitCharacters: defaultCommitCharacters,
- }
+ }, nil
}
func keywordCompletionData(
@@ -1853,6 +1707,7 @@ func (l *LanguageService) completionInfoFromData(
uniqueNames, sortedEntries := l.getCompletionEntriesFromSymbols(
ctx,
+ typeChecker,
data,
nil, /*replacementToken*/
position,
@@ -1867,7 +1722,7 @@ func (l *LanguageService) completionInfoFromData(
!data.isTypeOnlyLocation && isContextualKeywordInAutoImportableExpressionSpace(keywordEntry.Label) ||
!uniqueNames.Has(keywordEntry.Label) {
uniqueNames.Add(keywordEntry.Label)
- sortedEntries = core.InsertSorted(sortedEntries, keywordEntry, compareCompletionEntries)
+ sortedEntries = append(sortedEntries, keywordEntry)
}
}
}
@@ -1875,14 +1730,14 @@ func (l *LanguageService) completionInfoFromData(
for _, keywordEntry := range getContextualKeywords(file, contextToken, position) {
if !uniqueNames.Has(keywordEntry.Label) {
uniqueNames.Add(keywordEntry.Label)
- sortedEntries = core.InsertSorted(sortedEntries, keywordEntry, compareCompletionEntries)
+ sortedEntries = append(sortedEntries, keywordEntry)
}
}
for _, literal := range literals {
literalEntry := createCompletionItemForLiteral(file, preferences, literal)
uniqueNames.Add(literalEntry.Label)
- sortedEntries = core.InsertSorted(sortedEntries, literalEntry, compareCompletionEntries)
+ sortedEntries = append(sortedEntries, literalEntry)
}
if !isChecked {
@@ -1915,6 +1770,7 @@ func (l *LanguageService) completionInfoFromData(
func (l *LanguageService) getCompletionEntriesFromSymbols(
ctx context.Context,
+ typeChecker *checker.Checker,
data *completionDataData,
replacementToken *ast.Node,
position int,
@@ -1923,9 +1779,8 @@ func (l *LanguageService) getCompletionEntriesFromSymbols(
) (uniqueNames collections.Set[string], sortedEntries []*lsproto.CompletionItem) {
closestSymbolDeclaration := getClosestSymbolDeclaration(data.contextToken, data.location)
useSemicolons := lsutil.ProbablyUsesSemicolons(file)
- typeChecker, done := l.GetProgram().GetTypeCheckerForFile(ctx, file)
- defer done()
isMemberCompletion := isMemberCompletionKind(data.completionKind)
+ sortedEntries = slices.Grow(sortedEntries, len(data.symbols)+len(data.autoImports))
// Tracks unique names.
// Value is set to false for global variables or completions from external module exports, because we can have multiple of those;
// true otherwise. Based on the order we add things we will always see locals first, then globals, then module exports.
@@ -1987,7 +1842,55 @@ func (l *LanguageService) getCompletionEntriesFromSymbols(
!(symbol.Parent == nil &&
!core.Some(symbol.Declarations, func(d *ast.Node) bool { return ast.GetSourceFileOfNode(d) == file }))
uniques[name] = shouldShadowLaterSymbols
- sortedEntries = core.InsertSorted(sortedEntries, entry, compareCompletionEntries)
+ sortedEntries = append(sortedEntries, entry)
+ }
+
+ for _, autoImport := range data.autoImports {
+ // !!! check for type-only in JS
+ // !!! deprecation
+
+ if data.importStatementCompletion != nil {
+ // !!!
+ continue
+ }
+
+ if !autoImport.Export.IsUnresolvedAlias() {
+ if data.isTypeOnlyLocation {
+ if autoImport.Export.Flags&ast.SymbolFlagsType == 0 && autoImport.Export.Flags&ast.SymbolFlagsModule == 0 {
+ continue
+ }
+ } else if autoImport.Export.Flags&ast.SymbolFlagsValue == 0 {
+ continue
+ }
+ }
+
+ entry := l.createLSPCompletionItem(
+ ctx,
+ autoImport.Fix.Name,
+ "",
+ "",
+ SortTextAutoImportSuggestions,
+ autoImport.Export.ScriptElementKind,
+ autoImport.Export.ScriptElementKindModifiers,
+ nil,
+ nil,
+ &lsproto.CompletionItemLabelDetails{
+ Description: ptrTo(autoImport.Fix.ModuleSpecifier),
+ },
+ file,
+ position,
+ false, /*isMemberCompletion*/
+ false, /*isSnippet*/
+ true, /*hasAction*/
+ false, /*preselect*/
+ autoImport.Fix.ModuleSpecifier,
+ autoImport.Fix.AutoImportFix,
+ )
+
+ if isShadowed, _ := uniques[autoImport.Fix.Name]; !isShadowed {
+ uniques[autoImport.Fix.Name] = false
+ sortedEntries = append(sortedEntries, entry)
+ }
}
uniqueSet := collections.NewSetWithSizeHint[string](len(uniques))
@@ -2043,7 +1946,6 @@ func (l *LanguageService) createCompletionItem(
compilerOptions *core.CompilerOptions,
isMemberCompletion bool,
) *lsproto.CompletionItem {
- clientOptions := lsproto.GetClientCapabilities(ctx).TextDocument.Completion
contextToken := data.contextToken
var insertText string
var filterText string
@@ -2141,46 +2043,6 @@ func (l *LanguageService) createCompletionItem(
file)
}
- if originIsExport(origin) {
- resolvedOrigin := origin.asExport()
- labelDetails = &lsproto.CompletionItemLabelDetails{
- Description: &resolvedOrigin.moduleSpecifier, // !!! vscode @link support
- }
- if data.importStatementCompletion != nil {
- quotedModuleSpecifier := escapeSnippetText(quote(file, preferences, resolvedOrigin.moduleSpecifier))
- exportKind := ExportKindNamed
- if origin.isDefaultExport {
- exportKind = ExportKindDefault
- } else if resolvedOrigin.exportName == ast.InternalSymbolNameExportEquals {
- exportKind = ExportKindExportEquals
- }
-
- insertText = "import "
- typeOnlyText := scanner.TokenToString(ast.KindTypeKeyword) + " "
- if data.importStatementCompletion.isTopLevelTypeOnly {
- insertText += typeOnlyText
- }
- tabStop := core.IfElse(clientOptions.CompletionItem.SnippetSupport, "$1", "")
- importKind := getImportKind(file, exportKind, l.GetProgram(), true /*forceImportKeyword*/)
- escapedSnippet := escapeSnippetText(name)
- suffix := core.IfElse(useSemicolons, ";", "")
- switch importKind {
- case ImportKindCommonJS:
- insertText += fmt.Sprintf(`%s%s = require(%s)%s`, escapedSnippet, tabStop, quotedModuleSpecifier, suffix)
- case ImportKindDefault:
- insertText += fmt.Sprintf(`%s%s from %s%s`, escapedSnippet, tabStop, quotedModuleSpecifier, suffix)
- case ImportKindNamespace:
- insertText += fmt.Sprintf(`* as %s from %s%s`, escapedSnippet, quotedModuleSpecifier, suffix)
- case ImportKindNamed:
- importSpecifierTypeOnlyText := core.IfElse(data.importStatementCompletion.couldBeTypeOnlyImportSpecifier, typeOnlyText, "")
- insertText += fmt.Sprintf(`{ %s%s%s } from %s%s`, importSpecifierTypeOnlyText, escapedSnippet, tabStop, quotedModuleSpecifier, suffix)
- }
-
- replacementSpan = data.importStatementCompletion.replacementSpan
- isSnippet = clientOptions.CompletionItem.SnippetSupport
- }
- }
-
if originIsTypeOnlyAlias(origin) {
hasAction = true
}
@@ -2264,12 +2126,6 @@ func (l *LanguageService) createCompletionItem(
}
}
- var autoImportData *lsproto.AutoImportData
- if originIsExport(origin) {
- autoImportData = origin.toCompletionEntryData()
- hasAction = data.importStatementCompletion == nil
- }
-
parentNamedImportOrExport := ast.FindAncestor(data.location, isNamedImportsOrExports)
if parentNamedImportOrExport != nil {
if !scanner.IsIdentifierText(name, core.LanguageVariantStandard) {
@@ -2288,7 +2144,7 @@ func (l *LanguageService) createCompletionItem(
} else if parentNamedImportOrExport.Kind == ast.KindNamedImports {
possibleToken := scanner.StringToToken(name)
if possibleToken != ast.KindUnknown &&
- (possibleToken == ast.KindAwaitKeyword || isNonContextualKeyword(possibleToken)) {
+ (possibleToken == ast.KindAwaitKeyword || lsutil.IsNonContextualKeyword(possibleToken)) {
insertText = fmt.Sprintf("%s as %s_", name, name)
}
}
@@ -2296,10 +2152,10 @@ func (l *LanguageService) createCompletionItem(
// Commit characters
- elementKind := getSymbolKind(typeChecker, symbol, data.location)
+ elementKind := lsutil.GetSymbolKind(typeChecker, symbol, data.location)
var commitCharacters *[]string
if clientSupportsItemCommitCharacters(ctx) {
- if elementKind == ScriptElementKindWarning || elementKind == ScriptElementKindString {
+ if elementKind == lsutil.ScriptElementKindWarning || elementKind == lsutil.ScriptElementKindString {
commitCharacters = &[]string{}
} else if !clientSupportsDefaultCommitCharacters(ctx) {
commitCharacters = ptrTo(data.defaultCommitCharacters)
@@ -2308,7 +2164,7 @@ func (l *LanguageService) createCompletionItem(
}
preselect := isRecommendedCompletionMatch(symbol, data.recommendedCompletion, typeChecker)
- kindModifiers := getSymbolModifiers(typeChecker, symbol)
+ kindModifiers := lsutil.GetSymbolModifiers(typeChecker, symbol)
return l.createLSPCompletionItem(
ctx,
@@ -2328,7 +2184,7 @@ func (l *LanguageService) createCompletionItem(
hasAction,
preselect,
source,
- autoImportData,
+ nil,
)
}
@@ -2531,7 +2387,7 @@ func isClassLikeMemberCompletion(symbol *ast.Symbol, location *ast.Node, file *a
}
func symbolAppearsToBeTypeOnly(symbol *ast.Symbol, typeChecker *checker.Checker) bool {
- flags := checker.GetCombinedLocalAndExportSymbolFlags(checker.SkipAlias(symbol, typeChecker))
+ flags := checker.SkipAlias(symbol, typeChecker).CombinedLocalAndExportSymbolFlags()
return flags&ast.SymbolFlagsValue == 0 &&
(len(symbol.Declarations) == 0 || !ast.IsInJSFile(symbol.Declarations[0]) || flags&ast.SymbolFlagsType != 0)
}
@@ -2603,13 +2459,13 @@ func shouldIncludeSymbol(
// Auto Imports are not available for scripts so this conditional is always false.
if file.AsSourceFile().ExternalModuleIndicator != nil &&
compilerOptions.AllowUmdGlobalAccess != core.TSTrue &&
+ symbol != symbolOrigin &&
data.symbolToSortTextMap[ast.GetSymbolId(symbol)] == SortTextGlobalsOrKeywords &&
- (data.symbolToSortTextMap[ast.GetSymbolId(symbolOrigin)] == SortTextAutoImportSuggestions ||
- data.symbolToSortTextMap[ast.GetSymbolId(symbolOrigin)] == SortTextLocationPriority) {
+ symbol.Parent != nil && checker.IsExternalModuleSymbol(symbol.Parent) {
return false
}
- allFlags = allFlags | checker.GetCombinedLocalAndExportSymbolFlags(symbolOrigin)
+ allFlags = allFlags | symbolOrigin.CombinedLocalAndExportSymbolFlags()
// import m = /**/ <-- It can only access namespace (if typing import = x. this would get member symbols and not namespace)
if isInRightSideOfInternalImportEqualsDeclaration(data.location) {
@@ -2692,11 +2548,7 @@ func originIsIgnore(origin *symbolOriginInfo) bool {
}
func originIncludesSymbolName(origin *symbolOriginInfo) bool {
- return originIsExport(origin) || originIsComputedPropertyName(origin)
-}
-
-func originIsExport(origin *symbolOriginInfo) bool {
- return origin != nil && origin.kind&symbolOriginInfoKindExport != 0
+ return originIsComputedPropertyName(origin)
}
func originIsComputedPropertyName(origin *symbolOriginInfo) bool {
@@ -2728,14 +2580,6 @@ func originIsPromise(origin *symbolOriginInfo) bool {
}
func getSourceFromOrigin(origin *symbolOriginInfo) string {
- if originIsExport(origin) {
- return stringutil.StripQuotes(ast.SymbolName(origin.asExport().moduleSymbol))
- }
-
- if originIsExport(origin) {
- return origin.asExport().moduleSpecifier
- }
-
if originIsThisType(origin) {
return string(completionSourceThisProperty)
}
@@ -2785,7 +2629,7 @@ func isValidTrigger(file *ast.SourceFile, triggerCharacter CompletionsTriggerCha
return false
}
if ast.IsStringLiteralLike(contextToken) {
- return tryGetImportFromModuleSpecifier(contextToken) != nil
+ return ast.TryGetImportFromModuleSpecifier(contextToken) != nil
}
return contextToken.Kind == ast.KindLessThanSlashToken && ast.IsJsxClosingElement(contextToken.Parent)
case " ":
@@ -3264,50 +3108,50 @@ func generateIdentifierForArbitraryString(text string) string {
}
// Copied from vscode TS extension.
-func getCompletionsSymbolKind(kind ScriptElementKind) lsproto.CompletionItemKind {
+func getCompletionsSymbolKind(kind lsutil.ScriptElementKind) lsproto.CompletionItemKind {
switch kind {
- case ScriptElementKindPrimitiveType, ScriptElementKindKeyword:
+ case lsutil.ScriptElementKindPrimitiveType, lsutil.ScriptElementKindKeyword:
return lsproto.CompletionItemKindKeyword
- case ScriptElementKindConstElement, ScriptElementKindLetElement, ScriptElementKindVariableElement,
- ScriptElementKindLocalVariableElement, ScriptElementKindAlias, ScriptElementKindParameterElement:
+ case lsutil.ScriptElementKindConstElement, lsutil.ScriptElementKindLetElement, lsutil.ScriptElementKindVariableElement,
+ lsutil.ScriptElementKindLocalVariableElement, lsutil.ScriptElementKindAlias, lsutil.ScriptElementKindParameterElement:
return lsproto.CompletionItemKindVariable
- case ScriptElementKindMemberVariableElement, ScriptElementKindMemberGetAccessorElement,
- ScriptElementKindMemberSetAccessorElement:
+ case lsutil.ScriptElementKindMemberVariableElement, lsutil.ScriptElementKindMemberGetAccessorElement,
+ lsutil.ScriptElementKindMemberSetAccessorElement:
return lsproto.CompletionItemKindField
- case ScriptElementKindFunctionElement, ScriptElementKindLocalFunctionElement:
+ case lsutil.ScriptElementKindFunctionElement, lsutil.ScriptElementKindLocalFunctionElement:
return lsproto.CompletionItemKindFunction
- case ScriptElementKindMemberFunctionElement, ScriptElementKindConstructSignatureElement,
- ScriptElementKindCallSignatureElement, ScriptElementKindIndexSignatureElement:
+ case lsutil.ScriptElementKindMemberFunctionElement, lsutil.ScriptElementKindConstructSignatureElement,
+ lsutil.ScriptElementKindCallSignatureElement, lsutil.ScriptElementKindIndexSignatureElement:
return lsproto.CompletionItemKindMethod
- case ScriptElementKindEnumElement:
+ case lsutil.ScriptElementKindEnumElement:
return lsproto.CompletionItemKindEnum
- case ScriptElementKindEnumMemberElement:
+ case lsutil.ScriptElementKindEnumMemberElement:
return lsproto.CompletionItemKindEnumMember
- case ScriptElementKindModuleElement, ScriptElementKindExternalModuleName:
+ case lsutil.ScriptElementKindModuleElement, lsutil.ScriptElementKindExternalModuleName:
return lsproto.CompletionItemKindModule
- case ScriptElementKindClassElement, ScriptElementKindTypeElement:
+ case lsutil.ScriptElementKindClassElement, lsutil.ScriptElementKindTypeElement:
return lsproto.CompletionItemKindClass
- case ScriptElementKindInterfaceElement:
+ case lsutil.ScriptElementKindInterfaceElement:
return lsproto.CompletionItemKindInterface
- case ScriptElementKindWarning:
+ case lsutil.ScriptElementKindWarning:
return lsproto.CompletionItemKindText
- case ScriptElementKindScriptElement:
+ case lsutil.ScriptElementKindScriptElement:
return lsproto.CompletionItemKindFile
- case ScriptElementKindDirectory:
+ case lsutil.ScriptElementKindDirectory:
return lsproto.CompletionItemKindFolder
- case ScriptElementKindString:
+ case lsutil.ScriptElementKindString:
return lsproto.CompletionItemKindConstant
default:
@@ -3319,83 +3163,15 @@ func getCompletionsSymbolKind(kind ScriptElementKind) lsproto.CompletionItemKind
// So, it's important that we sort those ties in the order we want them displayed if it matters. We don't
// strictly need to sort by name or SortText here since clients are going to do it anyway, but we have to
// do the work of comparing them so we can sort those ties appropriately.
-func compareCompletionEntries(entryInSlice *lsproto.CompletionItem, entryToInsert *lsproto.CompletionItem) int {
+func CompareCompletionEntries(a, b *lsproto.CompletionItem) int {
compareStrings := stringutil.CompareStringsCaseInsensitiveThenSensitive
- result := compareStrings(*entryInSlice.SortText, *entryToInsert.SortText)
+ result := compareStrings(*a.SortText, *b.SortText)
if result == stringutil.ComparisonEqual {
- result = compareStrings(entryInSlice.Label, entryToInsert.Label)
- }
- if result == stringutil.ComparisonEqual && entryInSlice.Data != nil && entryToInsert.Data != nil {
- sliceEntryData := entryInSlice.Data
- insertEntryData := entryToInsert.Data
- if sliceEntryData.AutoImport != nil && sliceEntryData.AutoImport.ModuleSpecifier != "" &&
- insertEntryData.AutoImport != nil && insertEntryData.AutoImport.ModuleSpecifier != "" {
- // Sort same-named auto-imports by module specifier
- result = tspath.CompareNumberOfDirectorySeparators(
- sliceEntryData.AutoImport.ModuleSpecifier,
- insertEntryData.AutoImport.ModuleSpecifier,
- )
- if result == stringutil.ComparisonEqual {
- result = compareStrings(
- sliceEntryData.AutoImport.ModuleSpecifier,
- insertEntryData.AutoImport.ModuleSpecifier,
- )
- }
- }
- }
- if result == stringutil.ComparisonEqual {
- // Fall back to symbol order - if we return `EqualTo`, `insertSorted` will put later symbols first.
- return stringutil.ComparisonLessThan
+ result = compareStrings(a.Label, b.Label)
}
return result
}
-// True if the first character of `lowercaseCharacters` is the first character
-// of some "word" in `identiferString` (where the string is split into "words"
-// by camelCase and snake_case segments), then if the remaining characters of
-// `lowercaseCharacters` appear, in order, in the rest of `identifierString`.//
-// True:
-// 'state' in 'useState'
-// 'sae' in 'useState'
-// 'viable' in 'ENVIRONMENT_VARIABLE'//
-// False:
-// 'staet' in 'useState'
-// 'tate' in 'useState'
-// 'ment' in 'ENVIRONMENT_VARIABLE'
-func charactersFuzzyMatchInString(identifierString string, lowercaseCharacters string) bool {
- if lowercaseCharacters == "" {
- return true
- }
-
- var prevChar rune
- matchedFirstCharacter := false
- characterIndex := 0
- lowerCaseRunes := []rune(lowercaseCharacters)
- testChar := lowerCaseRunes[characterIndex]
-
- for _, strChar := range []rune(identifierString) {
- if strChar == testChar || strChar == unicode.ToUpper(testChar) {
- willMatchFirstChar := prevChar == 0 || // Beginning of word
- 'a' <= prevChar && prevChar <= 'z' && 'A' <= strChar && strChar <= 'Z' || // camelCase transition
- prevChar == '_' && strChar != '_' // snake_case transition
- matchedFirstCharacter = matchedFirstCharacter || willMatchFirstChar
- if !matchedFirstCharacter {
- continue
- }
- characterIndex++
- if characterIndex == len(lowerCaseRunes) {
- return true
- } else {
- testChar = lowerCaseRunes[characterIndex]
- }
- }
- prevChar = strChar
- }
-
- // Did not find all characters
- return false
-}
-
var (
keywordCompletionsCache = collections.SyncMap[KeywordCompletionFilters, []*lsproto.CompletionItem]{}
allKeywordCompletions = sync.OnceValue(func() []*lsproto.CompletionItem {
@@ -3590,16 +3366,12 @@ func (l *LanguageService) getJSCompletionEntries(
}
if !uniqueNames.Has(name) && scanner.IsIdentifierText(name, core.LanguageVariantStandard) {
uniqueNames.Add(name)
- sortedEntries = core.InsertSorted(
- sortedEntries,
- &lsproto.CompletionItem{
- Label: name,
- Kind: ptrTo(lsproto.CompletionItemKindText),
- SortText: ptrTo(string(SortTextJavascriptIdentifiers)),
- CommitCharacters: ptrTo([]string{}),
- },
- compareCompletionEntries,
- )
+ sortedEntries = append(sortedEntries, &lsproto.CompletionItem{
+ Label: name,
+ Kind: ptrTo(lsproto.CompletionItemKindText),
+ SortText: ptrTo(string(SortTextJavascriptIdentifiers)),
+ CommitCharacters: ptrTo([]string{}),
+ })
}
}
return sortedEntries
@@ -4498,8 +4270,8 @@ func (l *LanguageService) getJsxClosingTagCompletion(
"", /*insertText*/
"", /*filterText*/
SortTextLocationPriority,
- ScriptElementKindClassElement,
- collections.Set[ScriptElementKindModifier]{}, /*kindModifiers*/
+ lsutil.ScriptElementKindClassElement,
+ collections.Set[lsutil.ScriptElementKindModifier]{}, /*kindModifiers*/
nil, /*replacementSpan*/
nil, /*commitCharacters*/
nil, /*labelDetails*/
@@ -4535,8 +4307,8 @@ func (l *LanguageService) createLSPCompletionItem(
insertText string,
filterText string,
sortText SortText,
- elementKind ScriptElementKind,
- kindModifiers collections.Set[ScriptElementKindModifier],
+ elementKind lsutil.ScriptElementKind,
+ kindModifiers collections.Set[lsutil.ScriptElementKindModifier],
replacementSpan *lsproto.Range,
commitCharacters *[]string,
labelDetails *lsproto.CompletionItemLabelDetails,
@@ -4547,7 +4319,7 @@ func (l *LanguageService) createLSPCompletionItem(
hasAction bool,
preselect bool,
source string,
- autoImportEntryData *lsproto.AutoImportData,
+ autoImportFix *lsproto.AutoImportFix,
) *lsproto.CompletionItem {
kind := getCompletionsSymbolKind(elementKind)
data := &lsproto.CompletionItemData{
@@ -4555,7 +4327,7 @@ func (l *LanguageService) createLSPCompletionItem(
Position: int32(position),
Source: source,
Name: name,
- AutoImport: autoImportEntryData,
+ AutoImport: autoImportFix,
}
// Text edit
@@ -4582,7 +4354,7 @@ func (l *LanguageService) createLSPCompletionItem(
var tags *[]lsproto.CompletionItemTag
var detail *string
// Copied from vscode ts extension: `MyCompletionItem.constructor`.
- if kindModifiers.Has(ScriptElementKindModifierOptional) {
+ if kindModifiers.Has(lsutil.ScriptElementKindModifierOptional) {
if insertText == "" {
insertText = name
}
@@ -4591,11 +4363,11 @@ func (l *LanguageService) createLSPCompletionItem(
}
name = name + "?"
}
- if kindModifiers.Has(ScriptElementKindModifierDeprecated) {
+ if kindModifiers.Has(lsutil.ScriptElementKindModifierDeprecated) {
tags = &[]lsproto.CompletionItemTag{lsproto.CompletionItemTagDeprecated}
}
if kind == lsproto.CompletionItemKindFile {
- for _, extensionModifier := range fileExtensionKindModifiers {
+ for _, extensionModifier := range lsutil.FileExtensionKindModifiers {
if kindModifiers.Has(extensionModifier) {
if strings.HasSuffix(name, string(extensionModifier)) {
detail = ptrTo(name)
@@ -4684,8 +4456,8 @@ func (l *LanguageService) getLabelStatementCompletions(
"", /*insertText*/
"", /*filterText*/
SortTextLocationPriority,
- ScriptElementKindLabel,
- collections.Set[ScriptElementKindModifier]{}, /*kindModifiers*/
+ lsutil.ScriptElementKindLabel,
+ collections.Set[lsutil.ScriptElementKindModifier]{}, /*kindModifiers*/
nil, /*replacementSpan*/
nil, /*commitCharacters*/
nil, /*labelDetails*/
@@ -4972,16 +4744,6 @@ func getArgumentInfoForCompletions(node *ast.Node, position int, file *ast.Sourc
}
}
-func autoImportDataToSymbolOriginExport(d *lsproto.AutoImportData, symbolName string, moduleSymbol *ast.Symbol, isDefaultExport bool) *symbolOriginInfoExport {
- return &symbolOriginInfoExport{
- symbolName: symbolName,
- moduleSymbol: moduleSymbol,
- exportName: d.ExportName,
- exportMapKey: d.ExportMapKey,
- moduleSpecifier: d.ModuleSpecifier,
- }
-}
-
// Special values for `CompletionInfo['source']` used to disambiguate
// completion items with the same `name`. (Each completion item must
// have a unique name/source combination, because those two fields
@@ -5020,7 +4782,9 @@ func (l *LanguageService) ResolveCompletionItem(
return nil, fmt.Errorf("file not found: %s", data.FileName)
}
- return l.getCompletionItemDetails(ctx, program, int(data.Position), file, item, data), nil
+ checker, done := program.GetTypeCheckerForFile(ctx, file)
+ defer done()
+ return l.getCompletionItemDetails(ctx, program, checker, int(data.Position), file, item, data), nil
}
func getCompletionDocumentationFormat(ctx context.Context) lsproto.MarkupKind {
@@ -5030,13 +4794,12 @@ func getCompletionDocumentationFormat(ctx context.Context) lsproto.MarkupKind {
func (l *LanguageService) getCompletionItemDetails(
ctx context.Context,
program *compiler.Program,
+ checker *checker.Checker,
position int,
file *ast.SourceFile,
item *lsproto.CompletionItem,
data *lsproto.CompletionItemData,
) *lsproto.CompletionItem {
- checker, done := program.GetTypeCheckerForFile(ctx, file)
- defer done()
docFormat := getCompletionDocumentationFormat(ctx)
contextToken, previousToken := getRelevantTokens(position, file)
if IsInString(file, position, previousToken) {
@@ -5052,6 +4815,13 @@ func (l *LanguageService) getCompletionItemDetails(
)
}
+ if data.AutoImport != nil {
+ edits, description := (&autoimport.Fix{AutoImportFix: data.AutoImport}).Edits(ctx, file, program.Options(), l.FormatOptions(), l.converters, l.UserPreferences())
+ item.AdditionalTextEdits = &edits
+ item.Detail = strPtrTo(description)
+ return item
+ }
+
// Compute all the completion symbols again.
symbolCompletion := l.getSymbolCompletionFromItemData(
ctx,
@@ -5084,13 +4854,12 @@ func (l *LanguageService) getCompletionItemDetails(
}
case symbolCompletion.symbol != nil:
symbolDetails := symbolCompletion.symbol
- actions := l.getCompletionItemActions(ctx, checker, file, position, data, symbolDetails)
return l.createCompletionDetailsForSymbol(
item,
symbolDetails.symbol,
checker,
symbolDetails.location,
- actions,
+ nil,
docFormat,
)
case symbolCompletion.literal != nil:
@@ -5139,17 +4908,12 @@ func (l *LanguageService) getSymbolCompletionFromItemData(
cases: &struct{}{},
}
}
- if itemData.AutoImport != nil {
- if autoImportSymbolData := l.getAutoImportSymbolFromCompletionEntryData(ch, itemData.AutoImport.ExportName, itemData.AutoImport); autoImportSymbolData != nil {
- autoImportSymbolData.contextToken, autoImportSymbolData.previousToken = getRelevantTokens(position, file)
- autoImportSymbolData.location = astnav.GetTouchingPropertyName(file, position)
- autoImportSymbolData.jsxInitializer = jsxInitializer{false, nil}
- autoImportSymbolData.isTypeOnlyLocation = false
- return detailsData{symbol: autoImportSymbolData}
- }
+
+ completionData, err := l.getCompletionData(ctx, ch, file, position, &lsutil.UserPreferences{IncludeCompletionsForModuleExports: core.TSTrue, IncludeCompletionsForImportStatements: core.TSTrue})
+ if err != nil {
+ panic(err)
}
- completionData := l.getCompletionData(ctx, ch, file, position, &lsutil.UserPreferences{IncludeCompletionsForModuleExports: core.TSTrue, IncludeCompletionsForImportStatements: core.TSTrue})
if completionData == nil {
return detailsData{}
}
@@ -5204,49 +4968,6 @@ func (l *LanguageService) getSymbolCompletionFromItemData(
return detailsData{}
}
-func (l *LanguageService) getAutoImportSymbolFromCompletionEntryData(ch *checker.Checker, name string, autoImportData *lsproto.AutoImportData) *symbolDetails {
- containingProgram := l.GetProgram() // !!! isPackageJson ? packageJsonAutoimportProvider : program
- var moduleSymbol *ast.Symbol
- if autoImportData.AmbientModuleName != "" {
- moduleSymbol = ch.TryFindAmbientModule(autoImportData.AmbientModuleName)
- } else if autoImportData.FileName != "" {
- moduleSymbolSourceFile := containingProgram.GetSourceFile(autoImportData.FileName)
- if moduleSymbolSourceFile == nil {
- panic("module sourceFile not found: " + autoImportData.FileName)
- }
- moduleSymbol = ch.GetMergedSymbol(moduleSymbolSourceFile.Symbol)
- }
- if moduleSymbol == nil {
- return nil
- }
-
- var symbol *ast.Symbol
- if autoImportData.ExportName == ast.InternalSymbolNameExportEquals {
- symbol = ch.ResolveExternalModuleSymbol(moduleSymbol)
- } else {
- symbol = ch.TryGetMemberInModuleExportsAndProperties(autoImportData.ExportName, moduleSymbol)
- }
- if symbol == nil {
- return nil
- }
-
- isDefaultExport := autoImportData.ExportName == ast.InternalSymbolNameDefault
- if isDefaultExport {
- if localSymbol := binder.GetLocalSymbolForExportDefault(symbol); localSymbol != nil {
- symbol = localSymbol
- }
- }
- origin := &symbolOriginInfo{
- kind: symbolOriginInfoKindExport,
- fileName: autoImportData.FileName,
- isFromPackageJson: autoImportData.IsPackageJsonImport,
- isDefaultExport: isDefaultExport,
- data: autoImportDataToSymbolOriginExport(autoImportData, name, moduleSymbol, isDefaultExport),
- }
-
- return &symbolDetails{symbol: symbol, origin: origin}
-}
-
func createSimpleDetails(
item *lsproto.CompletionItem,
name string,
@@ -5305,60 +5026,6 @@ func (l *LanguageService) createCompletionDetailsForSymbol(
return createCompletionDetails(item, strings.Join(details, "\n\n"), documentation, docFormat)
}
-// !!! snippets
-func (l *LanguageService) getCompletionItemActions(ctx context.Context, ch *checker.Checker, file *ast.SourceFile, position int, itemData *lsproto.CompletionItemData, symbolDetails *symbolDetails) []codeAction {
- if itemData.AutoImport != nil && itemData.AutoImport.ModuleSpecifier != "" && symbolDetails.previousToken != nil {
- // Import statement completion: 'import c|'
- if symbolDetails.contextToken != nil && l.getImportStatementCompletionInfo(symbolDetails.contextToken, file).replacementSpan != nil {
- return nil
- } else if l.getImportStatementCompletionInfo(symbolDetails.previousToken, file).replacementSpan != nil {
- return nil // !!! sourceDisplay [textPart(data.moduleSpecifier)]
- }
- }
- // !!! CompletionSource.ClassMemberSnippet
- // !!! origin.isTypeOnlyAlias
- // entryId.source == CompletionSourceObjectLiteralMemberWithComma && contextToken
-
- if symbolDetails.origin == nil || symbolDetails.origin.data == nil {
- return nil
- }
-
- symbol := symbolDetails.symbol
- if symbol.ExportSymbol != nil {
- symbol = symbol.ExportSymbol
- }
- targetSymbol := ch.GetMergedSymbol(ch.SkipAlias(symbol))
- isJsxOpeningTagName := symbolDetails.contextToken != nil && symbolDetails.contextToken.Kind == ast.KindLessThanToken && ast.IsJsxOpeningLikeElement(symbolDetails.contextToken.Parent)
- if symbolDetails.previousToken != nil && ast.IsIdentifier(symbolDetails.previousToken) {
- // If the previous token is an identifier, we can use its start position.
- position = astnav.GetStartOfNode(symbolDetails.previousToken, file, false)
- }
-
- moduleSymbol := symbolDetails.origin.moduleSymbol()
-
- var exportMapkey lsproto.ExportInfoMapKey
- if itemData.AutoImport != nil {
- exportMapkey = itemData.AutoImport.ExportMapKey
- }
- moduleSpecifier, importCompletionAction := l.getImportCompletionAction(
- ctx,
- ch,
- targetSymbol,
- moduleSymbol,
- file,
- position,
- exportMapkey,
- itemData.Name,
- isJsxOpeningTagName,
- // formatContext,
- )
-
- if !(moduleSpecifier == itemData.AutoImport.ModuleSpecifier || itemData.AutoImport.ModuleSpecifier == "") {
- panic("")
- }
- return []codeAction{importCompletionAction}
-}
-
func (l *LanguageService) getImportStatementCompletionInfo(contextToken *ast.Node, sourceFile *ast.SourceFile) importStatementCompletionInfo {
result := importStatementCompletionInfo{}
var candidate *ast.Node
@@ -5516,7 +5183,7 @@ func getPotentiallyInvalidImportSpecifier(namedBindings *ast.NamedImportBindings
return nil
}
return core.Find(namedBindings.Elements(), func(e *ast.Node) bool {
- return e.PropertyName() == nil && isNonContextualKeyword(scanner.StringToToken(e.Name().Text())) &&
+ return e.PropertyName() == nil && lsutil.IsNonContextualKeyword(scanner.StringToToken(e.Name().Text())) &&
astnav.FindPrecedingToken(ast.GetSourceFileOfNode(namedBindings), e.Name().Pos()).Kind != ast.KindCommaToken
})
}
@@ -5896,9 +5563,9 @@ func getJSDocParamAnnotation(
inferredType := typeChecker.GetTypeAtLocation(initializer.Parent)
if inferredType.Flags()&(checker.TypeFlagsAny|checker.TypeFlagsVoid) == 0 {
file := ast.GetSourceFileOfNode(initializer)
- quotePreference := getQuotePreference(file, preferences)
+ quotePreference := lsutil.GetQuotePreference(file, preferences)
builderFlags := core.IfElse(
- quotePreference == quotePreferenceSingle,
+ quotePreference == lsutil.QuotePreferenceSingle,
nodebuilder.FlagsUseSingleQuotesForStringLiteralType,
nodebuilder.FlagsNone,
)
diff --git a/internal/ls/findallreferences.go b/internal/ls/findallreferences.go
index 5744271afa..13b847c003 100644
--- a/internal/ls/findallreferences.go
+++ b/internal/ls/findallreferences.go
@@ -198,7 +198,7 @@ func getContextNodeForNodeEntry(node *ast.Node) *ast.Node {
case ast.KindJsxSelfClosingElement, ast.KindLabeledStatement, ast.KindBreakStatement, ast.KindContinueStatement:
return node.Parent
case ast.KindStringLiteral, ast.KindNoSubstitutionTemplateLiteral:
- if validImport := tryGetImportFromModuleSpecifier(node); validImport != nil {
+ if validImport := ast.TryGetImportFromModuleSpecifier(node); validImport != nil {
declOrStatement := ast.FindAncestor(validImport, func(*ast.Node) bool {
return ast.IsDeclaration(node) || ast.IsStatement(node) || ast.IsJSDocTag(node)
})
@@ -736,7 +736,7 @@ func (l *LanguageService) symbolAndEntriesToRename(ctx context.Context, params *
defer done()
for _, entry := range entries {
uri := l.getFileNameOfEntry(entry)
- if l.UserPreferences().AllowRenameOfImportPath != core.TSTrue && entry.node != nil && ast.IsStringLiteralLike(entry.node) && tryGetImportFromModuleSpecifier(entry.node) != nil {
+ if l.UserPreferences().AllowRenameOfImportPath != core.TSTrue && entry.node != nil && ast.IsStringLiteralLike(entry.node) && ast.TryGetImportFromModuleSpecifier(entry.node) != nil {
continue
}
textEdit := &lsproto.TextEdit{
diff --git a/internal/ls/host.go b/internal/ls/host.go
index 8f517b787c..a0dc31967a 100644
--- a/internal/ls/host.go
+++ b/internal/ls/host.go
@@ -2,6 +2,7 @@ package ls
import (
"github.com/microsoft/typescript-go/internal/format"
+ "github.com/microsoft/typescript-go/internal/ls/autoimport"
"github.com/microsoft/typescript-go/internal/ls/lsconv"
"github.com/microsoft/typescript-go/internal/ls/lsutil"
"github.com/microsoft/typescript-go/internal/sourcemap"
@@ -14,4 +15,5 @@ type Host interface {
UserPreferences() *lsutil.UserPreferences
FormatOptions() *format.FormatCodeSettings
GetECMALineInfo(fileName string) *sourcemap.ECMALineInfo
+ AutoImportRegistry() *autoimport.Registry
}
diff --git a/internal/ls/importTracker.go b/internal/ls/importTracker.go
index 88f6febb4c..630b0b3d83 100644
--- a/internal/ls/importTracker.go
+++ b/internal/ls/importTracker.go
@@ -25,6 +25,16 @@ type ImportExportSymbol struct {
exportInfo *ExportInfo
}
+type ExportKind int
+
+const (
+ ExportKindNamed ExportKind = 0
+ ExportKindDefault ExportKind = 1
+ ExportKindExportEquals ExportKind = 2
+ ExportKindUMD ExportKind = 3
+ ExportKindModule ExportKind = 4
+)
+
type ExportInfo struct {
exportingModuleSymbol *ast.Symbol
exportKind ExportKind
@@ -87,7 +97,7 @@ func getDirectImportsMap(sourceFiles []*ast.SourceFile, checker *checker.Checker
func forEachImport(sourceFile *ast.SourceFile, action func(importStatement *ast.Node, imported *ast.Node)) {
if sourceFile.ExternalModuleIndicator != nil || len(sourceFile.Imports()) != 0 {
for _, i := range sourceFile.Imports() {
- action(importFromModuleSpecifier(i), i)
+ action(ast.ImportFromModuleSpecifier(i), i)
}
} else {
forEachPossibleImportOrExportStatement(sourceFile.AsNode(), func(node *ast.Node) bool {
diff --git a/internal/ls/inlay_hints.go b/internal/ls/inlay_hints.go
index 5db495a290..81b87f3858 100644
--- a/internal/ls/inlay_hints.go
+++ b/internal/ls/inlay_hints.go
@@ -32,7 +32,7 @@ func (l *LanguageService) ProvideInlayHint(
}
program, file := l.getProgramAndFile(params.TextDocument.Uri)
- quotePreference := getQuotePreference(file, userPreferences)
+ quotePreference := lsutil.GetQuotePreference(file, userPreferences)
checker, done := program.GetTypeCheckerForFile(ctx, file)
defer done()
@@ -53,7 +53,7 @@ type inlayHintState struct {
ctx context.Context
span core.TextRange
preferences *lsutil.InlayHintsPreferences
- quotePreference quotePreference
+ quotePreference lsutil.QuotePreference
file *ast.SourceFile
checker *checker.Checker
converters *lsconv.Converters
@@ -772,7 +772,7 @@ func (s *inlayHintState) getNodeDisplayPart(text string, node *ast.Node) *lsprot
func (s *inlayHintState) getLiteralText(node *ast.LiteralLikeNode) string {
switch node.Kind {
case ast.KindStringLiteral:
- if s.quotePreference == quotePreferenceSingle {
+ if s.quotePreference == lsutil.QuotePreferenceSingle {
return `'` + printer.EscapeString(node.Text(), printer.QuoteCharSingleQuote) + `'`
}
return `"` + printer.EscapeString(node.Text(), printer.QuoteCharDoubleQuote) + `"`
diff --git a/internal/ls/languageservice.go b/internal/ls/languageservice.go
index 2cfe172468..405bb7d7f1 100644
--- a/internal/ls/languageservice.go
+++ b/internal/ls/languageservice.go
@@ -4,6 +4,7 @@ import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/compiler"
"github.com/microsoft/typescript-go/internal/format"
+ "github.com/microsoft/typescript-go/internal/ls/autoimport"
"github.com/microsoft/typescript-go/internal/ls/lsconv"
"github.com/microsoft/typescript-go/internal/ls/lsutil"
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
@@ -12,6 +13,7 @@ import (
)
type LanguageService struct {
+ projectPath tspath.Path
host Host
program *compiler.Program
converters *lsconv.Converters
@@ -19,10 +21,12 @@ type LanguageService struct {
}
func NewLanguageService(
+ projectPath tspath.Path,
program *compiler.Program,
host Host,
) *LanguageService {
return &LanguageService{
+ projectPath: projectPath,
host: host,
program: program,
converters: host.Converters(),
@@ -84,3 +88,27 @@ func (l *LanguageService) UseCaseSensitiveFileNames() bool {
func (l *LanguageService) GetECMALineInfo(fileName string) *sourcemap.ECMALineInfo {
return l.host.GetECMALineInfo(fileName)
}
+
+// getPreparedAutoImportView returns an auto-import view for the given file if the registry is prepared
+// to provide up-to-date auto-imports for it. If not, it returns ErrNeedsAutoImports.
+func (l *LanguageService) getPreparedAutoImportView(fromFile *ast.SourceFile) (*autoimport.View, error) {
+ registry := l.host.AutoImportRegistry()
+ if !registry.IsPreparedForImportingFile(fromFile.FileName(), l.projectPath, l.UserPreferences()) {
+ return nil, ErrNeedsAutoImports
+ }
+
+ view := autoimport.NewView(registry, fromFile, l.projectPath, l.program, l.UserPreferences().ModuleSpecifierPreferences())
+ return view, nil
+}
+
+// getCurrentAutoImportView returns an auto-import view for the given file, based on the current state
+// of the auto-import registry, which may or may not be up-to-date.
+func (l *LanguageService) getCurrentAutoImportView(fromFile *ast.SourceFile) *autoimport.View {
+ return autoimport.NewView(
+ l.host.AutoImportRegistry(),
+ fromFile,
+ l.projectPath,
+ l.program,
+ l.UserPreferences().ModuleSpecifierPreferences(),
+ )
+}
diff --git a/internal/ls/symbol_display.go b/internal/ls/lsutil/symbol_display.go
similarity index 74%
rename from internal/ls/symbol_display.go
rename to internal/ls/lsutil/symbol_display.go
index 328f79bda5..c421d4b6fc 100644
--- a/internal/ls/symbol_display.go
+++ b/internal/ls/lsutil/symbol_display.go
@@ -1,4 +1,4 @@
-package ls
+package lsutil
import (
"github.com/microsoft/typescript-go/internal/ast"
@@ -7,79 +7,79 @@ import (
"github.com/microsoft/typescript-go/internal/core"
)
-type ScriptElementKind string
+type ScriptElementKind int
const (
- ScriptElementKindUnknown ScriptElementKind = ""
- ScriptElementKindWarning ScriptElementKind = "warning"
+ ScriptElementKindUnknown ScriptElementKind = iota
+ ScriptElementKindWarning
// predefined type (void) or keyword (class)
- ScriptElementKindKeyword ScriptElementKind = "keyword"
+ ScriptElementKindKeyword
// top level script node
- ScriptElementKindScriptElement ScriptElementKind = "script"
+ ScriptElementKindScriptElement
// module foo {}
- ScriptElementKindModuleElement ScriptElementKind = "module"
+ ScriptElementKindModuleElement
// class X {}
- ScriptElementKindClassElement ScriptElementKind = "class"
+ ScriptElementKindClassElement
// var x = class X {}
- ScriptElementKindLocalClassElement ScriptElementKind = "local class"
+ ScriptElementKindLocalClassElement
// interface Y {}
- ScriptElementKindInterfaceElement ScriptElementKind = "interface"
+ ScriptElementKindInterfaceElement
// type T = ...
- ScriptElementKindTypeElement ScriptElementKind = "type"
+ ScriptElementKindTypeElement
// enum E {}
- ScriptElementKindEnumElement ScriptElementKind = "enum"
- ScriptElementKindEnumMemberElement ScriptElementKind = "enum member"
+ ScriptElementKindEnumElement
+ ScriptElementKindEnumMemberElement
// Inside module and script only.
// const v = ...
- ScriptElementKindVariableElement ScriptElementKind = "var"
+ ScriptElementKindVariableElement
// Inside function.
- ScriptElementKindLocalVariableElement ScriptElementKind = "local var"
+ ScriptElementKindLocalVariableElement
// using foo = ...
- ScriptElementKindVariableUsingElement ScriptElementKind = "using"
+ ScriptElementKindVariableUsingElement
// await using foo = ...
- ScriptElementKindVariableAwaitUsingElement ScriptElementKind = "await using"
+ ScriptElementKindVariableAwaitUsingElement
// Inside module and script only.
// function f() {}
- ScriptElementKindFunctionElement ScriptElementKind = "function"
+ ScriptElementKindFunctionElement
// Inside function.
- ScriptElementKindLocalFunctionElement ScriptElementKind = "local function"
+ ScriptElementKindLocalFunctionElement
// class X { [public|private]* foo() {} }
- ScriptElementKindMemberFunctionElement ScriptElementKind = "method"
+ ScriptElementKindMemberFunctionElement
// class X { [public|private]* [get|set] foo:number; }
- ScriptElementKindMemberGetAccessorElement ScriptElementKind = "getter"
- ScriptElementKindMemberSetAccessorElement ScriptElementKind = "setter"
+ ScriptElementKindMemberGetAccessorElement
+ ScriptElementKindMemberSetAccessorElement
// class X { [public|private]* foo:number; }
// interface Y { foo:number; }
- ScriptElementKindMemberVariableElement ScriptElementKind = "property"
+ ScriptElementKindMemberVariableElement
// class X { [public|private]* accessor foo: number; }
- ScriptElementKindMemberAccessorVariableElement ScriptElementKind = "accessor"
+ ScriptElementKindMemberAccessorVariableElement
// class X { constructor() { } }
// class X { static { } }
- ScriptElementKindConstructorImplementationElement ScriptElementKind = "constructor"
+ ScriptElementKindConstructorImplementationElement
// interface Y { ():number; }
- ScriptElementKindCallSignatureElement ScriptElementKind = "call"
+ ScriptElementKindCallSignatureElement
// interface Y { []:number; }
- ScriptElementKindIndexSignatureElement ScriptElementKind = "index"
+ ScriptElementKindIndexSignatureElement
// interface Y { new():Y; }
- ScriptElementKindConstructSignatureElement ScriptElementKind = "construct"
+ ScriptElementKindConstructSignatureElement
// function foo(*Y*: string)
- ScriptElementKindParameterElement ScriptElementKind = "parameter"
- ScriptElementKindTypeParameterElement ScriptElementKind = "type parameter"
- ScriptElementKindPrimitiveType ScriptElementKind = "primitive type"
- ScriptElementKindLabel ScriptElementKind = "label"
- ScriptElementKindAlias ScriptElementKind = "alias"
- ScriptElementKindConstElement ScriptElementKind = "const"
- ScriptElementKindLetElement ScriptElementKind = "let"
- ScriptElementKindDirectory ScriptElementKind = "directory"
- ScriptElementKindExternalModuleName ScriptElementKind = "external module name"
+ ScriptElementKindParameterElement
+ ScriptElementKindTypeParameterElement
+ ScriptElementKindPrimitiveType
+ ScriptElementKindLabel
+ ScriptElementKindAlias
+ ScriptElementKindConstElement
+ ScriptElementKindLetElement
+ ScriptElementKindDirectory
+ ScriptElementKindExternalModuleName
// String literal
- ScriptElementKindString ScriptElementKind = "string"
+ ScriptElementKindString
// Jsdoc @link: in `{@link C link text}`, the before and after text "{@link " and "}"
- ScriptElementKindLink ScriptElementKind = "link"
+ ScriptElementKindLink
// Jsdoc @link: in `{@link C link text}`, the entity name "C"
- ScriptElementKindLinkName ScriptElementKind = "link name"
+ ScriptElementKindLinkName
// Jsdoc @link: in `{@link C link text}`, the link text "link text"
- ScriptElementKindLinkText ScriptElementKind = "link text"
+ ScriptElementKindLinkText
)
type ScriptElementKindModifier string
@@ -109,7 +109,7 @@ const (
ScriptElementKindModifierCjs ScriptElementKindModifier = ".cjs"
)
-var fileExtensionKindModifiers = []ScriptElementKindModifier{
+var FileExtensionKindModifiers = []ScriptElementKindModifier{
ScriptElementKindModifierDts,
ScriptElementKindModifierTs,
ScriptElementKindModifierTsx,
@@ -124,13 +124,12 @@ var fileExtensionKindModifiers = []ScriptElementKindModifier{
ScriptElementKindModifierCjs,
}
-func getSymbolKind(typeChecker *checker.Checker, symbol *ast.Symbol, location *ast.Node) ScriptElementKind {
+func GetSymbolKind(typeChecker *checker.Checker, symbol *ast.Symbol, location *ast.Node) ScriptElementKind {
result := getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location)
if result != ScriptElementKindUnknown {
return result
}
-
- flags := checker.GetCombinedLocalAndExportSymbolFlags(symbol)
+ flags := symbol.CombinedLocalAndExportSymbolFlags()
if flags&ast.SymbolFlagsClass != 0 {
decl := ast.GetDeclarationOfKind(symbol, ast.KindClassExpression)
if decl != nil {
@@ -164,27 +163,35 @@ func getSymbolKind(typeChecker *checker.Checker, symbol *ast.Symbol, location *a
}
func getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker *checker.Checker, symbol *ast.Symbol, location *ast.Node) ScriptElementKind {
- roots := typeChecker.GetRootSymbols(symbol)
+ var roots []*ast.Symbol
+ if typeChecker != nil {
+ roots = typeChecker.GetRootSymbols(symbol)
+ } else {
+ roots = []*ast.Symbol{symbol}
+ }
+
// If this is a method from a mapped type, leave as a method so long as it still has a call signature, as opposed to e.g.
// `{ [K in keyof I]: number }`.
if len(roots) == 1 &&
roots[0].Flags&ast.SymbolFlagsMethod != 0 &&
- len(typeChecker.GetCallSignatures(typeChecker.GetNonNullableType(typeChecker.GetTypeOfSymbolAtLocation(symbol, location)))) > 0 {
+ (typeChecker == nil || len(typeChecker.GetCallSignatures(typeChecker.GetNonNullableType(typeChecker.GetTypeOfSymbolAtLocation(symbol, location)))) > 0) {
return ScriptElementKindMemberFunctionElement
}
- if typeChecker.IsUndefinedSymbol(symbol) {
- return ScriptElementKindVariableElement
- }
- if typeChecker.IsArgumentsSymbol(symbol) {
- return ScriptElementKindLocalVariableElement
- }
- if location.Kind == ast.KindThisKeyword && ast.IsExpression(location) ||
- ast.IsThisInTypeQuery(location) {
- return ScriptElementKindParameterElement
+ if typeChecker != nil {
+ if typeChecker.IsUndefinedSymbol(symbol) {
+ return ScriptElementKindVariableElement
+ }
+ if typeChecker.IsArgumentsSymbol(symbol) {
+ return ScriptElementKindLocalVariableElement
+ }
+ if location.Kind == ast.KindThisKeyword && ast.IsExpression(location) ||
+ ast.IsThisInTypeQuery(location) {
+ return ScriptElementKindParameterElement
+ }
}
- flags := checker.GetCombinedLocalAndExportSymbolFlags(symbol)
+ flags := symbol.CombinedLocalAndExportSymbolFlags()
if flags&ast.SymbolFlagsVariable != 0 {
if isFirstDeclarationOfSymbolParameter(symbol) {
return ScriptElementKindParameterElement
@@ -227,8 +234,7 @@ func getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker *
}
if flags&ast.SymbolFlagsProperty != 0 {
- if flags&ast.SymbolFlagsTransient != 0 &&
- symbol.CheckFlags&ast.CheckFlagsSynthetic != 0 {
+ if typeChecker != nil && flags&ast.SymbolFlagsTransient != 0 && symbol.CheckFlags&ast.CheckFlagsSynthetic != 0 {
// If union property is result of union of non method (property/accessors/variables), it is labeled as property
var unionPropertyKind ScriptElementKind
for _, rootSymbol := range roots {
@@ -305,13 +311,13 @@ func isLocalVariableOrFunction(symbol *ast.Symbol) bool {
return false
}
-func getSymbolModifiers(typeChecker *checker.Checker, symbol *ast.Symbol) collections.Set[ScriptElementKindModifier] {
+func GetSymbolModifiers(typeChecker *checker.Checker, symbol *ast.Symbol) collections.Set[ScriptElementKindModifier] {
if symbol == nil {
return collections.Set[ScriptElementKindModifier]{}
}
modifiers := getNormalizedSymbolModifiers(typeChecker, symbol)
- if symbol.Flags&ast.SymbolFlagsAlias != 0 {
+ if symbol.Flags&ast.SymbolFlagsAlias != 0 && typeChecker != nil {
resolvedSymbol := typeChecker.GetAliasedSymbol(symbol)
if resolvedSymbol != symbol {
aliasModifiers := getNormalizedSymbolModifiers(typeChecker, resolvedSymbol)
@@ -335,8 +341,8 @@ func getNormalizedSymbolModifiers(typeChecker *checker.Checker, symbol *ast.Symb
// omit deprecated flag if some declarations are not deprecated
var excludeFlags ast.ModifierFlags
if len(declarations) > 0 &&
- typeChecker.IsDeprecatedDeclaration(declaration) && // !!! include jsdoc node flags
- core.Some(declarations, func(d *ast.Node) bool { return !typeChecker.IsDeprecatedDeclaration(d) }) {
+ isDeprecatedDeclaration(typeChecker, declaration) && // !!! include jsdoc node flags
+ core.Some(declarations, func(d *ast.Node) bool { return !isDeprecatedDeclaration(typeChecker, d) }) {
excludeFlags = ast.ModifierFlagsDeprecated
} else {
excludeFlags = ast.ModifierFlagsNone
@@ -347,6 +353,13 @@ func getNormalizedSymbolModifiers(typeChecker *checker.Checker, symbol *ast.Symb
return modifierSet
}
+func isDeprecatedDeclaration(typeChecker *checker.Checker, declaration *ast.Node) bool {
+ if typeChecker != nil {
+ return typeChecker.IsDeprecatedDeclaration(declaration)
+ }
+ return ast.GetCombinedNodeFlags(declaration)&ast.NodeFlagsDeprecated != 0
+}
+
func getNodeModifiers(node *ast.Node, excludeFlags ast.ModifierFlags) collections.Set[ScriptElementKindModifier] {
var result collections.Set[ScriptElementKindModifier]
var flags ast.ModifierFlags
diff --git a/internal/ls/lsutil/userpreferences.go b/internal/ls/lsutil/userpreferences.go
index d57197a027..a3568b100f 100644
--- a/internal/ls/lsutil/userpreferences.go
+++ b/internal/ls/lsutil/userpreferences.go
@@ -4,9 +4,11 @@ import (
"slices"
"strings"
+ "github.com/dlclark/regexp2"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/modulespecifiers"
"github.com/microsoft/typescript-go/internal/tsoptions"
+ "github.com/microsoft/typescript-go/internal/vfs"
)
func NewDefaultUserPreferences() *UserPreferences {
@@ -362,6 +364,13 @@ func (p *UserPreferences) CopyOrDefault() *UserPreferences {
return p.Copy()
}
+func (p *UserPreferences) OrDefault() *UserPreferences {
+ if p == nil {
+ return NewDefaultUserPreferences()
+ }
+ return p
+}
+
func (p *UserPreferences) ModuleSpecifierPreferences() modulespecifiers.UserPreferences {
return modulespecifiers.UserPreferences{
ImportModuleSpecifierPreference: p.ImportModuleSpecifierPreference,
@@ -719,3 +728,26 @@ func (p *UserPreferences) set(name string, value any) {
p.CodeLens.ImplementationsCodeLensShowOnAllClassMethods = parseBoolWithDefault(value, false)
}
}
+
+func (p *UserPreferences) ParsedAutoImportFileExcludePatterns(useCaseSensitiveFileNames bool) []*regexp2.Regexp {
+ if len(p.AutoImportFileExcludePatterns) == 0 {
+ return nil
+ }
+ var patterns []*regexp2.Regexp
+ for _, spec := range p.AutoImportFileExcludePatterns {
+ pattern := vfs.GetSubPatternFromSpec(spec, "", vfs.UsageExclude, vfs.WildcardMatcher{})
+ if pattern != "" {
+ if re := vfs.GetRegexFromPattern(pattern, useCaseSensitiveFileNames); re != nil {
+ patterns = append(patterns, re)
+ }
+ }
+ }
+ return patterns
+}
+
+func (p *UserPreferences) IsModuleSpecifierExcluded(moduleSpecifier string) bool {
+ if modulespecifiers.IsExcludedByRegex(moduleSpecifier, p.AutoImportSpecifierExcludeRegexes) {
+ return true
+ }
+ return false
+}
diff --git a/internal/ls/lsutil/utilities.go b/internal/ls/lsutil/utilities.go
index b5c6972bcc..4f88c6349f 100644
--- a/internal/ls/lsutil/utilities.go
+++ b/internal/ls/lsutil/utilities.go
@@ -2,12 +2,15 @@ package lsutil
import (
"strings"
+ "unicode"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/astnav"
"github.com/microsoft/typescript-go/internal/compiler"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/scanner"
+ "github.com/microsoft/typescript-go/internal/stringutil"
+ "github.com/microsoft/typescript-go/internal/tspath"
)
func ProbablyUsesSemicolons(file *ast.SourceFile) bool {
@@ -68,16 +71,83 @@ func ProbablyUsesSemicolons(file *ast.SourceFile) bool {
return withSemicolon/withoutSemicolon > 1/nStatementsToObserve
}
-func ShouldUseUriStyleNodeCoreModules(file *ast.SourceFile, program *compiler.Program) bool {
+func ShouldUseUriStyleNodeCoreModules(file *ast.SourceFile, program *compiler.Program) core.Tristate {
for _, node := range file.Imports() {
if core.NodeCoreModules()[node.Text()] && !core.ExclusivelyPrefixedNodeCoreModules[node.Text()] {
if strings.HasPrefix(node.Text(), "node:") {
- return true
+ return core.TSTrue
} else {
- return false
+ return core.TSFalse
}
}
}
return program.UsesUriStyleNodeCoreModules()
}
+
+func QuotePreferenceFromString(str *ast.StringLiteral) QuotePreference {
+ if str.TokenFlags&ast.TokenFlagsSingleQuote != 0 {
+ return QuotePreferenceSingle
+ }
+ return QuotePreferenceDouble
+}
+
+func GetQuotePreference(sourceFile *ast.SourceFile, preferences *UserPreferences) QuotePreference {
+ if preferences.QuotePreference != "" && preferences.QuotePreference != "auto" {
+ if preferences.QuotePreference == "single" {
+ return QuotePreferenceSingle
+ }
+ return QuotePreferenceDouble
+ }
+ // ignore synthetic import added when importHelpers: true
+ firstModuleSpecifier := core.Find(sourceFile.Imports(), func(n *ast.Node) bool {
+ return ast.IsStringLiteral(n) && !ast.NodeIsSynthesized(n.Parent)
+ })
+ if firstModuleSpecifier != nil {
+ return QuotePreferenceFromString(firstModuleSpecifier.AsStringLiteral())
+ }
+ return QuotePreferenceDouble
+}
+
+func ModuleSymbolToValidIdentifier(moduleSymbol *ast.Symbol, target core.ScriptTarget, forceCapitalize bool) string {
+ return ModuleSpecifierToValidIdentifier(stringutil.StripQuotes(moduleSymbol.Name), target, forceCapitalize)
+}
+
+func ModuleSpecifierToValidIdentifier(moduleSpecifier string, target core.ScriptTarget, forceCapitalize bool) string {
+ baseName := tspath.GetBaseFileName(strings.TrimSuffix(tspath.RemoveFileExtension(moduleSpecifier), "/index"))
+ res := []rune{}
+ lastCharWasValid := true
+ baseNameRunes := []rune(baseName)
+ if len(baseNameRunes) > 0 && scanner.IsIdentifierStart(baseNameRunes[0]) {
+ if forceCapitalize {
+ res = append(res, unicode.ToUpper(baseNameRunes[0]))
+ } else {
+ res = append(res, baseNameRunes[0])
+ }
+ } else {
+ lastCharWasValid = false
+ }
+
+ for i := 1; i < len(baseNameRunes); i++ {
+ isValid := scanner.IsIdentifierPart(baseNameRunes[i])
+ if isValid {
+ if !lastCharWasValid {
+ res = append(res, unicode.ToUpper(baseNameRunes[i]))
+ } else {
+ res = append(res, baseNameRunes[i])
+ }
+ }
+ lastCharWasValid = isValid
+ }
+
+ // Need `"_"` to ensure result isn't empty.
+ resString := string(res)
+ if resString != "" && !IsNonContextualKeyword(scanner.StringToToken(resString)) {
+ return resString
+ }
+ return "_" + resString
+}
+
+func IsNonContextualKeyword(token ast.Kind) bool {
+ return ast.IsKeywordKind(token) && !ast.IsContextualKeyword(token)
+}
diff --git a/internal/ls/string_completions.go b/internal/ls/string_completions.go
index a27b3768ef..37dedaa7a9 100644
--- a/internal/ls/string_completions.go
+++ b/internal/ls/string_completions.go
@@ -11,6 +11,7 @@ import (
"github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/compiler"
"github.com/microsoft/typescript-go/internal/core"
+ "github.com/microsoft/typescript-go/internal/ls/lsutil"
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/tspath"
@@ -28,8 +29,8 @@ type completionsFromProperties struct {
type pathCompletion struct {
name string
- // ScriptElementKindScriptElement | ScriptElementKindDirectory | ScriptElementKindExternalModuleName
- kind ScriptElementKind
+ // lsutil.ScriptElementKindScriptElement | lsutil.ScriptElementKindDirectory | lsutil.ScriptElementKindExternalModuleName
+ kind lsutil.ScriptElementKind
extension string
textRange *core.TextRange
}
@@ -45,6 +46,7 @@ func (l *LanguageService) getStringLiteralCompletions(
file *ast.SourceFile,
position int,
contextToken *ast.Node,
+ checker *checker.Checker,
compilerOptions *core.CompilerOptions,
) *lsproto.CompletionList {
// !!! reference comment
@@ -56,13 +58,16 @@ func (l *LanguageService) getStringLiteralCompletions(
ctx,
file,
contextToken,
- position)
+ position,
+ checker,
+ )
return l.convertStringLiteralCompletions(
ctx,
entries,
contextToken,
file,
position,
+ checker,
compilerOptions,
)
}
@@ -75,6 +80,7 @@ func (l *LanguageService) convertStringLiteralCompletions(
contextToken *ast.StringLiteralLike,
file *ast.SourceFile,
position int,
+ typeChecker *checker.Checker,
options *core.CompilerOptions,
) *lsproto.CompletionList {
if completion == nil {
@@ -97,6 +103,7 @@ func (l *LanguageService) convertStringLiteralCompletions(
}
_, items := l.getCompletionEntriesFromSymbols(
ctx,
+ typeChecker,
data,
contextToken, /*replacementToken*/
position,
@@ -135,8 +142,8 @@ func (l *LanguageService) convertStringLiteralCompletions(
"", /*insertText*/
"", /*filterText*/
SortTextLocationPriority,
- ScriptElementKindString,
- collections.Set[ScriptElementKindModifier]{},
+ lsutil.ScriptElementKindString,
+ collections.Set[lsutil.ScriptElementKindModifier]{},
l.getReplacementRangeForContextToken(file, contextToken, position),
nil, /*commitCharacters*/
nil, /*labelDetails*/
@@ -220,9 +227,8 @@ func (l *LanguageService) getStringLiteralCompletionEntries(
file *ast.SourceFile,
node *ast.StringLiteralLike,
position int,
+ typeChecker *checker.Checker,
) *stringLiteralCompletions {
- typeChecker, done := l.GetProgram().GetTypeCheckerForFile(ctx, file)
- defer done()
parent := walkUpParentheses(node.Parent)
switch parent.Kind {
case ast.KindLiteralType:
@@ -578,36 +584,36 @@ func isRequireCallArgument(node *ast.Node) bool {
ast.IsIdentifier(node.Parent.Expression()) && node.Parent.Expression().Text() == "require"
}
-func kindModifiersFromExtension(extension string) ScriptElementKindModifier {
+func kindModifiersFromExtension(extension string) lsutil.ScriptElementKindModifier {
switch extension {
case tspath.ExtensionDts:
- return ScriptElementKindModifierDts
+ return lsutil.ScriptElementKindModifierDts
case tspath.ExtensionJs:
- return ScriptElementKindModifierJs
+ return lsutil.ScriptElementKindModifierJs
case tspath.ExtensionJson:
- return ScriptElementKindModifierJson
+ return lsutil.ScriptElementKindModifierJson
case tspath.ExtensionJsx:
- return ScriptElementKindModifierJsx
+ return lsutil.ScriptElementKindModifierJsx
case tspath.ExtensionTs:
- return ScriptElementKindModifierTs
+ return lsutil.ScriptElementKindModifierTs
case tspath.ExtensionTsx:
- return ScriptElementKindModifierTsx
+ return lsutil.ScriptElementKindModifierTsx
case tspath.ExtensionDmts:
- return ScriptElementKindModifierDmts
+ return lsutil.ScriptElementKindModifierDmts
case tspath.ExtensionMjs:
- return ScriptElementKindModifierMjs
+ return lsutil.ScriptElementKindModifierMjs
case tspath.ExtensionMts:
- return ScriptElementKindModifierMts
+ return lsutil.ScriptElementKindModifierMts
case tspath.ExtensionDcts:
- return ScriptElementKindModifierDcts
+ return lsutil.ScriptElementKindModifierDcts
case tspath.ExtensionCjs:
- return ScriptElementKindModifierCjs
+ return lsutil.ScriptElementKindModifierCjs
case tspath.ExtensionCts:
- return ScriptElementKindModifierCts
+ return lsutil.ScriptElementKindModifierCts
case tspath.ExtensionTsBuildInfo:
panic(fmt.Sprintf("Extension %v is unsupported.", tspath.ExtensionTsBuildInfo))
case "":
- return ScriptElementKindModifierNone
+ return lsutil.ScriptElementKindModifierNone
default:
panic(fmt.Sprintf("Unexpected extension: %v", extension))
}
@@ -673,6 +679,7 @@ func (l *LanguageService) getStringLiteralCompletionDetails(
file,
contextToken,
position,
+ checker,
)
if completions == nil {
return item
diff --git a/internal/ls/utilities.go b/internal/ls/utilities.go
index 9ffa49ae30..3a4989fc70 100644
--- a/internal/ls/utilities.go
+++ b/internal/ls/utilities.go
@@ -5,7 +5,6 @@ import (
"iter"
"slices"
"strings"
- "unicode"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/astnav"
@@ -19,7 +18,6 @@ import (
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
"github.com/microsoft/typescript-go/internal/scanner"
"github.com/microsoft/typescript-go/internal/stringutil"
- "github.com/microsoft/typescript-go/internal/tspath"
)
var quoteReplacer = strings.NewReplacer("'", `\'`, `\"`, `"`)
@@ -44,37 +42,6 @@ func IsInString(sourceFile *ast.SourceFile, position int, previousToken *ast.Nod
return false
}
-func importFromModuleSpecifier(node *ast.Node) *ast.Node {
- if result := tryGetImportFromModuleSpecifier(node); result != nil {
- return result
- }
- debug.FailBadSyntaxKind(node.Parent)
- return nil
-}
-
-func tryGetImportFromModuleSpecifier(node *ast.StringLiteralLike) *ast.Node {
- switch node.Parent.Kind {
- case ast.KindImportDeclaration, ast.KindJSImportDeclaration, ast.KindExportDeclaration:
- return node.Parent
- case ast.KindExternalModuleReference:
- return node.Parent.Parent
- case ast.KindCallExpression:
- if ast.IsImportCall(node.Parent) || ast.IsRequireCall(node.Parent, false /*requireStringLiteralLikeArgument*/) {
- return node.Parent
- }
- return nil
- case ast.KindLiteralType:
- if !ast.IsStringLiteral(node) {
- return nil
- }
- if ast.IsImportTypeNode(node.Parent.Parent) {
- return node.Parent.Parent
- }
- return nil
- }
- return nil
-}
-
func isModuleSpecifierLike(node *ast.Node) bool {
if !ast.IsStringLiteralLike(node) {
return false
@@ -100,45 +67,6 @@ func getNonModuleSymbolOfMergedModuleSymbol(symbol *ast.Symbol) *ast.Symbol {
return nil
}
-func moduleSymbolToValidIdentifier(moduleSymbol *ast.Symbol, target core.ScriptTarget, forceCapitalize bool) string {
- return moduleSpecifierToValidIdentifier(stringutil.StripQuotes(moduleSymbol.Name), target, forceCapitalize)
-}
-
-func moduleSpecifierToValidIdentifier(moduleSpecifier string, target core.ScriptTarget, forceCapitalize bool) string {
- baseName := tspath.GetBaseFileName(strings.TrimSuffix(tspath.RemoveFileExtension(moduleSpecifier), "/index"))
- res := []rune{}
- lastCharWasValid := true
- baseNameRunes := []rune(baseName)
- if len(baseNameRunes) > 0 && scanner.IsIdentifierStart(baseNameRunes[0]) {
- if forceCapitalize {
- res = append(res, unicode.ToUpper(baseNameRunes[0]))
- } else {
- res = append(res, baseNameRunes[0])
- }
- } else {
- lastCharWasValid = false
- }
-
- for i := 1; i < len(baseNameRunes); i++ {
- isValid := scanner.IsIdentifierPart(baseNameRunes[i])
- if isValid {
- if !lastCharWasValid {
- res = append(res, unicode.ToUpper(baseNameRunes[i]))
- } else {
- res = append(res, baseNameRunes[i])
- }
- }
- lastCharWasValid = isValid
- }
-
- // Need `"_"` to ensure result isn't empty.
- resString := string(res)
- if resString != "" && !isNonContextualKeyword(scanner.StringToToken(resString)) {
- return resString
- }
- return "_" + resString
-}
-
func getLocalSymbolForExportSpecifier(referenceLocation *ast.Identifier, referenceSymbol *ast.Symbol, exportSpecifier *ast.ExportSpecifier, ch *checker.Checker) *ast.Symbol {
if isExportSpecifierAlias(referenceLocation, exportSpecifier) {
if symbol := ch.GetExportSpecifierLocalTargetSymbol(exportSpecifier.AsNode()); symbol != nil {
@@ -378,49 +306,14 @@ func (l *LanguageService) createLspPosition(position int, file *ast.SourceFile)
func quote(file *ast.SourceFile, preferences *lsutil.UserPreferences, text string) string {
// Editors can pass in undefined or empty string - we want to infer the preference in those cases.
- quotePreference := getQuotePreference(file, preferences)
+ quotePreference := lsutil.GetQuotePreference(file, preferences)
quoted, _ := core.StringifyJson(text, "" /*prefix*/, "" /*indent*/)
- if quotePreference == quotePreferenceSingle {
+ if quotePreference == lsutil.QuotePreferenceSingle {
quoted = quoteReplacer.Replace(stringutil.StripQuotes(quoted))
}
return quoted
}
-type quotePreference int
-
-const (
- quotePreferenceSingle quotePreference = iota
- quotePreferenceDouble
-)
-
-func quotePreferenceFromString(str *ast.StringLiteral) quotePreference {
- if str.TokenFlags&ast.TokenFlagsSingleQuote != 0 {
- return quotePreferenceSingle
- }
- return quotePreferenceDouble
-}
-
-func getQuotePreference(sourceFile *ast.SourceFile, preferences *lsutil.UserPreferences) quotePreference {
- if preferences.QuotePreference != "" && preferences.QuotePreference != "auto" {
- if preferences.QuotePreference == "single" {
- return quotePreferenceSingle
- }
- return quotePreferenceDouble
- }
- // ignore synthetic import added when importHelpers: true
- firstModuleSpecifier := core.Find(sourceFile.Imports(), func(n *ast.Node) bool {
- return ast.IsStringLiteral(n) && !ast.NodeIsSynthesized(n.Parent)
- })
- if firstModuleSpecifier != nil {
- return quotePreferenceFromString(firstModuleSpecifier.AsStringLiteral())
- }
- return quotePreferenceDouble
-}
-
-func isNonContextualKeyword(token ast.Kind) bool {
- return ast.IsKeywordKind(token) && !ast.IsContextualKeyword(token)
-}
-
var typeKeywords *collections.Set[ast.Kind] = collections.NewSetFromItems(
ast.KindAnyKeyword,
ast.KindAssertsKeyword,
@@ -1590,3 +1483,7 @@ func toContextRange(textRange *core.TextRange, contextFile *ast.SourceFile, cont
}
return nil
}
+
+func ptrTo[T any](v T) *T {
+ return &v
+}
diff --git a/internal/lsp/lsproto/_generate/generate.mts b/internal/lsp/lsproto/_generate/generate.mts
index 2ea801f879..660aa6abd0 100644
--- a/internal/lsp/lsproto/_generate/generate.mts
+++ b/internal/lsp/lsproto/_generate/generate.mts
@@ -51,49 +51,30 @@ const customStructures: Structure[] = [
documentation: "InitializationOptions contains user-provided initialization options.",
},
{
- name: "ExportInfoMapKey",
+ name: "AutoImportFix",
properties: [
{
- name: "symbolName",
- type: { kind: "base", name: "string" },
- documentation: "The symbol name.",
- omitzeroValue: true,
- },
- {
- name: "symbolId",
- type: { kind: "reference", name: "uint64" },
- documentation: "The symbol ID.",
+ name: "kind",
+ type: { kind: "reference", name: "AutoImportFixKind" },
omitzeroValue: true,
},
{
- name: "ambientModuleName",
+ name: "name",
type: { kind: "base", name: "string" },
- documentation: "The ambient module name.",
omitzeroValue: true,
},
{
- name: "moduleFile",
- type: { kind: "base", name: "string" },
- documentation: "The module file path.",
- omitzeroValue: true,
+ name: "importKind",
+ type: { kind: "reference", name: "ImportKind" },
},
- ],
- documentation: "ExportInfoMapKey uniquely identifies an export for auto-import purposes.",
- },
- {
- name: "AutoImportData",
- properties: [
{
- name: "exportName",
- type: { kind: "base", name: "string" },
- documentation: "The name of the property or export in the module's symbol table. Differs from the completion name in the case of InternalSymbolName.ExportEquals and InternalSymbolName.Default.",
+ name: "useRequire",
+ type: { kind: "base", name: "boolean" },
omitzeroValue: true,
},
{
- name: "exportMapKey",
- type: { kind: "reference", name: "ExportInfoMapKey" },
- documentation: "The export map key for this auto-import.",
- omitzeroValue: true,
+ name: "addAsTypeOnly",
+ type: { kind: "reference", name: "AddAsTypeOnly" },
},
{
name: "moduleSpecifier",
@@ -102,25 +83,22 @@ const customStructures: Structure[] = [
omitzeroValue: true,
},
{
- name: "fileName",
- type: { kind: "base", name: "string" },
- documentation: "The file name declaring the export's module symbol, if it was an external module.",
- omitzeroValue: true,
+ name: "importIndex",
+ type: { kind: "base", name: "integer" },
+ documentation: "Index of the import to modify when adding to an existing import declaration.",
},
{
- name: "ambientModuleName",
- type: { kind: "base", name: "string" },
- documentation: "The module name (with quotes stripped) of the export's module symbol, if it was an ambient module.",
- omitzeroValue: true,
+ name: "usagePosition",
+ type: { kind: "reference", name: "Position" },
+ optional: true,
},
{
- name: "isPackageJsonImport",
- type: { kind: "base", name: "boolean" },
- documentation: "True if the export was found in the package.json AutoImportProvider.",
+ name: "namespacePrefix",
+ type: { kind: "base", name: "string" },
omitzeroValue: true,
},
],
- documentation: "AutoImportData contains information about an auto-import suggestion.",
+ documentation: "AutoImportFix contains information about an auto-import suggestion.",
},
{
name: "CompletionItemData",
@@ -151,7 +129,7 @@ const customStructures: Structure[] = [
},
{
name: "autoImport",
- type: { kind: "reference", name: "AutoImportData" },
+ type: { kind: "reference", name: "AutoImportFix" },
optional: true,
documentation: "Auto-import data for this completion item.",
},
@@ -193,6 +171,36 @@ const customEnumerations: Enumeration[] = [
},
],
},
+ {
+ name: "AutoImportFixKind",
+ type: { kind: "base", name: "integer" },
+ values: [
+ { name: "UseNamespace", value: 0, documentation: "Augment an existing namespace import." },
+ { name: "JsdocTypeImport", value: 1, documentation: "Add a JSDoc-only type import." },
+ { name: "AddToExisting", value: 2, documentation: "Insert into an existing import declaration." },
+ { name: "AddNew", value: 3, documentation: "Create a fresh import statement." },
+ { name: "PromoteTypeOnly", value: 4, documentation: "Promote a type-only import when necessary." },
+ ],
+ },
+ {
+ name: "ImportKind",
+ type: { kind: "base", name: "integer" },
+ values: [
+ { name: "Named", value: 0, documentation: "Adds a named import." },
+ { name: "Default", value: 1, documentation: "Adds a default import." },
+ { name: "Namespace", value: 2, documentation: "Adds a namespace import." },
+ { name: "CommonJS", value: 3, documentation: "Adds a CommonJS import assignment." },
+ ],
+ },
+ {
+ name: "AddAsTypeOnly",
+ type: { kind: "base", name: "integer" },
+ values: [
+ { name: "Allowed", value: 1, documentation: "Import may be marked type-only if needed." },
+ { name: "Required", value: 2, documentation: "Import must be marked type-only." },
+ { name: "NotAllowed", value: 4, documentation: "Import cannot be marked type-only." },
+ ],
+ },
];
// Track which custom Data structures were declared explicitly
diff --git a/internal/lsp/lsproto/lsp_generated.go b/internal/lsp/lsproto/lsp_generated.go
index a93e686a73..de35dcefab 100644
--- a/internal/lsp/lsproto/lsp_generated.go
+++ b/internal/lsp/lsproto/lsp_generated.go
@@ -21628,40 +21628,116 @@ type InitializationOptions struct {
CodeLensShowLocationsCommandName *string `json:"codeLensShowLocationsCommandName,omitzero"`
}
-// ExportInfoMapKey uniquely identifies an export for auto-import purposes.
-type ExportInfoMapKey struct {
- // The symbol name.
- SymbolName string `json:"symbolName,omitzero"`
+// AutoImportFix contains information about an auto-import suggestion.
+type AutoImportFix struct {
+ Kind AutoImportFixKind `json:"kind,omitzero"`
- // The symbol ID.
- SymbolId uint64 `json:"symbolId,omitzero"`
-
- // The ambient module name.
- AmbientModuleName string `json:"ambientModuleName,omitzero"`
+ Name string `json:"name,omitzero"`
- // The module file path.
- ModuleFile string `json:"moduleFile,omitzero"`
-}
+ ImportKind ImportKind `json:"importKind"`
-// AutoImportData contains information about an auto-import suggestion.
-type AutoImportData struct {
- // The name of the property or export in the module's symbol table. Differs from the completion name in the case of InternalSymbolName.ExportEquals and InternalSymbolName.Default.
- ExportName string `json:"exportName,omitzero"`
+ UseRequire bool `json:"useRequire,omitzero"`
- // The export map key for this auto-import.
- ExportMapKey ExportInfoMapKey `json:"exportMapKey,omitzero"`
+ AddAsTypeOnly AddAsTypeOnly `json:"addAsTypeOnly"`
// The module specifier for this auto-import.
ModuleSpecifier string `json:"moduleSpecifier,omitzero"`
- // The file name declaring the export's module symbol, if it was an external module.
- FileName string `json:"fileName,omitzero"`
+ // Index of the import to modify when adding to an existing import declaration.
+ ImportIndex int32 `json:"importIndex"`
+
+ UsagePosition *Position `json:"usagePosition,omitzero"`
+
+ NamespacePrefix string `json:"namespacePrefix,omitzero"`
+}
+
+var _ json.UnmarshalerFrom = (*AutoImportFix)(nil)
+
+func (s *AutoImportFix) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+ const (
+ missingImportKind uint = 1 << iota
+ missingAddAsTypeOnly
+ missingImportIndex
+ _missingLast
+ )
+ missing := _missingLast - 1
+
+ if k := dec.PeekKind(); k != '{' {
+ return fmt.Errorf("expected object start, but encountered %v", k)
+ }
+ if _, err := dec.ReadToken(); err != nil {
+ return err
+ }
+
+ for dec.PeekKind() != '}' {
+ name, err := dec.ReadValue()
+ if err != nil {
+ return err
+ }
+ switch string(name) {
+ case `"kind"`:
+ if err := json.UnmarshalDecode(dec, &s.Kind); err != nil {
+ return err
+ }
+ case `"name"`:
+ if err := json.UnmarshalDecode(dec, &s.Name); err != nil {
+ return err
+ }
+ case `"importKind"`:
+ missing &^= missingImportKind
+ if err := json.UnmarshalDecode(dec, &s.ImportKind); err != nil {
+ return err
+ }
+ case `"useRequire"`:
+ if err := json.UnmarshalDecode(dec, &s.UseRequire); err != nil {
+ return err
+ }
+ case `"addAsTypeOnly"`:
+ missing &^= missingAddAsTypeOnly
+ if err := json.UnmarshalDecode(dec, &s.AddAsTypeOnly); err != nil {
+ return err
+ }
+ case `"moduleSpecifier"`:
+ if err := json.UnmarshalDecode(dec, &s.ModuleSpecifier); err != nil {
+ return err
+ }
+ case `"importIndex"`:
+ missing &^= missingImportIndex
+ if err := json.UnmarshalDecode(dec, &s.ImportIndex); err != nil {
+ return err
+ }
+ case `"usagePosition"`:
+ if err := json.UnmarshalDecode(dec, &s.UsagePosition); err != nil {
+ return err
+ }
+ case `"namespacePrefix"`:
+ if err := json.UnmarshalDecode(dec, &s.NamespacePrefix); err != nil {
+ return err
+ }
+ default:
+ // Ignore unknown properties.
+ }
+ }
- // The module name (with quotes stripped) of the export's module symbol, if it was an ambient module.
- AmbientModuleName string `json:"ambientModuleName,omitzero"`
+ if _, err := dec.ReadToken(); err != nil {
+ return err
+ }
- // True if the export was found in the package.json AutoImportProvider.
- IsPackageJsonImport bool `json:"isPackageJsonImport,omitzero"`
+ if missing != 0 {
+ var missingProps []string
+ if missing&missingImportKind != 0 {
+ missingProps = append(missingProps, "importKind")
+ }
+ if missing&missingAddAsTypeOnly != 0 {
+ missingProps = append(missingProps, "addAsTypeOnly")
+ }
+ if missing&missingImportIndex != 0 {
+ missingProps = append(missingProps, "importIndex")
+ }
+ return fmt.Errorf("missing required properties: %s", strings.Join(missingProps, ", "))
+ }
+
+ return nil
}
// CompletionItemData is preserved on a CompletionItem between CompletionRequest and CompletionResolveRequest.
@@ -21679,7 +21755,7 @@ type CompletionItemData struct {
Name string `json:"name,omitzero"`
// Auto-import data for this completion item.
- AutoImport *AutoImportData `json:"autoImport,omitzero"`
+ AutoImport *AutoImportFix `json:"autoImport,omitzero"`
}
type CodeLensData struct {
@@ -22916,6 +22992,88 @@ const (
CodeLensKindImplementations CodeLensKind = "implementations"
)
+type AutoImportFixKind int32
+
+const (
+ // Augment an existing namespace import.
+ AutoImportFixKindUseNamespace AutoImportFixKind = 0
+ // Add a JSDoc-only type import.
+ AutoImportFixKindJsdocTypeImport AutoImportFixKind = 1
+ // Insert into an existing import declaration.
+ AutoImportFixKindAddToExisting AutoImportFixKind = 2
+ // Create a fresh import statement.
+ AutoImportFixKindAddNew AutoImportFixKind = 3
+ // Promote a type-only import when necessary.
+ AutoImportFixKindPromoteTypeOnly AutoImportFixKind = 4
+)
+
+const _AutoImportFixKind_name = "UseNamespaceJsdocTypeImportAddToExistingAddNewPromoteTypeOnly"
+
+var _AutoImportFixKind_index = [...]uint16{0, 12, 27, 40, 46, 61}
+
+func (e AutoImportFixKind) String() string {
+ i := int(e) - 0
+ if i < 0 || i >= len(_AutoImportFixKind_index)-1 {
+ return fmt.Sprintf("AutoImportFixKind(%d)", e)
+ }
+ return _AutoImportFixKind_name[_AutoImportFixKind_index[i]:_AutoImportFixKind_index[i+1]]
+}
+
+type ImportKind int32
+
+const (
+ // Adds a named import.
+ ImportKindNamed ImportKind = 0
+ // Adds a default import.
+ ImportKindDefault ImportKind = 1
+ // Adds a namespace import.
+ ImportKindNamespace ImportKind = 2
+ // Adds a CommonJS import assignment.
+ ImportKindCommonJS ImportKind = 3
+)
+
+const _ImportKind_name = "NamedDefaultNamespaceCommonJS"
+
+var _ImportKind_index = [...]uint16{0, 5, 12, 21, 29}
+
+func (e ImportKind) String() string {
+ i := int(e) - 0
+ if i < 0 || i >= len(_ImportKind_index)-1 {
+ return fmt.Sprintf("ImportKind(%d)", e)
+ }
+ return _ImportKind_name[_ImportKind_index[i]:_ImportKind_index[i+1]]
+}
+
+type AddAsTypeOnly int32
+
+const (
+ // Import may be marked type-only if needed.
+ AddAsTypeOnlyAllowed AddAsTypeOnly = 1
+ // Import must be marked type-only.
+ AddAsTypeOnlyRequired AddAsTypeOnly = 2
+ // Import cannot be marked type-only.
+ AddAsTypeOnlyNotAllowed AddAsTypeOnly = 4
+)
+
+const _AddAsTypeOnly_name = "AllowedRequiredNotAllowed"
+
+var (
+ _AddAsTypeOnly_index_0 = [...]uint16{0, 7, 15}
+ _AddAsTypeOnly_index_1 = [...]uint16{0, 10}
+)
+
+func (e AddAsTypeOnly) String() string {
+ switch {
+ case 1 <= e && e <= 2:
+ i := int(e) - 1
+ return _AddAsTypeOnly_name[0+_AddAsTypeOnly_index_0[i] : 0+_AddAsTypeOnly_index_0[i+1]]
+ case e == 4:
+ return _AddAsTypeOnly_name[15:25]
+ default:
+ return fmt.Sprintf("AddAsTypeOnly(%d)", e)
+ }
+}
+
func unmarshalParams(method Method, data []byte) (any, error) {
switch method {
case MethodTextDocumentImplementation:
diff --git a/internal/lsp/server.go b/internal/lsp/server.go
index ad30b34eae..21b7fd85bb 100644
--- a/internal/lsp/server.go
+++ b/internal/lsp/server.go
@@ -498,7 +498,10 @@ func (s *Server) handleRequestOrNotification(ctx context.Context, req *lsproto.R
ctx = lsproto.WithClientCapabilities(ctx, &s.clientCapabilities)
if handler := handlers()[req.Method]; handler != nil {
- return handler(s, ctx, req)
+ start := time.Now()
+ err := handler(s, ctx, req)
+ s.logger.Info("handled method '", req.Method, "' in ", time.Since(start))
+ return err
}
s.logger.Warn("unknown method", req.Method)
if req.ID != nil {
@@ -529,7 +532,6 @@ var handlers = sync.OnceValue(func() handlerMap {
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentHoverInfo, (*Server).handleHover)
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentDefinitionInfo, (*Server).handleDefinition)
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentTypeDefinitionInfo, (*Server).handleTypeDefinition)
- registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentCompletionInfo, (*Server).handleCompletion)
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentSignatureHelpInfo, (*Server).handleSignatureHelp)
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentFormattingInfo, (*Server).handleDocumentFormat)
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentRangeFormattingInfo, (*Server).handleDocumentRangeFormat)
@@ -543,6 +545,9 @@ var handlers = sync.OnceValue(func() handlerMap {
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentPrepareCallHierarchyInfo, (*Server).handlePrepareCallHierarchy)
registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentFoldingRangeInfo, (*Server).handleFoldingRange)
+ registerLanguageServiceWithAutoImportsRequestHandler(handlers, lsproto.TextDocumentCompletionInfo, (*Server).handleCompletion)
+ registerLanguageServiceWithAutoImportsRequestHandler(handlers, lsproto.TextDocumentCodeActionInfo, (*Server).handleCodeAction)
+
registerMultiProjectReferenceRequestHandler(handlers, lsproto.TextDocumentReferencesInfo, (*ls.LanguageService).ProvideReferences)
registerMultiProjectReferenceRequestHandler(handlers, lsproto.TextDocumentRenameInfo, (*ls.LanguageService).ProvideRename)
registerMultiProjectReferenceRequestHandler(handlers, lsproto.TextDocumentImplementationInfo, (*ls.LanguageService).ProvideImplementations)
@@ -626,6 +631,43 @@ func registerLanguageServiceDocumentRequestHandler[Req lsproto.HasTextDocumentUR
}
}
+func registerLanguageServiceWithAutoImportsRequestHandler[Req lsproto.HasTextDocumentURI, Resp any](handlers handlerMap, info lsproto.RequestInfo[Req, Resp], fn func(*Server, context.Context, *ls.LanguageService, Req) (Resp, error)) {
+ handlers[info.Method] = func(s *Server, ctx context.Context, req *lsproto.RequestMessage) error {
+ var params Req
+ // Ignore empty params.
+ if req.Params != nil {
+ params = req.Params.(Req)
+ }
+ languageService, err := s.session.GetLanguageService(ctx, params.TextDocumentURI())
+ if err != nil {
+ return err
+ }
+ defer s.recover(req)
+ resp, err := fn(s, ctx, languageService, params)
+ if errors.Is(err, ls.ErrNeedsAutoImports) {
+ languageService, err = s.session.GetLanguageServiceWithAutoImports(ctx, params.TextDocumentURI())
+ if err != nil {
+ return err
+ }
+ if ctx.Err() != nil {
+ return ctx.Err()
+ }
+ resp, err = fn(s, ctx, languageService, params)
+ if errors.Is(err, ls.ErrNeedsAutoImports) {
+ panic(info.Method + " returned ErrNeedsAutoImports even after enabling auto imports")
+ }
+ }
+ if err != nil {
+ return err
+ }
+ if ctx.Err() != nil {
+ return ctx.Err()
+ }
+ s.sendResult(req.ID, resp)
+ return nil
+ }
+}
+
func registerMultiProjectReferenceRequestHandler[Req lsproto.HasTextDocumentPosition, Resp any](
handlers handlerMap,
info lsproto.RequestInfo[Req, Resp],
diff --git a/internal/module/resolver.go b/internal/module/resolver.go
index f5ae94746f..40bcfd8aa6 100644
--- a/internal/module/resolver.go
+++ b/internal/module/resolver.go
@@ -11,8 +11,9 @@ import (
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/diagnostics"
"github.com/microsoft/typescript-go/internal/packagejson"
- "github.com/microsoft/typescript-go/internal/semver"
+ "github.com/microsoft/typescript-go/internal/stringutil"
"github.com/microsoft/typescript-go/internal/tspath"
+ "github.com/microsoft/typescript-go/internal/vfs"
)
type resolved struct {
@@ -1818,13 +1819,7 @@ func (r *resolutionState) conditionMatches(condition string) bool {
if !slices.Contains(r.conditions, "types") {
return false // only apply versioned types conditions if the types condition is applied
}
- if !strings.HasPrefix(condition, "types@") {
- return false
- }
- if versionRange, ok := semver.TryParseVersionRange(condition[len("types@"):]); ok {
- return versionRange.Test(&typeScriptVersion)
- }
- return false
+ return IsApplicableVersionedTypesKey(condition)
}
func (r *resolutionState) getTraceFunc() func(m *diagnostics.Message, args ...any) {
@@ -2022,3 +2017,209 @@ func GetAutomaticTypeDirectiveNames(options *core.CompilerOptions, host Resoluti
}
return result
}
+
+type ResolvedEntrypoints struct {
+ Entrypoints []*ResolvedEntrypoint
+ FailedLookupLocations []string
+}
+
+type Ending int
+
+const (
+ EndingFixed Ending = iota
+ EndingExtensionChangeable
+ EndingChangeable
+)
+
+type ResolvedEntrypoint struct {
+ ResolvedFileName string
+ ModuleSpecifier string
+ Ending Ending
+ IncludeConditions *collections.Set[string]
+ ExcludeConditions *collections.Set[string]
+}
+
+func (r *Resolver) GetEntrypointsFromPackageJsonInfo(packageJson *packagejson.InfoCacheEntry, packageName string) *ResolvedEntrypoints {
+ extensions := extensionsTypeScript | extensionsDeclaration
+ features := NodeResolutionFeaturesAll
+ state := &resolutionState{resolver: r, extensions: extensions, features: features, compilerOptions: r.compilerOptions}
+ if packageJson.Exists() && packageJson.Contents.Exports.IsPresent() {
+ entrypoints := state.loadEntrypointsFromExportMap(packageJson, packageName, packageJson.Contents.Exports)
+ return &ResolvedEntrypoints{
+ Entrypoints: entrypoints,
+ FailedLookupLocations: state.failedLookupLocations,
+ }
+ }
+
+ result := &ResolvedEntrypoints{}
+ mainResolution := state.loadNodeModuleFromDirectoryWorker(
+ extensions,
+ packageJson.PackageDirectory,
+ false, /*onlyRecordFailures*/
+ packageJson,
+ )
+
+ otherFiles := vfs.ReadDirectory(
+ r.host.FS(),
+ r.host.GetCurrentDirectory(),
+ packageJson.PackageDirectory,
+ extensions.Array(),
+ []string{"node_modules"},
+ []string{"**/*"},
+ nil,
+ )
+
+ if mainResolution.isResolved() {
+ result.Entrypoints = append(result.Entrypoints, &ResolvedEntrypoint{
+ ResolvedFileName: mainResolution.path,
+ ModuleSpecifier: packageName,
+ })
+ }
+
+ comparePathsOptions := tspath.ComparePathsOptions{UseCaseSensitiveFileNames: r.host.FS().UseCaseSensitiveFileNames()}
+ for _, file := range otherFiles {
+ if mainResolution.isResolved() && tspath.ComparePaths(file, mainResolution.path, comparePathsOptions) == 0 {
+ continue
+ }
+ result.Entrypoints = append(result.Entrypoints, &ResolvedEntrypoint{
+ ResolvedFileName: file,
+ ModuleSpecifier: tspath.ResolvePath(packageName, tspath.GetRelativePathFromDirectory(packageJson.PackageDirectory, file, comparePathsOptions)),
+ Ending: EndingChangeable,
+ })
+ }
+
+ if len(result.Entrypoints) > 0 {
+ result.FailedLookupLocations = state.failedLookupLocations
+ return result
+ }
+ return nil
+}
+
+func (r *resolutionState) loadEntrypointsFromExportMap(
+ packageJson *packagejson.InfoCacheEntry,
+ packageName string,
+ exports packagejson.ExportsOrImports,
+) []*ResolvedEntrypoint {
+ var loadEntrypointsFromTargetExports func(subpath string, includeConditions *collections.Set[string], excludeConditions *collections.Set[string], exports packagejson.ExportsOrImports)
+ var entrypoints []*ResolvedEntrypoint
+
+ loadEntrypointsFromTargetExports = func(subpath string, includeConditions *collections.Set[string], excludeConditions *collections.Set[string], exports packagejson.ExportsOrImports) {
+ if exports.Type == packagejson.JSONValueTypeString && strings.HasPrefix(exports.AsString(), "./") {
+ if strings.ContainsRune(exports.AsString(), '*') {
+ if strings.IndexByte(exports.AsString(), '*') != strings.LastIndexByte(exports.AsString(), '*') {
+ return
+ }
+ patternPath := tspath.ResolvePath(packageJson.PackageDirectory, exports.AsString())
+ leadingSlice, trailingSlice, _ := strings.Cut(patternPath, "*")
+ caseSensitive := r.resolver.host.FS().UseCaseSensitiveFileNames()
+ files := vfs.ReadDirectory(
+ r.resolver.host.FS(),
+ r.resolver.host.GetCurrentDirectory(),
+ packageJson.PackageDirectory,
+ r.extensions.Array(),
+ nil,
+ []string{
+ tspath.ChangeFullExtension(strings.Replace(exports.AsString(), "*", "**/*", 1), ".*"),
+ },
+ nil,
+ )
+ for _, file := range files {
+ matchedStar, ok := r.getMatchedStarForPatternEntrypoint(file, leadingSlice, trailingSlice, caseSensitive)
+ if !ok {
+ continue
+ }
+ moduleSpecifier := tspath.ResolvePath(packageName, strings.Replace(subpath, "*", matchedStar, 1))
+ entrypoints = append(entrypoints, &ResolvedEntrypoint{
+ ResolvedFileName: file,
+ ModuleSpecifier: moduleSpecifier,
+ IncludeConditions: includeConditions,
+ ExcludeConditions: excludeConditions,
+ Ending: core.IfElse(strings.HasSuffix(exports.AsString(), "*"), EndingExtensionChangeable, EndingFixed),
+ })
+ }
+ } else {
+ partsAfterFirst := tspath.GetPathComponents(exports.AsString(), "")[2:]
+ if slices.Contains(partsAfterFirst, "..") || slices.Contains(partsAfterFirst, ".") || slices.Contains(partsAfterFirst, "node_modules") {
+ return
+ }
+ resolvedTarget := tspath.ResolvePath(packageJson.PackageDirectory, exports.AsString())
+ if result := r.loadFileNameFromPackageJSONField(r.extensions, resolvedTarget, exports.AsString(), false /*onlyRecordFailures*/); result.isResolved() {
+ entrypoints = append(entrypoints, &ResolvedEntrypoint{
+ ResolvedFileName: result.path,
+ ModuleSpecifier: tspath.ResolvePath(packageName, subpath),
+ IncludeConditions: includeConditions,
+ ExcludeConditions: excludeConditions,
+ })
+ }
+ }
+ } else if exports.Type == packagejson.JSONValueTypeArray {
+ for _, element := range exports.AsArray() {
+ loadEntrypointsFromTargetExports(subpath, includeConditions, excludeConditions, element)
+ }
+ } else if exports.Type == packagejson.JSONValueTypeObject {
+ var prevConditions []string
+ for condition, export := range exports.AsObject().Entries() {
+ if excludeConditions != nil && excludeConditions.Has(condition) {
+ continue
+ }
+
+ conditionAlwaysMatches := condition == "default" || condition == "types" || IsApplicableVersionedTypesKey(condition)
+ newIncludeConditions := includeConditions
+ if !(conditionAlwaysMatches) {
+ newIncludeConditions = includeConditions.Clone()
+ excludeConditions = excludeConditions.Clone()
+ if newIncludeConditions == nil {
+ newIncludeConditions = &collections.Set[string]{}
+ }
+ newIncludeConditions.Add(condition)
+ for _, prevCondition := range prevConditions {
+ if excludeConditions == nil {
+ excludeConditions = &collections.Set[string]{}
+ }
+ excludeConditions.Add(prevCondition)
+ }
+ }
+
+ prevConditions = append(prevConditions, condition)
+ loadEntrypointsFromTargetExports(subpath, newIncludeConditions, excludeConditions, export)
+ if conditionAlwaysMatches {
+ break
+ }
+ }
+ }
+ }
+
+ switch exports.Type {
+ case packagejson.JSONValueTypeArray:
+ for _, element := range exports.AsArray() {
+ loadEntrypointsFromTargetExports(".", nil, nil, element)
+ }
+ case packagejson.JSONValueTypeObject:
+ if exports.IsSubpaths() {
+ for subpath, export := range exports.AsObject().Entries() {
+ loadEntrypointsFromTargetExports(subpath, nil, nil, export)
+ }
+ } else {
+ loadEntrypointsFromTargetExports(".", nil, nil, exports)
+ }
+ default:
+ loadEntrypointsFromTargetExports(".", nil, nil, exports)
+ }
+
+ return entrypoints
+}
+
+func (r *resolutionState) getMatchedStarForPatternEntrypoint(file string, leadingSlice string, trailingSlice string, caseSensitive bool) (string, bool) {
+ if stringutil.HasPrefixAndSuffixWithoutOverlap(file, leadingSlice, trailingSlice, caseSensitive) {
+ return file[len(leadingSlice) : len(file)-len(trailingSlice)], true
+ }
+
+ if jsExtension := TryGetJSExtensionForFile(file, r.compilerOptions); len(jsExtension) > 0 {
+ swapped := tspath.ChangeFullExtension(file, jsExtension)
+ if stringutil.HasPrefixAndSuffixWithoutOverlap(swapped, leadingSlice, trailingSlice, caseSensitive) {
+ return swapped[len(leadingSlice) : len(swapped)-len(trailingSlice)], true
+ }
+ }
+
+ return "", false
+}
diff --git a/internal/module/types.go b/internal/module/types.go
index 59b79950f3..2106544af8 100644
--- a/internal/module/types.go
+++ b/internal/module/types.go
@@ -135,13 +135,13 @@ func (e extensions) String() string {
func (e extensions) Array() []string {
result := []string{}
if e&extensionsTypeScript != 0 {
- result = append(result, tspath.ExtensionTs, tspath.ExtensionTsx)
+ result = append(result, tspath.SupportedTSImplementationExtensions...)
}
if e&extensionsJavaScript != 0 {
- result = append(result, tspath.ExtensionJs, tspath.ExtensionJsx)
+ result = append(result, tspath.SupportedJSExtensionsFlat...)
}
if e&extensionsDeclaration != 0 {
- result = append(result, tspath.ExtensionDts)
+ result = append(result, tspath.SupportedDeclarationExtensions...)
}
if e&extensionsJson != 0 {
result = append(result, tspath.ExtensionJson)
diff --git a/internal/module/util.go b/internal/module/util.go
index 04f75150f4..1247a7c7fd 100644
--- a/internal/module/util.go
+++ b/internal/module/util.go
@@ -14,6 +14,17 @@ var typeScriptVersion = semver.MustParse(core.Version())
const InferredTypesContainingFile = "__inferred type names__.ts"
+func IsApplicableVersionedTypesKey(key string) bool {
+ if !strings.HasPrefix(key, "types@") {
+ return false
+ }
+ range_, ok := semver.TryParseVersionRange(key[len("types@"):])
+ if !ok {
+ return false
+ }
+ return range_.Test(&typeScriptVersion)
+}
+
func ParseNodeModuleFromPath(resolved string, isFolder bool) string {
path := tspath.NormalizePath(resolved)
idx := strings.LastIndex(path, "/node_modules/")
@@ -67,6 +78,14 @@ func GetTypesPackageName(packageName string) string {
return "@types/" + MangleScopedPackageName(packageName)
}
+func GetPackageNameFromTypesPackageName(mangledName string) string {
+ withoutAtTypePrefix := strings.TrimPrefix(mangledName, "@types/")
+ if withoutAtTypePrefix != mangledName {
+ return UnmangleScopedPackageName(withoutAtTypePrefix)
+ }
+ return mangledName
+}
+
func ComparePatternKeys(a, b string) int {
aPatternIndex := strings.Index(a, "*")
bPatternIndex := strings.Index(b, "*")
@@ -153,3 +172,26 @@ func GetResolutionDiagnostic(options *core.CompilerOptions, resolvedModule *Reso
return needAllowArbitraryExtensions()
}
}
+
+// TryGetJSExtensionForFile maps TS/JS/DTS extensions to the output JS-side extension.
+// Returns an empty string if the extension is unsupported.
+func TryGetJSExtensionForFile(fileName string, options *core.CompilerOptions) string {
+ ext := tspath.TryGetExtensionFromPath(fileName)
+ switch ext {
+ case tspath.ExtensionTs, tspath.ExtensionDts:
+ return tspath.ExtensionJs
+ case tspath.ExtensionTsx:
+ if options.Jsx == core.JsxEmitPreserve {
+ return tspath.ExtensionJsx
+ }
+ return tspath.ExtensionJs
+ case tspath.ExtensionJs, tspath.ExtensionJsx, tspath.ExtensionJson:
+ return ext
+ case tspath.ExtensionDmts, tspath.ExtensionMts, tspath.ExtensionMjs:
+ return tspath.ExtensionMjs
+ case tspath.ExtensionDcts, tspath.ExtensionCts, tspath.ExtensionCjs:
+ return tspath.ExtensionCjs
+ default:
+ return ""
+ }
+}
diff --git a/internal/modulespecifiers/preferences.go b/internal/modulespecifiers/preferences.go
index f28f1293c6..16c8e9e4aa 100644
--- a/internal/modulespecifiers/preferences.go
+++ b/internal/modulespecifiers/preferences.go
@@ -144,6 +144,66 @@ type ModuleSpecifierPreferences struct {
excludeRegexes []string
}
+func GetAllowedEndingsInPreferredOrder(
+ prefs UserPreferences,
+ host ModuleSpecifierGenerationHost,
+ compilerOptions *core.CompilerOptions,
+ importingSourceFile SourceFileForSpecifierGeneration,
+ oldImportSpecifier string,
+ syntaxImpliedNodeFormat core.ResolutionMode,
+) []ModuleSpecifierEnding {
+ preferredEnding := getPreferredEnding(
+ prefs,
+ host,
+ compilerOptions,
+ importingSourceFile,
+ oldImportSpecifier,
+ core.ResolutionModeNone,
+ )
+ resolutionMode := host.GetDefaultResolutionModeForFile(importingSourceFile)
+ if resolutionMode != syntaxImpliedNodeFormat {
+ preferredEnding = getPreferredEnding(
+ prefs,
+ host,
+ compilerOptions,
+ importingSourceFile,
+ oldImportSpecifier,
+ syntaxImpliedNodeFormat,
+ )
+ }
+ moduleResolution := compilerOptions.GetModuleResolutionKind()
+ moduleResolutionIsNodeNext := core.ModuleResolutionKindNode16 <= moduleResolution && moduleResolution <= core.ModuleResolutionKindNodeNext
+ allowImportingTsExtension := shouldAllowImportingTsExtension(compilerOptions, importingSourceFile.FileName())
+ if syntaxImpliedNodeFormat == core.ResolutionModeESM && moduleResolutionIsNodeNext {
+ if allowImportingTsExtension {
+ return []ModuleSpecifierEnding{ModuleSpecifierEndingTsExtension, ModuleSpecifierEndingJsExtension}
+ }
+ return []ModuleSpecifierEnding{ModuleSpecifierEndingJsExtension}
+ }
+ switch preferredEnding {
+ case ModuleSpecifierEndingJsExtension:
+ if allowImportingTsExtension {
+ return []ModuleSpecifierEnding{ModuleSpecifierEndingJsExtension, ModuleSpecifierEndingTsExtension, ModuleSpecifierEndingMinimal, ModuleSpecifierEndingIndex}
+ }
+ return []ModuleSpecifierEnding{ModuleSpecifierEndingJsExtension, ModuleSpecifierEndingMinimal, ModuleSpecifierEndingIndex}
+ case ModuleSpecifierEndingTsExtension:
+ return []ModuleSpecifierEnding{ModuleSpecifierEndingTsExtension, ModuleSpecifierEndingMinimal, ModuleSpecifierEndingJsExtension, ModuleSpecifierEndingIndex}
+ case ModuleSpecifierEndingIndex:
+ if allowImportingTsExtension {
+ return []ModuleSpecifierEnding{ModuleSpecifierEndingIndex, ModuleSpecifierEndingMinimal, ModuleSpecifierEndingTsExtension, ModuleSpecifierEndingJsExtension}
+ }
+ return []ModuleSpecifierEnding{ModuleSpecifierEndingIndex, ModuleSpecifierEndingMinimal, ModuleSpecifierEndingJsExtension}
+ case ModuleSpecifierEndingMinimal:
+ if allowImportingTsExtension {
+ return []ModuleSpecifierEnding{ModuleSpecifierEndingMinimal, ModuleSpecifierEndingIndex, ModuleSpecifierEndingTsExtension, ModuleSpecifierEndingJsExtension}
+ }
+ return []ModuleSpecifierEnding{ModuleSpecifierEndingMinimal, ModuleSpecifierEndingIndex, ModuleSpecifierEndingJsExtension}
+ default:
+ debug.AssertNever(preferredEnding)
+ }
+ return []ModuleSpecifierEnding{ModuleSpecifierEndingMinimal}
+}
+
func getModuleSpecifierPreferences(
prefs UserPreferences,
host ModuleSpecifierGenerationHost,
@@ -170,59 +230,16 @@ func getModuleSpecifierPreferences(
// all others are shortest
}
}
- filePreferredEnding := getPreferredEnding(
- prefs,
- host,
- compilerOptions,
- importingSourceFile,
- oldImportSpecifier,
- core.ResolutionModeNone,
- )
getAllowedEndingsInPreferredOrder := func(syntaxImpliedNodeFormat core.ResolutionMode) []ModuleSpecifierEnding {
- preferredEnding := filePreferredEnding
- resolutionMode := host.GetDefaultResolutionModeForFile(importingSourceFile)
- if resolutionMode != syntaxImpliedNodeFormat {
- preferredEnding = getPreferredEnding(
- prefs,
- host,
- compilerOptions,
- importingSourceFile,
- oldImportSpecifier,
- syntaxImpliedNodeFormat,
- )
- }
- moduleResolution := compilerOptions.GetModuleResolutionKind()
- moduleResolutionIsNodeNext := core.ModuleResolutionKindNode16 <= moduleResolution && moduleResolution <= core.ModuleResolutionKindNodeNext
- allowImportingTsExtension := shouldAllowImportingTsExtension(compilerOptions, importingSourceFile.FileName())
- if syntaxImpliedNodeFormat == core.ResolutionModeESM && moduleResolutionIsNodeNext {
- if allowImportingTsExtension {
- return []ModuleSpecifierEnding{ModuleSpecifierEndingTsExtension, ModuleSpecifierEndingJsExtension}
- }
- return []ModuleSpecifierEnding{ModuleSpecifierEndingJsExtension}
- }
- switch preferredEnding {
- case ModuleSpecifierEndingJsExtension:
- if allowImportingTsExtension {
- return []ModuleSpecifierEnding{ModuleSpecifierEndingJsExtension, ModuleSpecifierEndingTsExtension, ModuleSpecifierEndingMinimal, ModuleSpecifierEndingIndex}
- }
- return []ModuleSpecifierEnding{ModuleSpecifierEndingJsExtension, ModuleSpecifierEndingMinimal, ModuleSpecifierEndingIndex}
- case ModuleSpecifierEndingTsExtension:
- return []ModuleSpecifierEnding{ModuleSpecifierEndingTsExtension, ModuleSpecifierEndingMinimal, ModuleSpecifierEndingJsExtension, ModuleSpecifierEndingIndex}
- case ModuleSpecifierEndingIndex:
- if allowImportingTsExtension {
- return []ModuleSpecifierEnding{ModuleSpecifierEndingIndex, ModuleSpecifierEndingMinimal, ModuleSpecifierEndingTsExtension, ModuleSpecifierEndingJsExtension}
- }
- return []ModuleSpecifierEnding{ModuleSpecifierEndingIndex, ModuleSpecifierEndingMinimal, ModuleSpecifierEndingJsExtension}
- case ModuleSpecifierEndingMinimal:
- if allowImportingTsExtension {
- return []ModuleSpecifierEnding{ModuleSpecifierEndingMinimal, ModuleSpecifierEndingIndex, ModuleSpecifierEndingTsExtension, ModuleSpecifierEndingJsExtension}
- }
- return []ModuleSpecifierEnding{ModuleSpecifierEndingMinimal, ModuleSpecifierEndingIndex, ModuleSpecifierEndingJsExtension}
- default:
- debug.AssertNever(preferredEnding)
- }
- return []ModuleSpecifierEnding{ModuleSpecifierEndingMinimal}
+ return GetAllowedEndingsInPreferredOrder(
+ prefs,
+ host,
+ compilerOptions,
+ importingSourceFile,
+ oldImportSpecifier,
+ syntaxImpliedNodeFormat,
+ )
}
return ModuleSpecifierPreferences{
diff --git a/internal/modulespecifiers/specifiers.go b/internal/modulespecifiers/specifiers.go
index 2a1dad8b50..5f20843648 100644
--- a/internal/modulespecifiers/specifiers.go
+++ b/internal/modulespecifiers/specifiers.go
@@ -51,7 +51,7 @@ func GetModuleSpecifiersWithInfo(
) ([]string, ResultKind) {
ambient := tryGetModuleNameFromAmbientModule(moduleSymbol, checker)
if len(ambient) > 0 {
- if forAutoImports && isExcludedByRegex(ambient, userPreferences.AutoImportSpecifierExcludeRegexes) {
+ if forAutoImports && IsExcludedByRegex(ambient, userPreferences.AutoImportSpecifierExcludeRegexes) {
return nil, ResultKindAmbient
}
return []string{ambient}, ResultKindAmbient
@@ -62,9 +62,29 @@ func GetModuleSpecifiersWithInfo(
return nil, ResultKindNone
}
+ return GetModuleSpecifiersForFileWithInfo(
+ importingSourceFile,
+ moduleSourceFile.FileName(),
+ compilerOptions,
+ host,
+ userPreferences,
+ options,
+ forAutoImports,
+ )
+}
+
+func GetModuleSpecifiersForFileWithInfo(
+ importingSourceFile SourceFileForSpecifierGeneration,
+ moduleFileName string,
+ compilerOptions *core.CompilerOptions,
+ host ModuleSpecifierGenerationHost,
+ userPreferences UserPreferences,
+ options ModuleSpecifierOptions,
+ forAutoImports bool,
+) ([]string, ResultKind) {
modulePaths := getAllModulePathsWorker(
getInfo(host.GetSourceOfProjectReferenceIfOutputIncluded(importingSourceFile), host),
- moduleSourceFile.FileName(),
+ moduleFileName,
host,
compilerOptions,
options,
@@ -83,7 +103,7 @@ func GetModuleSpecifiersWithInfo(
func tryGetModuleNameFromAmbientModule(moduleSymbol *ast.Symbol, checker CheckerShape) string {
for _, decl := range moduleSymbol.Declarations {
- if isNonGlobalAmbientModule(decl) && (!ast.IsModuleAugmentationExternal(decl) || !tspath.IsExternalModuleNameRelative(decl.Name().Text())) {
+ if ast.IsModuleWithStringLiteralName(decl) && (!ast.IsModuleAugmentationExternal(decl) || !tspath.IsExternalModuleNameRelative(decl.Name().Text())) {
return decl.Name().Text()
}
}
@@ -103,7 +123,7 @@ func tryGetModuleNameFromAmbientModule(moduleSymbol *ast.Symbol, checker Checker
continue
}
- possibleContainer := ast.FindAncestor(d, isNonGlobalAmbientModule)
+ possibleContainer := ast.FindAncestor(d, ast.IsModuleWithStringLiteralName)
if possibleContainer == nil || possibleContainer.Parent == nil || !ast.IsSourceFile(possibleContainer.Parent) {
continue
}
@@ -390,7 +410,7 @@ func computeModuleSpecifiers(
if modulePath.IsInNodeModules {
specifier = tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, userPreferences /*packageNameOnly*/, false, options.OverrideImportMode)
}
- if len(specifier) > 0 && !(forAutoImport && isExcludedByRegex(specifier, preferences.excludeRegexes)) {
+ if len(specifier) > 0 && !(forAutoImport && IsExcludedByRegex(specifier, preferences.excludeRegexes)) {
nodeModulesSpecifiers = append(nodeModulesSpecifiers, specifier)
if modulePath.IsRedirect {
// If we got a specifier for a redirect, it was a bare package specifier (e.g. "@foo/bar",
@@ -412,7 +432,7 @@ func computeModuleSpecifiers(
preferences,
/*pathsOnly*/ modulePath.IsRedirect || len(specifier) > 0,
)
- if len(local) == 0 || forAutoImport && isExcludedByRegex(local, preferences.excludeRegexes) {
+ if len(local) == 0 || forAutoImport && IsExcludedByRegex(local, preferences.excludeRegexes) {
continue
}
if modulePath.IsRedirect {
@@ -538,8 +558,8 @@ func getLocalModuleSpecifier(
return relativePath
}
- relativeIsExcluded := isExcludedByRegex(relativePath, preferences.excludeRegexes)
- nonRelativeIsExcluded := isExcludedByRegex(maybeNonRelative, preferences.excludeRegexes)
+ relativeIsExcluded := IsExcludedByRegex(relativePath, preferences.excludeRegexes)
+ nonRelativeIsExcluded := IsExcludedByRegex(maybeNonRelative, preferences.excludeRegexes)
if !relativeIsExcluded && nonRelativeIsExcluded {
return relativePath
}
@@ -627,7 +647,7 @@ func processEnding(
}
if tspath.FileExtensionIsOneOf(fileName, []string{tspath.ExtensionDmts, tspath.ExtensionDcts}) {
inputExt := tspath.GetDeclarationFileExtension(fileName)
- ext := getJsExtensionForDeclarationFileExtension(inputExt)
+ ext := GetJSExtensionForDeclarationFileExtension(inputExt)
return tspath.RemoveExtension(fileName, inputExt) + ext
}
if tspath.FileExtensionIsOneOf(fileName, []string{tspath.ExtensionMts, tspath.ExtensionCts}) {
@@ -774,7 +794,7 @@ func tryGetModuleNameAsNodeModule(
// If the module was found in @types, get the actual Node package name
nodeModulesDirectoryName := moduleSpecifier[parts.TopLevelPackageNameIndex+1:]
- return GetPackageNameFromTypesPackageName(nodeModulesDirectoryName)
+ return module.GetPackageNameFromTypesPackageName(nodeModulesDirectoryName)
}
type pkgJsonDirAttemptResult struct {
@@ -823,7 +843,7 @@ func tryDirectoryWithPackageJson(
// name in the package.json content via url/filepath dependency specifiers. We need to
// use the actual directory name, so don't look at `packageJsonContent.name` here.
nodeModulesDirectoryName := packageRootPath[parts.TopLevelPackageNameIndex+1:]
- packageName := GetPackageNameFromTypesPackageName(nodeModulesDirectoryName)
+ packageName := module.GetPackageNameFromTypesPackageName(nodeModulesDirectoryName)
// Determine resolution mode for package.json exports condition matching.
// TypeScript's tryDirectoryWithPackageJson uses the importing file's mode (moduleSpecifiers.ts:1257),
@@ -1179,7 +1199,7 @@ func tryGetModuleNameFromExportsOrImports(
pathOrPattern := tspath.GetNormalizedAbsolutePath(tspath.CombinePaths(packageDirectory, strValue), "")
var extensionSwappedTarget string
if tspath.HasTSFileExtension(targetFilePath) {
- extensionSwappedTarget = tspath.RemoveFileExtension(targetFilePath) + tryGetJSExtensionForFile(targetFilePath, options)
+ extensionSwappedTarget = tspath.RemoveFileExtension(targetFilePath) + module.TryGetJSExtensionForFile(targetFilePath, options)
}
canTryTsExtension := preferTsExtension && tspath.HasImplementationTSFileExtension(targetFilePath)
@@ -1241,7 +1261,7 @@ func tryGetModuleNameFromExportsOrImports(
if len(declarationFile) > 0 && stringutil.HasPrefixAndSuffixWithoutOverlap(declarationFile, leadingSlice, trailingSlice, caseSensitive) {
starReplacement := declarationFile[len(leadingSlice) : len(declarationFile)-len(trailingSlice)]
substituted := replaceFirstStar(packageName, starReplacement)
- jsExtension := tryGetJSExtensionForFile(declarationFile, options)
+ jsExtension := module.TryGetJSExtensionForFile(declarationFile, options)
if len(jsExtension) > 0 {
return tspath.ChangeFullExtension(substituted, jsExtension)
}
@@ -1260,7 +1280,7 @@ func tryGetModuleNameFromExportsOrImports(
// conditional mapping
obj := exports.AsObject()
for key, value := range obj.Entries() {
- if key == "default" || slices.Contains(conditions, key) || isApplicableVersionedTypesKey(conditions, key) {
+ if key == "default" || slices.Contains(conditions, key) || slices.Contains(conditions, "types") && module.IsApplicableVersionedTypesKey(key) {
result := tryGetModuleNameFromExportsOrImports(options, host, targetFilePath, packageDirectory, packageName, value, conditions, mode, isImports, preferTsExtension)
if len(result) > 0 {
return result
diff --git a/internal/modulespecifiers/util.go b/internal/modulespecifiers/util.go
index 12a5c3a342..a6d75afece 100644
--- a/internal/modulespecifiers/util.go
+++ b/internal/modulespecifiers/util.go
@@ -12,7 +12,6 @@ import (
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/module"
"github.com/microsoft/typescript-go/internal/packagejson"
- "github.com/microsoft/typescript-go/internal/semver"
"github.com/microsoft/typescript-go/internal/tsoptions"
"github.com/microsoft/typescript-go/internal/tspath"
)
@@ -27,10 +26,6 @@ var (
regexPatternCache = make(map[regexPatternCacheKey]*regexp2.Regexp)
)
-func isNonGlobalAmbientModule(node *ast.Node) bool {
- return ast.IsModuleDeclaration(node) && ast.IsStringLiteral(node.Name())
-}
-
func comparePathsByRedirectAndNumberOfDirectorySeparators(a ModulePath, b ModulePath) int {
if a.IsRedirect == b.IsRedirect {
return strings.Count(a.FileName, "/") - strings.Count(b.FileName, "/")
@@ -45,7 +40,7 @@ func PathIsBareSpecifier(path string) bool {
return !tspath.PathIsAbsolute(path) && !tspath.PathIsRelative(path)
}
-func isExcludedByRegex(moduleSpecifier string, excludes []string) bool {
+func IsExcludedByRegex(moduleSpecifier string, excludes []string) bool {
for _, pattern := range excludes {
re := stringToRegex(pattern)
if re == nil {
@@ -140,7 +135,7 @@ func ensurePathIsNonModuleName(path string) string {
return path
}
-func getJsExtensionForDeclarationFileExtension(ext string) string {
+func GetJSExtensionForDeclarationFileExtension(ext string) string {
switch ext {
case tspath.ExtensionDts:
return tspath.ExtensionJs
@@ -155,7 +150,7 @@ func getJsExtensionForDeclarationFileExtension(ext string) string {
}
func getJSExtensionForFile(fileName string, options *core.CompilerOptions) string {
- result := tryGetJSExtensionForFile(fileName, options)
+ result := module.TryGetJSExtensionForFile(fileName, options)
if len(result) == 0 {
panic(fmt.Sprintf("Extension %s is unsupported:: FileName:: %s", extensionFromPath(fileName), fileName))
}
@@ -174,27 +169,6 @@ func extensionFromPath(path string) string {
return ext
}
-func tryGetJSExtensionForFile(fileName string, options *core.CompilerOptions) string {
- ext := tspath.TryGetExtensionFromPath(fileName)
- switch ext {
- case tspath.ExtensionTs, tspath.ExtensionDts:
- return tspath.ExtensionJs
- case tspath.ExtensionTsx:
- if options.Jsx == core.JsxEmitPreserve {
- return tspath.ExtensionJsx
- }
- return tspath.ExtensionJs
- case tspath.ExtensionJs, tspath.ExtensionJsx, tspath.ExtensionJson:
- return ext
- case tspath.ExtensionDmts, tspath.ExtensionMts, tspath.ExtensionMjs:
- return tspath.ExtensionMjs
- case tspath.ExtensionDcts, tspath.ExtensionCts, tspath.ExtensionCjs:
- return tspath.ExtensionCjs
- default:
- return ""
- }
-}
-
func tryGetAnyFileFromPath(host ModuleSpecifierGenerationHost, path string) bool {
// !!! TODO: shouldn't this use readdir instead of fileexists for perf?
// We check all js, `node` and `json` extensions in addition to TS, since node module resolution would also choose those over the directory
@@ -271,22 +245,6 @@ func prefersTsExtension(allowedEndings []ModuleSpecifierEnding) bool {
return false
}
-var typeScriptVersion = semver.MustParse(core.Version()) // TODO: unify with clone inside module resolver?
-
-func isApplicableVersionedTypesKey(conditions []string, key string) bool {
- if !slices.Contains(conditions, "types") {
- return false // only apply versioned types conditions if the types condition is applied
- }
- if !strings.HasPrefix(key, "types@") {
- return false
- }
- range_, ok := semver.TryParseVersionRange(key[len("types@"):])
- if !ok {
- return false
- }
- return range_.Test(&typeScriptVersion)
-}
-
func replaceFirstStar(s string, replacement string) string {
return strings.Replace(s, "*", replacement, 1)
}
@@ -378,14 +336,6 @@ func GetNodeModulesPackageName(
return ""
}
-func GetPackageNameFromTypesPackageName(mangledName string) string {
- withoutAtTypePrefix := strings.TrimPrefix(mangledName, "@types/")
- if withoutAtTypePrefix != mangledName {
- return module.UnmangleScopedPackageName(withoutAtTypePrefix)
- }
- return mangledName
-}
-
func allKeysStartWithDot(obj *collections.OrderedMap[string, packagejson.ExportsOrImports]) bool {
for k := range obj.Keys() {
if !strings.HasPrefix(k, ".") {
@@ -394,3 +344,133 @@ func allKeysStartWithDot(obj *collections.OrderedMap[string, packagejson.Exports
}
return true
}
+
+func GetPackageNameFromDirectory(fileOrDirectoryPath string) string {
+ idx := strings.LastIndex(fileOrDirectoryPath, "/node_modules/")
+ if idx == -1 {
+ return ""
+ }
+
+ basename := fileOrDirectoryPath[idx+len("/node_modules/"):]
+ nextSlash := strings.Index(basename, "/")
+ if nextSlash == -1 {
+ return basename
+ }
+
+ if basename[0] != '@' || nextSlash == len(basename)-1 {
+ return basename[:nextSlash]
+ }
+
+ secondSlash := strings.Index(basename[nextSlash+1:], "/")
+ if secondSlash == -1 {
+ return basename
+ }
+
+ return basename[:nextSlash+1+secondSlash]
+}
+
+// ProcessEntrypointEnding processes a pre-computed module specifier from a package.json exports
+// entrypoint according to the entrypoint's Ending type and the user's preferred endings.
+func ProcessEntrypointEnding(
+ entrypoint *module.ResolvedEntrypoint,
+ prefs UserPreferences,
+ host ModuleSpecifierGenerationHost,
+ options *core.CompilerOptions,
+ importingSourceFile SourceFileForSpecifierGeneration,
+ allowedEndings []ModuleSpecifierEnding,
+) string {
+ specifier := entrypoint.ModuleSpecifier
+ if entrypoint.Ending == module.EndingFixed {
+ return specifier
+ }
+
+ if len(allowedEndings) == 0 {
+ allowedEndings = GetAllowedEndingsInPreferredOrder(
+ prefs,
+ host,
+ options,
+ importingSourceFile,
+ "",
+ host.GetDefaultResolutionModeForFile(importingSourceFile),
+ )
+ }
+
+ preferredEnding := allowedEndings[0]
+
+ // Handle declaration file extensions
+ dtsExtension := tspath.GetDeclarationFileExtension(specifier)
+ if dtsExtension != "" {
+ switch preferredEnding {
+ case ModuleSpecifierEndingTsExtension, ModuleSpecifierEndingJsExtension:
+ // Map .d.ts -> .js, .d.mts -> .mjs, .d.cts -> .cjs
+ jsExtension := GetJSExtensionForDeclarationFileExtension(dtsExtension)
+ return tspath.ChangeAnyExtension(specifier, jsExtension, []string{dtsExtension}, false)
+ case ModuleSpecifierEndingMinimal, ModuleSpecifierEndingIndex:
+ if entrypoint.Ending == module.EndingChangeable {
+ // .d.mts/.d.cts must keep an extension; rewrite to .mjs/.cjs instead of dropping
+ if dtsExtension == tspath.ExtensionDts {
+ specifier = tspath.RemoveExtension(specifier, dtsExtension)
+ if preferredEnding == ModuleSpecifierEndingMinimal {
+ specifier = strings.TrimSuffix(specifier, "/index")
+ }
+ return specifier
+ }
+ jsExtension := GetJSExtensionForDeclarationFileExtension(dtsExtension)
+ return tspath.ChangeAnyExtension(specifier, jsExtension, []string{dtsExtension}, false)
+ }
+ // EndingExtensionChangeable - can only change extension, not remove it
+ jsExtension := GetJSExtensionForDeclarationFileExtension(dtsExtension)
+ return tspath.ChangeAnyExtension(specifier, jsExtension, []string{dtsExtension}, false)
+ }
+ return specifier
+ }
+
+ // Handle .ts/.tsx/.mts/.cts extensions
+ if tspath.FileExtensionIsOneOf(specifier, []string{tspath.ExtensionTs, tspath.ExtensionTsx, tspath.ExtensionMts, tspath.ExtensionCts}) {
+ switch preferredEnding {
+ case ModuleSpecifierEndingTsExtension:
+ return specifier
+ case ModuleSpecifierEndingJsExtension:
+ if jsExtension := module.TryGetJSExtensionForFile(specifier, options); jsExtension != "" {
+ return tspath.RemoveFileExtension(specifier) + jsExtension
+ }
+ return specifier
+ case ModuleSpecifierEndingMinimal, ModuleSpecifierEndingIndex:
+ if entrypoint.Ending == module.EndingChangeable {
+ specifier = tspath.RemoveFileExtension(specifier)
+ if preferredEnding == ModuleSpecifierEndingMinimal {
+ specifier = strings.TrimSuffix(specifier, "/index")
+ }
+ return specifier
+ }
+ // EndingExtensionChangeable - can only change extension, not remove it
+ if jsExtension := module.TryGetJSExtensionForFile(specifier, options); jsExtension != "" {
+ return tspath.RemoveFileExtension(specifier) + jsExtension
+ }
+ return specifier
+ }
+ return specifier
+ }
+
+ // Handle .js/.jsx/.mjs/.cjs extensions
+ if tspath.FileExtensionIsOneOf(specifier, []string{tspath.ExtensionJs, tspath.ExtensionJsx, tspath.ExtensionMjs, tspath.ExtensionCjs}) {
+ switch preferredEnding {
+ case ModuleSpecifierEndingTsExtension, ModuleSpecifierEndingJsExtension:
+ return specifier
+ case ModuleSpecifierEndingMinimal, ModuleSpecifierEndingIndex:
+ if entrypoint.Ending == module.EndingChangeable {
+ specifier = tspath.RemoveFileExtension(specifier)
+ if preferredEnding == ModuleSpecifierEndingMinimal {
+ specifier = strings.TrimSuffix(specifier, "/index")
+ }
+ return specifier
+ }
+ // EndingExtensionChangeable - keep the extension
+ return specifier
+ }
+ return specifier
+ }
+
+ // For other extensions (like .json), return as-is
+ return specifier
+}
diff --git a/internal/packagejson/jsonvalue.go b/internal/packagejson/jsonvalue.go
index 5c38a59310..048b8f2153 100644
--- a/internal/packagejson/jsonvalue.go
+++ b/internal/packagejson/jsonvalue.go
@@ -44,6 +44,10 @@ type JSONValue struct {
Value any
}
+func (v *JSONValue) IsPresent() bool {
+ return v.Type != JSONValueTypeNotPresent
+}
+
func (v *JSONValue) IsFalsy() bool {
switch v.Type {
case JSONValueTypeNotPresent, JSONValueTypeNull:
@@ -73,6 +77,13 @@ func (v JSONValue) AsArray() []JSONValue {
return v.Value.([]JSONValue)
}
+func (v JSONValue) AsString() string {
+ if v.Type != JSONValueTypeString {
+ panic(fmt.Sprintf("expected string, got %v", v.Type))
+ }
+ return v.Value.(string)
+}
+
var _ json.UnmarshalerFrom = (*JSONValue)(nil)
func (v *JSONValue) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
diff --git a/internal/packagejson/packagejson.go b/internal/packagejson/packagejson.go
index 191a639185..dae1d64b77 100644
--- a/internal/packagejson/packagejson.go
+++ b/internal/packagejson/packagejson.go
@@ -56,6 +56,37 @@ func (df *DependencyFields) HasDependency(name string) bool {
return false
}
+func (df *DependencyFields) RangeDependencies(f func(name, version, dependencyField string) bool) {
+ if deps, ok := df.Dependencies.GetValue(); ok {
+ for name, version := range deps {
+ if !f(name, version, "dependencies") {
+ return
+ }
+ }
+ }
+ if devDeps, ok := df.DevDependencies.GetValue(); ok {
+ for name, version := range devDeps {
+ if !f(name, version, "devDependencies") {
+ return
+ }
+ }
+ }
+ if peerDeps, ok := df.PeerDependencies.GetValue(); ok {
+ for name, version := range peerDeps {
+ if !f(name, version, "peerDependencies") {
+ return
+ }
+ }
+ }
+ if optDeps, ok := df.OptionalDependencies.GetValue(); ok {
+ for name, version := range optDeps {
+ if !f(name, version, "optionalDependencies") {
+ return
+ }
+ }
+ }
+}
+
func (df *DependencyFields) GetRuntimeDependencyNames() *collections.Set[string] {
var count int
deps, _ := df.Dependencies.GetValue()
diff --git a/internal/parser/references.go b/internal/parser/references.go
index 7a6b4c01c8..4d688c5ec5 100644
--- a/internal/parser/references.go
+++ b/internal/parser/references.go
@@ -55,10 +55,7 @@ func collectModuleReferences(file *ast.SourceFile, node *ast.Statement, inAmbien
if ast.IsExternalModule(file) || (inAmbientModule && !tspath.IsExternalModuleNameRelative(nameText)) {
file.ModuleAugmentations = append(file.ModuleAugmentations, node.AsModuleDeclaration().Name())
} else if !inAmbientModule {
- if file.IsDeclarationFile {
- // for global .d.ts files record name of ambient module
- file.AmbientModuleNames = append(file.AmbientModuleNames, nameText)
- }
+ file.AmbientModuleNames = append(file.AmbientModuleNames, nameText)
// An AmbientExternalModuleDeclaration declares an external module.
// This type of declaration is permitted only in the global module.
// The StringLiteral must specify a top - level external module name.
diff --git a/internal/project/autoimport.go b/internal/project/autoimport.go
new file mode 100644
index 0000000000..1f8d1e3a7c
--- /dev/null
+++ b/internal/project/autoimport.go
@@ -0,0 +1,171 @@
+package project
+
+import (
+ "sync"
+
+ "github.com/microsoft/typescript-go/internal/ast"
+ "github.com/microsoft/typescript-go/internal/collections"
+ "github.com/microsoft/typescript-go/internal/compiler"
+ "github.com/microsoft/typescript-go/internal/core"
+ "github.com/microsoft/typescript-go/internal/ls/autoimport"
+ "github.com/microsoft/typescript-go/internal/packagejson"
+ "github.com/microsoft/typescript-go/internal/tspath"
+ "github.com/microsoft/typescript-go/internal/vfs"
+)
+
+type autoImportBuilderFS struct {
+ snapshotFSBuilder *snapshotFSBuilder
+ untrackedFiles collections.SyncMap[tspath.Path, FileHandle]
+}
+
+var _ FileSource = (*autoImportBuilderFS)(nil)
+
+// FS implements FileSource.
+func (a *autoImportBuilderFS) FS() vfs.FS {
+ return a.snapshotFSBuilder.fs
+}
+
+// GetFile implements FileSource.
+func (a *autoImportBuilderFS) GetFile(fileName string) FileHandle {
+ path := a.snapshotFSBuilder.toPath(fileName)
+ return a.GetFileByPath(fileName, path)
+}
+
+// GetFileByPath implements FileSource.
+func (a *autoImportBuilderFS) GetFileByPath(fileName string, path tspath.Path) FileHandle {
+ // We want to avoid long-term caching of files referenced only by auto-imports, so we
+ // override GetFileByPath to avoid collecting more files into the snapshotFSBuilder's
+ // diskFiles. (Note the reason we can't just use the finalized SnapshotFS is that changed
+ // files not read during other parts of the snapshot clone will be marked as dirty, but
+ // not yet refreshed from disk.)
+ if overlay, ok := a.snapshotFSBuilder.overlays[path]; ok {
+ return overlay
+ }
+ if diskFile, ok := a.snapshotFSBuilder.diskFiles.Load(path); ok {
+ return a.snapshotFSBuilder.reloadEntryIfNeeded(diskFile)
+ }
+ if fh, ok := a.untrackedFiles.Load(path); ok {
+ return fh
+ }
+ var fh FileHandle
+ content, ok := a.snapshotFSBuilder.fs.ReadFile(fileName)
+ if ok {
+ fh = newDiskFile(fileName, content)
+ }
+ fh, _ = a.untrackedFiles.LoadOrStore(path, fh)
+ return fh
+}
+
+type autoImportRegistryCloneHost struct {
+ projectCollection *ProjectCollection
+ parseCache *ParseCache
+ fs *sourceFS
+ currentDirectory string
+
+ filesMu sync.Mutex
+ files []ParseCacheKey
+}
+
+var _ autoimport.RegistryCloneHost = (*autoImportRegistryCloneHost)(nil)
+
+func newAutoImportRegistryCloneHost(
+ projectCollection *ProjectCollection,
+ parseCache *ParseCache,
+ snapshotFSBuilder *snapshotFSBuilder,
+ currentDirectory string,
+ toPath func(fileName string) tspath.Path,
+) *autoImportRegistryCloneHost {
+ return &autoImportRegistryCloneHost{
+ projectCollection: projectCollection,
+ parseCache: parseCache,
+ fs: newSourceFS(false, &autoImportBuilderFS{snapshotFSBuilder: snapshotFSBuilder}, toPath),
+ }
+}
+
+// FS implements autoimport.RegistryCloneHost.
+func (a *autoImportRegistryCloneHost) FS() vfs.FS {
+ return a.fs
+}
+
+// GetCurrentDirectory implements autoimport.RegistryCloneHost.
+func (a *autoImportRegistryCloneHost) GetCurrentDirectory() string {
+ return a.currentDirectory
+}
+
+// GetDefaultProject implements autoimport.RegistryCloneHost.
+func (a *autoImportRegistryCloneHost) GetDefaultProject(path tspath.Path) (tspath.Path, *compiler.Program) {
+ project := a.projectCollection.GetDefaultProject(path)
+ if project == nil {
+ return "", nil
+ }
+ return project.configFilePath, project.GetProgram()
+}
+
+// GetPackageJson implements autoimport.RegistryCloneHost.
+func (a *autoImportRegistryCloneHost) GetPackageJson(fileName string) *packagejson.InfoCacheEntry {
+ // !!! ref-counted shared cache
+ fh := a.fs.GetFile(fileName)
+ packageDirectory := tspath.GetDirectoryPath(fileName)
+ if fh == nil {
+ return &packagejson.InfoCacheEntry{
+ DirectoryExists: a.fs.DirectoryExists(packageDirectory),
+ PackageDirectory: packageDirectory,
+ }
+ }
+ fields, err := packagejson.Parse([]byte(fh.Content()))
+ if err != nil {
+ return &packagejson.InfoCacheEntry{
+ DirectoryExists: true,
+ PackageDirectory: tspath.GetDirectoryPath(fileName),
+ Contents: &packagejson.PackageJson{
+ Parseable: false,
+ },
+ }
+ }
+ return &packagejson.InfoCacheEntry{
+ DirectoryExists: true,
+ PackageDirectory: tspath.GetDirectoryPath(fileName),
+ Contents: &packagejson.PackageJson{
+ Fields: fields,
+ Parseable: true,
+ },
+ }
+}
+
+// GetProgramForProject implements autoimport.RegistryCloneHost.
+func (a *autoImportRegistryCloneHost) GetProgramForProject(projectPath tspath.Path) *compiler.Program {
+ project := a.projectCollection.GetProjectByPath(projectPath)
+ if project == nil {
+ return nil
+ }
+ return project.GetProgram()
+}
+
+// GetSourceFile implements autoimport.RegistryCloneHost.
+func (a *autoImportRegistryCloneHost) GetSourceFile(fileName string, path tspath.Path) *ast.SourceFile {
+ fh := a.fs.GetFile(fileName)
+ if fh == nil {
+ return nil
+ }
+ opts := ast.SourceFileParseOptions{
+ FileName: fileName,
+ Path: path,
+ CompilerOptions: core.EmptyCompilerOptions.SourceFileAffecting(),
+ JSDocParsingMode: ast.JSDocParsingModeParseAll,
+ }
+ key := NewParseCacheKey(opts, fh.Hash(), fh.Kind())
+
+ a.filesMu.Lock()
+ a.files = append(a.files, key)
+ a.filesMu.Unlock()
+ return a.parseCache.Acquire(key, fh)
+}
+
+// Dispose implements autoimport.RegistryCloneHost.
+func (a *autoImportRegistryCloneHost) Dispose() {
+ a.filesMu.Lock()
+ defer a.filesMu.Unlock()
+ for _, key := range a.files {
+ a.parseCache.Deref(key)
+ }
+}
diff --git a/internal/project/compilerhost.go b/internal/project/compilerhost.go
index d36d67593a..9e329ec4c2 100644
--- a/internal/project/compilerhost.go
+++ b/internal/project/compilerhost.go
@@ -1,10 +1,7 @@
package project
import (
- "time"
-
"github.com/microsoft/typescript-go/internal/ast"
- "github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/compiler"
"github.com/microsoft/typescript-go/internal/diagnostics"
"github.com/microsoft/typescript-go/internal/project/logging"
@@ -20,54 +17,27 @@ type compilerHost struct {
currentDirectory string
sessionOptions *SessionOptions
- fs *snapshotFSBuilder
- compilerFS *compilerFS
+ sourceFS *sourceFS
configFileRegistry *ConfigFileRegistry
- seenFiles *collections.SyncSet[tspath.Path]
project *Project
builder *ProjectCollectionBuilder
logger *logging.LogTree
}
-type builderFileSource struct {
- seenFiles *collections.SyncSet[tspath.Path]
- snapshotFSBuilder *snapshotFSBuilder
-}
-
-func (c *builderFileSource) GetFile(fileName string) FileHandle {
- path := c.snapshotFSBuilder.toPath(fileName)
- c.seenFiles.Add(path)
- return c.snapshotFSBuilder.GetFileByPath(fileName, path)
-}
-
-func (c *builderFileSource) FS() vfs.FS {
- return c.snapshotFSBuilder.FS()
-}
-
func newCompilerHost(
currentDirectory string,
project *Project,
builder *ProjectCollectionBuilder,
logger *logging.LogTree,
) *compilerHost {
- seenFiles := &collections.SyncSet[tspath.Path]{}
- compilerFS := &compilerFS{
- source: &builderFileSource{
- seenFiles: seenFiles,
- snapshotFSBuilder: builder.fs,
- },
- }
-
return &compilerHost{
configFilePath: project.configFilePath,
currentDirectory: currentDirectory,
sessionOptions: builder.sessionOptions,
- compilerFS: compilerFS,
- seenFiles: seenFiles,
+ sourceFS: newSourceFS(true, builder.fs, builder.toPath),
- fs: builder.fs,
project: project,
builder: builder,
logger: logger,
@@ -80,9 +50,9 @@ func (c *compilerHost) freeze(snapshotFS *SnapshotFS, configFileRegistry *Config
if c.builder == nil {
panic("freeze can only be called once")
}
- c.compilerFS.source = snapshotFS
+ c.sourceFS.source = snapshotFS
+ c.sourceFS.DisableTracking()
c.configFileRegistry = configFileRegistry
- c.fs = nil
c.builder = nil
c.project = nil
c.logger = nil
@@ -101,7 +71,7 @@ func (c *compilerHost) DefaultLibraryPath() string {
// FS implements compiler.CompilerHost.
func (c *compilerHost) FS() vfs.FS {
- return c.compilerFS
+ return c.sourceFS
}
// GetCurrentDirectory implements compiler.CompilerHost.
@@ -114,7 +84,8 @@ func (c *compilerHost) GetResolvedProjectReference(fileName string, path tspath.
if c.builder == nil {
return c.configFileRegistry.GetConfig(path)
} else {
- c.seenFiles.Add(path)
+ // acquireConfigForProject will bypass sourceFS, so track the file here.
+ c.sourceFS.Track(fileName)
return c.builder.configFileRegistryBuilder.acquireConfigForProject(fileName, path, c.project, c.logger)
}
}
@@ -124,8 +95,7 @@ func (c *compilerHost) GetResolvedProjectReference(fileName string, path tspath.
// be a corresponding release for each call made.
func (c *compilerHost) GetSourceFile(opts ast.SourceFileParseOptions) *ast.SourceFile {
c.ensureAlive()
- c.seenFiles.Add(opts.Path)
- if fh := c.fs.GetFileByPath(opts.FileName, opts.Path); fh != nil {
+ if fh := c.sourceFS.GetFileByPath(opts.FileName, opts.Path); fh != nil {
return c.builder.parseCache.Acquire(NewParseCacheKey(opts, fh.Hash(), fh.Kind()), fh)
}
return nil
@@ -135,70 +105,3 @@ func (c *compilerHost) GetSourceFile(opts ast.SourceFileParseOptions) *ast.Sourc
func (c *compilerHost) Trace(msg *diagnostics.Message, args ...any) {
panic("unimplemented")
}
-
-var _ vfs.FS = (*compilerFS)(nil)
-
-type compilerFS struct {
- source FileSource
-}
-
-// DirectoryExists implements vfs.FS.
-func (fs *compilerFS) DirectoryExists(path string) bool {
- return fs.source.FS().DirectoryExists(path)
-}
-
-// FileExists implements vfs.FS.
-func (fs *compilerFS) FileExists(path string) bool {
- if fh := fs.source.GetFile(path); fh != nil {
- return true
- }
- return fs.source.FS().FileExists(path)
-}
-
-// GetAccessibleEntries implements vfs.FS.
-func (fs *compilerFS) GetAccessibleEntries(path string) vfs.Entries {
- return fs.source.FS().GetAccessibleEntries(path)
-}
-
-// ReadFile implements vfs.FS.
-func (fs *compilerFS) ReadFile(path string) (contents string, ok bool) {
- if fh := fs.source.GetFile(path); fh != nil {
- return fh.Content(), true
- }
- return "", false
-}
-
-// Realpath implements vfs.FS.
-func (fs *compilerFS) Realpath(path string) string {
- return fs.source.FS().Realpath(path)
-}
-
-// Stat implements vfs.FS.
-func (fs *compilerFS) Stat(path string) vfs.FileInfo {
- return fs.source.FS().Stat(path)
-}
-
-// UseCaseSensitiveFileNames implements vfs.FS.
-func (fs *compilerFS) UseCaseSensitiveFileNames() bool {
- return fs.source.FS().UseCaseSensitiveFileNames()
-}
-
-// WalkDir implements vfs.FS.
-func (fs *compilerFS) WalkDir(root string, walkFn vfs.WalkDirFunc) error {
- panic("unimplemented")
-}
-
-// WriteFile implements vfs.FS.
-func (fs *compilerFS) WriteFile(path string, data string, writeByteOrderMark bool) error {
- panic("unimplemented")
-}
-
-// Remove implements vfs.FS.
-func (fs *compilerFS) Remove(path string) error {
- panic("unimplemented")
-}
-
-// Chtimes implements vfs.FS.
-func (fs *compilerFS) Chtimes(path string, atime time.Time, mtime time.Time) error {
- panic("unimplemented")
-}
diff --git a/internal/project/dirty/map.go b/internal/project/dirty/map.go
index 10f72d7ae4..e90d8b7130 100644
--- a/internal/project/dirty/map.go
+++ b/internal/project/dirty/map.go
@@ -19,6 +19,17 @@ func (e *MapEntry[K, V]) Change(apply func(V)) {
apply(e.value)
}
+func (e *MapEntry[K, V]) Replace(newValue V) {
+ if e.delete {
+ panic("tried to change a deleted entry")
+ }
+ if !e.dirty {
+ e.dirty = true
+ e.m.dirty[e.key] = e
+ }
+ e.value = newValue
+}
+
func (e *MapEntry[K, V]) ChangeIf(cond func(V) bool, apply func(V)) bool {
if cond(e.Value()) {
e.Change(apply)
@@ -96,10 +107,16 @@ func (m *Map[K, V]) Change(key K, apply func(V)) {
}
}
-func (m *Map[K, V]) Delete(key K) {
+func (m *Map[K, V]) TryDelete(key K) bool {
if entry, ok := m.Get(key); ok {
entry.Delete()
- } else {
+ return true
+ }
+ return false
+}
+
+func (m *Map[K, V]) Delete(key K) {
+ if !m.TryDelete(key) {
panic("tried to delete a non-existent entry")
}
}
diff --git a/internal/project/dirty/mapbuilder.go b/internal/project/dirty/mapbuilder.go
new file mode 100644
index 0000000000..f14e205bbd
--- /dev/null
+++ b/internal/project/dirty/mapbuilder.go
@@ -0,0 +1,74 @@
+package dirty
+
+import "maps"
+
+type MapBuilder[K comparable, VBase any, VBuilder any] struct {
+ base map[K]VBase
+ dirty map[K]VBuilder
+ deleted map[K]struct{}
+
+ toBuilder func(VBase) VBuilder
+ build func(VBuilder) VBase
+}
+
+func NewMapBuilder[K comparable, VBase any, VBuilder any](
+ base map[K]VBase,
+ toBuilder func(VBase) VBuilder,
+ build func(VBuilder) VBase,
+) *MapBuilder[K, VBase, VBuilder] {
+ return &MapBuilder[K, VBase, VBuilder]{
+ base: base,
+ dirty: make(map[K]VBuilder),
+ toBuilder: toBuilder,
+ build: build,
+ }
+}
+
+func (mb *MapBuilder[K, VBase, VBuilder]) Set(key K, value VBuilder) {
+ mb.dirty[key] = value
+ delete(mb.deleted, key)
+}
+
+func (mb *MapBuilder[K, VBase, VBuilder]) Delete(key K) {
+ if mb.deleted == nil {
+ mb.deleted = make(map[K]struct{})
+ }
+ mb.deleted[key] = struct{}{}
+ delete(mb.dirty, key)
+}
+
+func (mb *MapBuilder[K, VBase, VBuilder]) Clear() {
+ mb.dirty = make(map[K]VBuilder)
+ mb.deleted = make(map[K]struct{}, len(mb.base))
+ for key := range mb.base {
+ mb.deleted[key] = struct{}{}
+ }
+}
+
+func (mb *MapBuilder[K, VBase, VBuilder]) Has(key K) bool {
+ if _, ok := mb.deleted[key]; ok {
+ return false
+ }
+ if _, ok := mb.dirty[key]; ok {
+ return true
+ }
+ _, ok := mb.base[key]
+ return ok
+}
+
+func (mb *MapBuilder[K, VBase, VBuilder]) Build() map[K]VBase {
+ if len(mb.dirty) == 0 && len(mb.deleted) == 0 {
+ return mb.base
+ }
+ result := maps.Clone(mb.base)
+ if result == nil {
+ result = make(map[K]VBase)
+ }
+ for key := range mb.deleted {
+ delete(result, key)
+ }
+ for key, value := range mb.dirty {
+ result[key] = mb.build(value)
+ }
+ return result
+}
diff --git a/internal/project/projectcollection.go b/internal/project/projectcollection.go
index 079c80d0ce..c34bf6f08a 100644
--- a/internal/project/projectcollection.go
+++ b/internal/project/projectcollection.go
@@ -108,7 +108,7 @@ func (c *ProjectCollection) GetProjectsContainingFile(path tspath.Path) []ls.Pro
}
// !!! result could be cached
-func (c *ProjectCollection) GetDefaultProject(fileName string, path tspath.Path) *Project {
+func (c *ProjectCollection) GetDefaultProject(path tspath.Path) *Project {
if result, ok := c.fileDefaultProjects[path]; ok {
if result == inferredProjectName {
return c.inferredProject
@@ -155,20 +155,20 @@ func (c *ProjectCollection) GetDefaultProject(fileName string, path tspath.Path)
return firstConfiguredProject
}
// Multiple projects include the file directly.
- if defaultProject := c.findDefaultConfiguredProject(fileName, path); defaultProject != nil {
+ if defaultProject := c.findDefaultConfiguredProject(path); defaultProject != nil {
return defaultProject
}
return firstConfiguredProject
}
-func (c *ProjectCollection) findDefaultConfiguredProject(fileName string, path tspath.Path) *Project {
+func (c *ProjectCollection) findDefaultConfiguredProject(path tspath.Path) *Project {
if configFileName := c.configFileRegistry.GetConfigFileName(path); configFileName != "" {
- return c.findDefaultConfiguredProjectWorker(fileName, path, configFileName, nil, nil)
+ return c.findDefaultConfiguredProjectWorker(path, configFileName, nil, nil)
}
return nil
}
-func (c *ProjectCollection) findDefaultConfiguredProjectWorker(fileName string, path tspath.Path, configFileName string, visited *collections.SyncSet[*Project], fallback *Project) *Project {
+func (c *ProjectCollection) findDefaultConfiguredProjectWorker(path tspath.Path, configFileName string, visited *collections.SyncSet[*Project], fallback *Project) *Project {
configFilePath := c.toPath(configFileName)
project, ok := c.configuredProjects[configFilePath]
if !ok {
@@ -218,7 +218,7 @@ func (c *ProjectCollection) findDefaultConfiguredProjectWorker(fileName string,
return fallback
}
if ancestorConfigName := c.configFileRegistry.GetAncestorConfigFileName(path, configFileName); ancestorConfigName != "" {
- return c.findDefaultConfiguredProjectWorker(fileName, path, ancestorConfigName, visited, fallback)
+ return c.findDefaultConfiguredProjectWorker(path, ancestorConfigName, visited, fallback)
}
return fallback
}
diff --git a/internal/project/projectcollectionbuilder.go b/internal/project/projectcollectionbuilder.go
index fb06c9f6cc..269e2f96d5 100644
--- a/internal/project/projectcollectionbuilder.go
+++ b/internal/project/projectcollectionbuilder.go
@@ -981,7 +981,7 @@ func (b *ProjectCollectionBuilder) updateProgram(entry dirty.Value[*Project], lo
project.ProgramUpdateKind = result.UpdateKind
project.ProgramLastUpdate = b.newSnapshotID
if result.UpdateKind == ProgramUpdateKindCloned {
- project.host.seenFiles = oldHost.seenFiles
+ project.host.sourceFS.seenFiles = oldHost.sourceFS.seenFiles
}
if result.UpdateKind == ProgramUpdateKindNewFiles {
filesChanged = true
diff --git a/internal/project/refcountcache.go b/internal/project/refcountcache.go
index 6c499ff8fb..8a18d0412e 100644
--- a/internal/project/refcountcache.go
+++ b/internal/project/refcountcache.go
@@ -38,8 +38,8 @@ func NewRefCountCache[K comparable, V any, AcquireArgs any](
}
}
-// Acquire retrieves or creates a cache entry for the given identity and hash.
-// If an entry exists with matching identity and hash, its refcount is incremented
+// Acquire retrieves or creates a cache entry for the given identity.
+// If an entry exists with matching identity, its refcount is incremented
// and the cached value is returned. Otherwise, parse() is called to create the
// value, which is stored and returned with refcount 1.
//
diff --git a/internal/project/session.go b/internal/project/session.go
index 0ef5d204b9..0abd13a2be 100644
--- a/internal/project/session.go
+++ b/internal/project/session.go
@@ -36,6 +36,7 @@ const (
UpdateReasonRequestedLanguageServiceForFileNotOpen
UpdateReasonRequestedLanguageServiceProjectDirty
UpdateReasonRequestedLoadProjectTree
+ UpdateReasonRequestedLanguageServiceWithAutoImports
)
// SessionOptions are the immutable initialization options for a session.
@@ -159,11 +160,24 @@ func NewSession(init *SessionInit) *Session {
fs: init.FS,
},
init.Options,
- parseCache,
- extendedConfigCache,
&ConfigFileRegistry{},
nil,
Config{},
+ nil,
+ NewWatchedFiles(
+ "auto-import",
+ lsproto.WatchKindCreate|lsproto.WatchKindChange|lsproto.WatchKindDelete,
+ func(nodeModulesDirs map[tspath.Path]string) PatternsAndIgnored {
+ patterns := make([]string, 0, len(nodeModulesDirs))
+ for _, dir := range nodeModulesDirs {
+ patterns = append(patterns, getRecursiveGlobPattern(dir))
+ }
+ slices.Sort(patterns)
+ return PatternsAndIgnored{
+ patterns: patterns,
+ }
+ },
+ ),
toPath,
),
pendingATAChanges: make(map[tspath.Path]*ATAStateChange),
@@ -414,6 +428,8 @@ func (s *Session) getSnapshot(
updateReason = UpdateReasonRequestedLanguageServiceProjectDirty
} else if request.ProjectTree != nil {
updateReason = UpdateReasonRequestedLoadProjectTree
+ } else if request.AutoImports != "" {
+ updateReason = UpdateReasonRequestedLanguageServiceWithAutoImports
} else {
for _, document := range request.Documents {
if snapshot.fs.isOpenFile(document.FileName()) {
@@ -453,7 +469,7 @@ func (s *Session) getSnapshotAndDefaultProject(ctx context.Context, uri lsproto.
if project == nil {
return nil, nil, nil, fmt.Errorf("no project found for URI %s", uri)
}
- return snapshot, project, ls.NewLanguageService(project.GetProgram(), snapshot), nil
+ return snapshot, project, ls.NewLanguageService(project.configFilePath, project.GetProgram(), snapshot), nil
}
func (s *Session) GetLanguageService(ctx context.Context, uri lsproto.DocumentUri) (*ls.LanguageService, error) {
@@ -499,7 +515,7 @@ func (s *Session) GetLanguageServiceForProjectWithFile(ctx context.Context, proj
if !project.HasFile(uri.FileName()) {
return nil
}
- return ls.NewLanguageService(project.GetProgram(), snapshot)
+ return ls.NewLanguageService(project.configFilePath, project.GetProgram(), snapshot)
}
func (s *Session) GetSnapshotLoadingProjectTree(
@@ -514,6 +530,22 @@ func (s *Session) GetSnapshotLoadingProjectTree(
return snapshot
}
+// GetLanguageServiceWithAutoImports clones the current snapshot with a request to
+// prepare auto-imports for the given URI, then returns a LanguageService for the
+// default project of that URI. It should only be called after GetLanguageService.
+// !!! take snapshot that GetLanguageService initially returned
+func (s *Session) GetLanguageServiceWithAutoImports(ctx context.Context, uri lsproto.DocumentUri) (*ls.LanguageService, error) {
+ snapshot := s.getSnapshot(ctx, ResourceRequest{
+ Documents: []lsproto.DocumentUri{uri},
+ AutoImports: uri,
+ })
+ project := snapshot.GetDefaultProject(uri)
+ if project == nil {
+ return nil, fmt.Errorf("no project found for URI %s", uri)
+ }
+ return ls.NewLanguageService(project.configFilePath, project.GetProgram(), snapshot), nil
+}
+
func (s *Session) UpdateSnapshot(ctx context.Context, overlays map[tspath.Path]*Overlay, change SnapshotChange) *Snapshot {
s.snapshotMu.Lock()
oldSnapshot := s.snapshot
@@ -545,6 +577,7 @@ func (s *Session) UpdateSnapshot(ctx context.Context, overlays map[tspath.Path]*
}
}
s.publishProgramDiagnostics(oldSnapshot, newSnapshot)
+ s.warmAutoImportCache(ctx, change, oldSnapshot, newSnapshot)
})
return newSnapshot
@@ -679,6 +712,10 @@ func (s *Session) updateWatches(oldSnapshot *Snapshot, newSnapshot *Snapshot) er
},
)
+ if oldSnapshot.autoImportsWatch.ID() != newSnapshot.autoImportsWatch.ID() {
+ errors = append(errors, updateWatch(ctx, s, s.logger, oldSnapshot.autoImportsWatch, newSnapshot.autoImportsWatch)...)
+ }
+
if len(errors) > 0 {
return fmt.Errorf("errors updating watches: %v", errors)
} else if s.options.LoggingEnabled {
@@ -788,6 +825,25 @@ func (s *Session) logCacheStats(snapshot *Snapshot) {
s.logger.Logf("Parse cache size: %6d", parseCacheSize)
s.logger.Logf("Program count: %6d", programCount)
s.logger.Logf("Extended config cache size: %6d", extendedConfigCount)
+
+ s.logger.Log("Auto Imports:")
+ autoImportStats := snapshot.AutoImportRegistry().GetCacheStats()
+ if len(autoImportStats.ProjectBuckets) > 0 {
+ s.logger.Log("\tProject buckets:")
+ for _, bucket := range autoImportStats.ProjectBuckets {
+ s.logger.Logf("\t\t%s%s:", bucket.Path, core.IfElse(bucket.State.Dirty(), " (dirty)", ""))
+ s.logger.Logf("\t\t\tFiles: %d", bucket.FileCount)
+ s.logger.Logf("\t\t\tExports: %d", bucket.ExportCount)
+ }
+ }
+ if len(autoImportStats.NodeModulesBuckets) > 0 {
+ s.logger.Log("\tnode_modules buckets:")
+ for _, bucket := range autoImportStats.NodeModulesBuckets {
+ s.logger.Logf("\t\t%s%s:", bucket.Path, core.IfElse(bucket.State.Dirty(), " (dirty)", ""))
+ s.logger.Logf("\t\t\tFiles: %d", bucket.FileCount)
+ s.logger.Logf("\t\t\tExports: %d", bucket.ExportCount)
+ }
+ }
}
}
@@ -904,3 +960,27 @@ func (s *Session) triggerATAForUpdatedProjects(newSnapshot *Snapshot) {
}
}
}
+
+func (s *Session) warmAutoImportCache(ctx context.Context, change SnapshotChange, oldSnapshot, newSnapshot *Snapshot) {
+ if change.fileChanges.Changed.Len() == 1 {
+ var changedFile lsproto.DocumentUri
+ for uri := range change.fileChanges.Changed.Keys() {
+ changedFile = uri
+ }
+ if !newSnapshot.fs.isOpenFile(changedFile.FileName()) {
+ return
+ }
+ project := newSnapshot.GetDefaultProject(changedFile)
+ if project == nil {
+ return
+ }
+ if newSnapshot.AutoImports.IsPreparedForImportingFile(
+ changedFile.FileName(),
+ project.configFilePath,
+ newSnapshot.config.tsUserPreferences.OrDefault(),
+ ) {
+ return
+ }
+ _, _ = s.GetLanguageServiceWithAutoImports(ctx, changedFile)
+ }
+}
diff --git a/internal/project/snapshot.go b/internal/project/snapshot.go
index 50889c7cbd..f8ece3835a 100644
--- a/internal/project/snapshot.go
+++ b/internal/project/snapshot.go
@@ -12,6 +12,7 @@ import (
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/format"
"github.com/microsoft/typescript-go/internal/ls"
+ "github.com/microsoft/typescript-go/internal/ls/autoimport"
"github.com/microsoft/typescript-go/internal/ls/lsconv"
"github.com/microsoft/typescript-go/internal/ls/lsutil"
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
@@ -37,6 +38,8 @@ type Snapshot struct {
fs *SnapshotFS
ProjectCollection *ProjectCollection
ConfigFileRegistry *ConfigFileRegistry
+ AutoImports *autoimport.Registry
+ autoImportsWatch *WatchedFiles[map[tspath.Path]string]
compilerOptionsForInferredProjects *core.CompilerOptions
config Config
@@ -49,11 +52,11 @@ func NewSnapshot(
id uint64,
fs *SnapshotFS,
sessionOptions *SessionOptions,
- parseCache *ParseCache,
- extendedConfigCache *ExtendedConfigCache,
configFileRegistry *ConfigFileRegistry,
compilerOptionsForInferredProjects *core.CompilerOptions,
config Config,
+ autoImports *autoimport.Registry,
+ autoImportsWatch *WatchedFiles[map[tspath.Path]string],
toPath func(fileName string) tspath.Path,
) *Snapshot {
s := &Snapshot{
@@ -67,6 +70,8 @@ func NewSnapshot(
ProjectCollection: &ProjectCollection{toPath: toPath},
compilerOptionsForInferredProjects: compilerOptionsForInferredProjects,
config: config,
+ AutoImports: autoImports,
+ autoImportsWatch: autoImportsWatch,
}
s.converters = lsconv.NewConverters(s.sessionOptions.PositionEncoding, s.LSPLineMap)
s.refCount.Store(1)
@@ -74,9 +79,7 @@ func NewSnapshot(
}
func (s *Snapshot) GetDefaultProject(uri lsproto.DocumentUri) *Project {
- fileName := uri.FileName()
- path := s.toPath(fileName)
- return s.ProjectCollection.GetDefaultProject(fileName, path)
+ return s.ProjectCollection.GetDefaultProject(uri.Path(s.UseCaseSensitiveFileNames()))
}
func (s *Snapshot) GetProjectsContainingFile(uri lsproto.DocumentUri) []ls.Project {
@@ -116,6 +119,10 @@ func (s *Snapshot) Converters() *lsconv.Converters {
return s.converters
}
+func (s *Snapshot) AutoImportRegistry() *autoimport.Registry {
+ return s.AutoImports
+}
+
func (s *Snapshot) ID() uint64 {
return s.id
}
@@ -170,6 +177,8 @@ type ResourceRequest struct {
// This is used to compute the solution and project tree so that
// we can find references across all the projects in the solution irrespective of which project is open
ProjectTree *ProjectTreeRequest
+ // AutoImports is the document URI for which auto imports should be prepared.
+ AutoImports lsproto.DocumentUri
}
type SnapshotChange struct {
@@ -317,23 +326,23 @@ func (s *Snapshot) Clone(ctx context.Context, change SnapshotChange, overlays ma
projectCollection, configFileRegistry := projectCollectionBuilder.Finalize(logger)
+ projectsWithNewProgramStructure := make(map[tspath.Path]bool)
+ for _, project := range projectCollection.Projects() {
+ if project.ProgramLastUpdate == newSnapshotID && project.ProgramUpdateKind != ProgramUpdateKindCloned {
+ projectsWithNewProgramStructure[project.configFilePath] = project.ProgramUpdateKind == ProgramUpdateKindNewFiles
+ }
+ }
+
// Clean cached disk files not touched by any open project. It's not important that we do this on
// file open specifically, but we don't need to do it on every snapshot clone.
if len(change.fileChanges.Opened) != 0 {
- var changedFiles bool
- for _, project := range projectCollection.Projects() {
- if project.ProgramLastUpdate == newSnapshotID && project.ProgramUpdateKind != ProgramUpdateKindCloned {
- changedFiles = true
- break
- }
- }
// The set of seen files can change only if a program was constructed (not cloned) during this snapshot.
- if changedFiles {
+ if len(projectsWithNewProgramStructure) > 0 {
cleanFilesStart := time.Now()
removedFiles := 0
fs.diskFiles.Range(func(entry *dirty.SyncMapEntry[tspath.Path, *diskFile]) bool {
for _, project := range projectCollection.Projects() {
- if project.host != nil && project.host.seenFiles.Has(entry.Key()) {
+ if project.host != nil && project.host.sourceFS.Seen(entry.Key()) {
return true
}
}
@@ -357,16 +366,49 @@ func (s *Snapshot) Clone(ctx context.Context, change SnapshotChange, overlays ma
}
}
+ autoImportHost := newAutoImportRegistryCloneHost(
+ projectCollection,
+ session.parseCache,
+ fs,
+ s.sessionOptions.CurrentDirectory,
+ s.toPath,
+ )
+ openFiles := make(map[tspath.Path]string, len(overlays))
+ for path, overlay := range overlays {
+ openFiles[path] = overlay.FileName()
+ }
+ oldAutoImports := s.AutoImports
+ if oldAutoImports == nil {
+ oldAutoImports = autoimport.NewRegistry(s.toPath)
+ }
+ prepareAutoImports := tspath.Path("")
+ if change.ResourceRequest.AutoImports != "" {
+ prepareAutoImports = change.ResourceRequest.AutoImports.Path(s.UseCaseSensitiveFileNames())
+ }
+ var autoImportsWatch *WatchedFiles[map[tspath.Path]string]
+ autoImports, err := oldAutoImports.Clone(ctx, autoimport.RegistryChange{
+ RequestedFile: prepareAutoImports,
+ OpenFiles: openFiles,
+ Changed: change.fileChanges.Changed,
+ Created: change.fileChanges.Created,
+ Deleted: change.fileChanges.Deleted,
+ RebuiltPrograms: projectsWithNewProgramStructure,
+ UserPreferences: config.tsUserPreferences,
+ }, autoImportHost, logger.Fork("UpdateAutoImports"))
+ if err == nil {
+ autoImportsWatch = s.autoImportsWatch.Clone(autoImports.NodeModulesDirectories())
+ }
+
snapshotFS, _ := fs.Finalize()
newSnapshot := NewSnapshot(
newSnapshotID,
snapshotFS,
s.sessionOptions,
- session.parseCache,
- session.extendedConfigCache,
nil,
compilerOptionsForInferredProjects,
config,
+ autoImports,
+ autoImportsWatch,
s.toPath,
)
newSnapshot.parentId = s.id
diff --git a/internal/project/snapshot_test.go b/internal/project/snapshot_test.go
index ded5e16b5e..8032907bc1 100644
--- a/internal/project/snapshot_test.go
+++ b/internal/project/snapshot_test.go
@@ -67,7 +67,7 @@ func TestSnapshot(t *testing.T) {
assert.Equal(t, snapshotBefore.ProjectCollection.InferredProject(), snapshotAfter.ProjectCollection.InferredProject())
assert.Equal(t, snapshotAfter.ProjectCollection.InferredProject().ProgramUpdateKind, ProgramUpdateKindNewFiles)
// host for inferred project should not change
- assert.Equal(t, snapshotAfter.ProjectCollection.InferredProject().host.compilerFS.source, snapshotBefore.fs)
+ assert.Equal(t, snapshotAfter.ProjectCollection.InferredProject().host.sourceFS.source, snapshotBefore.fs)
})
t.Run("cached disk files are cleaned up", func(t *testing.T) {
diff --git a/internal/project/snapshotfs.go b/internal/project/snapshotfs.go
index bff9168ba9..46fe914bdb 100644
--- a/internal/project/snapshotfs.go
+++ b/internal/project/snapshotfs.go
@@ -3,6 +3,7 @@ package project
import (
"strings"
"sync"
+ "time"
"github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
@@ -16,6 +17,7 @@ import (
type FileSource interface {
FS() vfs.FS
GetFile(fileName string) FileHandle
+ GetFileByPath(fileName string, path tspath.Path) FileHandle
}
var (
@@ -38,10 +40,14 @@ func (s *SnapshotFS) FS() vfs.FS {
}
func (s *SnapshotFS) GetFile(fileName string) FileHandle {
- if file, ok := s.overlays[s.toPath(fileName)]; ok {
+ return s.GetFileByPath(fileName, s.toPath(fileName))
+}
+
+func (s *SnapshotFS) GetFileByPath(fileName string, path tspath.Path) FileHandle {
+ if file, ok := s.overlays[path]; ok {
return file
}
- if file, ok := s.diskFiles[s.toPath(fileName)]; ok {
+ if file, ok := s.diskFiles[path]; ok {
return file
}
newEntry := memoizedDiskFile(sync.OnceValue(func() FileHandle {
@@ -50,7 +56,7 @@ func (s *SnapshotFS) GetFile(fileName string) FileHandle {
}
return nil
}))
- entry, _ := s.readFiles.LoadOrStore(s.toPath(fileName), newEntry)
+ entry, _ := s.readFiles.LoadOrStore(path, newEntry)
return entry()
}
@@ -112,22 +118,26 @@ func (s *snapshotFSBuilder) GetFileByPath(fileName string, path tspath.Path) Fil
if file, ok := s.overlays[path]; ok {
return file
}
- entry, _ := s.diskFiles.LoadOrStore(path, &diskFile{fileBase: fileBase{fileName: fileName}, needsReload: true})
- if entry != nil {
- entry.Locked(func(entry dirty.Value[*diskFile]) {
- if entry.Value() != nil && !entry.Value().MatchesDiskText() {
- if content, ok := s.fs.ReadFile(fileName); ok {
- entry.Change(func(file *diskFile) {
- file.content = content
- file.hash = xxh3.HashString128(content)
- file.needsReload = false
- })
- } else {
- entry.Delete()
- }
- }
- })
+ if entry, _ := s.diskFiles.LoadOrStore(path, &diskFile{fileBase: fileBase{fileName: fileName}, needsReload: true}); entry != nil {
+ return s.reloadEntryIfNeeded(entry)
}
+ return nil
+}
+
+func (s *snapshotFSBuilder) reloadEntryIfNeeded(entry *dirty.SyncMapEntry[tspath.Path, *diskFile]) FileHandle {
+ entry.Locked(func(entry dirty.Value[*diskFile]) {
+ if entry.Value() != nil && !entry.Value().MatchesDiskText() {
+ if content, ok := s.fs.ReadFile(entry.Value().fileName); ok {
+ entry.Change(func(file *diskFile) {
+ file.content = content
+ file.hash = xxh3.HashString128(content)
+ file.needsReload = false
+ })
+ } else {
+ entry.Delete()
+ }
+ }
+ })
if entry == nil || entry.Value() == nil {
return nil
}
@@ -188,3 +198,114 @@ func (s *snapshotFSBuilder) markDirtyFiles(change FileChangeSummary) {
}
}
}
+
+// sourceFS is a vfs.FS that sources files from a FileSource and tracks seen files.
+type sourceFS struct {
+ tracking bool
+ toPath func(fileName string) tspath.Path
+ seenFiles *collections.SyncSet[tspath.Path]
+ source FileSource
+}
+
+func newSourceFS(tracking bool, source FileSource, toPath func(fileName string) tspath.Path) *sourceFS {
+ fs := &sourceFS{
+ tracking: tracking,
+ toPath: toPath,
+ source: source,
+ }
+ if tracking {
+ fs.seenFiles = &collections.SyncSet[tspath.Path]{}
+ }
+ return fs
+}
+
+var _ vfs.FS = (*sourceFS)(nil)
+
+func (fs *sourceFS) DisableTracking() {
+ fs.tracking = false
+}
+
+func (fs *sourceFS) Track(fileName string) {
+ if !fs.tracking {
+ return
+ }
+ fs.seenFiles.Add(fs.toPath(fileName))
+}
+
+func (fs *sourceFS) Seen(path tspath.Path) bool {
+ if fs.seenFiles == nil {
+ return false
+ }
+ return fs.seenFiles.Has(path)
+}
+
+func (fs *sourceFS) GetFile(fileName string) FileHandle {
+ fs.Track(fileName)
+ return fs.source.GetFile(fileName)
+}
+
+func (fs *sourceFS) GetFileByPath(fileName string, path tspath.Path) FileHandle {
+ fs.Track(fileName)
+ return fs.source.GetFileByPath(fileName, path)
+}
+
+// DirectoryExists implements vfs.FS.
+func (fs *sourceFS) DirectoryExists(path string) bool {
+ return fs.source.FS().DirectoryExists(path)
+}
+
+// FileExists implements vfs.FS.
+func (fs *sourceFS) FileExists(path string) bool {
+ if fh := fs.GetFile(path); fh != nil {
+ return true
+ }
+ return fs.source.FS().FileExists(path)
+}
+
+// GetAccessibleEntries implements vfs.FS.
+func (fs *sourceFS) GetAccessibleEntries(path string) vfs.Entries {
+ return fs.source.FS().GetAccessibleEntries(path)
+}
+
+// ReadFile implements vfs.FS.
+func (fs *sourceFS) ReadFile(path string) (contents string, ok bool) {
+ if fh := fs.GetFile(path); fh != nil {
+ return fh.Content(), true
+ }
+ return "", false
+}
+
+// Realpath implements vfs.FS.
+func (fs *sourceFS) Realpath(path string) string {
+ return fs.source.FS().Realpath(path)
+}
+
+// Stat implements vfs.FS.
+func (fs *sourceFS) Stat(path string) vfs.FileInfo {
+ return fs.source.FS().Stat(path)
+}
+
+// UseCaseSensitiveFileNames implements vfs.FS.
+func (fs *sourceFS) UseCaseSensitiveFileNames() bool {
+ return fs.source.FS().UseCaseSensitiveFileNames()
+}
+
+// WalkDir implements vfs.FS.
+func (fs *sourceFS) WalkDir(root string, walkFn vfs.WalkDirFunc) error {
+ return fs.source.FS().WalkDir(root, walkFn)
+}
+
+// WriteFile implements vfs.FS.
+func (fs *sourceFS) WriteFile(path string, data string, writeByteOrderMark bool) error {
+ panic("unimplemented")
+}
+
+// Remove implements vfs.FS.
+func (fs *sourceFS) Remove(path string) error {
+ panic("unimplemented")
+}
+
+// Chtimes implements vfs.FS.
+func (fs *sourceFS) Chtimes(path string, atime time.Time, mtime time.Time) error {
+ panic("unimplemented")
+}
diff --git a/internal/testutil/autoimporttestutil/fixtures.go b/internal/testutil/autoimporttestutil/fixtures.go
new file mode 100644
index 0000000000..0fcdb64b5d
--- /dev/null
+++ b/internal/testutil/autoimporttestutil/fixtures.go
@@ -0,0 +1,590 @@
+package autoimporttestutil
+
+import (
+ "fmt"
+ "maps"
+ "slices"
+ "strings"
+ "testing"
+
+ "github.com/microsoft/typescript-go/internal/ls/lsconv"
+ "github.com/microsoft/typescript-go/internal/lsp/lsproto"
+ "github.com/microsoft/typescript-go/internal/project"
+ "github.com/microsoft/typescript-go/internal/testutil/projecttestutil"
+ "github.com/microsoft/typescript-go/internal/tspath"
+)
+
+// FileHandle represents a file created for an autoimport lifecycle test.
+type FileHandle struct {
+ fileName string
+ content string
+}
+
+func (f FileHandle) FileName() string { return f.fileName }
+func (f FileHandle) Content() string { return f.content }
+func (f FileHandle) URI() lsproto.DocumentUri { return lsconv.FileNameToDocumentURI(f.fileName) }
+
+// ProjectFileHandle adds export metadata for TypeScript source files.
+type ProjectFileHandle struct {
+ FileHandle
+ exportIdentifier string
+}
+
+// NodeModulesPackageHandle describes a generated package under node_modules.
+type NodeModulesPackageHandle struct {
+ Name string
+ Directory string
+ packageJSON FileHandle
+ declaration FileHandle
+}
+
+func (p NodeModulesPackageHandle) PackageJSONFile() FileHandle { return p.packageJSON }
+func (p NodeModulesPackageHandle) DeclarationFile() FileHandle { return p.declaration }
+
+// MonorepoHandle exposes the generated monorepo layout including root and packages.
+type MonorepoHandle struct {
+ root string
+ rootNodeModules []NodeModulesPackageHandle
+ rootDependencies []string
+ packages []ProjectHandle
+ rootTSConfig FileHandle
+ rootPackageJSON FileHandle
+}
+
+func (m MonorepoHandle) Root() string { return m.root }
+func (m MonorepoHandle) RootNodeModules() []NodeModulesPackageHandle {
+ return slices.Clone(m.rootNodeModules)
+}
+func (m MonorepoHandle) RootDependencies() []string { return slices.Clone(m.rootDependencies) }
+func (m MonorepoHandle) Packages() []ProjectHandle { return slices.Clone(m.packages) }
+func (m MonorepoHandle) Package(index int) ProjectHandle {
+ if index < 0 || index >= len(m.packages) {
+ panic(fmt.Sprintf("package index %d out of range", index))
+ }
+ return m.packages[index]
+}
+func (m MonorepoHandle) RootTSConfig() FileHandle { return m.rootTSConfig }
+func (m MonorepoHandle) RootPackageJSONFile() FileHandle { return m.rootPackageJSON }
+
+// ProjectHandle exposes the generated project layout for a fixture project root.
+type ProjectHandle struct {
+ root string
+ files []ProjectFileHandle
+ tsconfig FileHandle
+ packageJSON FileHandle
+ nodeModules []NodeModulesPackageHandle
+ dependencies []string
+}
+
+func (p ProjectHandle) Root() string { return p.root }
+func (p ProjectHandle) Files() []ProjectFileHandle { return slices.Clone(p.files) }
+func (p ProjectHandle) File(index int) ProjectFileHandle {
+ if index < 0 || index >= len(p.files) {
+ panic(fmt.Sprintf("file index %d out of range", index))
+ }
+ return p.files[index]
+}
+func (p ProjectHandle) TSConfig() FileHandle { return p.tsconfig }
+func (p ProjectHandle) PackageJSONFile() FileHandle { return p.packageJSON }
+func (p ProjectHandle) NodeModules() []NodeModulesPackageHandle {
+ return slices.Clone(p.nodeModules)
+}
+func (p ProjectHandle) Dependencies() []string { return slices.Clone(p.dependencies) }
+
+func (p ProjectHandle) NodeModuleByName(name string) *NodeModulesPackageHandle {
+ for i := range p.nodeModules {
+ if p.nodeModules[i].Name == name {
+ return &p.nodeModules[i]
+ }
+ }
+ return nil
+}
+
+// Fixture encapsulates a fully-initialized auto import lifecycle test session.
+type Fixture struct {
+ session *project.Session
+ utils *projecttestutil.SessionUtils
+ projects []ProjectHandle
+}
+
+func (f *Fixture) Session() *project.Session { return f.session }
+func (f *Fixture) Utils() *projecttestutil.SessionUtils { return f.utils }
+func (f *Fixture) Projects() []ProjectHandle { return slices.Clone(f.projects) }
+func (f *Fixture) Project(index int) ProjectHandle {
+ if index < 0 || index >= len(f.projects) {
+ panic(fmt.Sprintf("project index %d out of range", index))
+ }
+ return f.projects[index]
+}
+func (f *Fixture) SingleProject() ProjectHandle { return f.Project(0) }
+
+// MonorepoFixture encapsulates a fully-initialized monorepo lifecycle test session.
+type MonorepoFixture struct {
+ session *project.Session
+ utils *projecttestutil.SessionUtils
+ monorepo MonorepoHandle
+ extra []FileHandle
+}
+
+func (f *MonorepoFixture) Session() *project.Session { return f.session }
+func (f *MonorepoFixture) Utils() *projecttestutil.SessionUtils { return f.utils }
+func (f *MonorepoFixture) Monorepo() MonorepoHandle { return f.monorepo }
+func (f *MonorepoFixture) ExtraFiles() []FileHandle { return slices.Clone(f.extra) }
+func (f *MonorepoFixture) ExtraFile(path string) FileHandle {
+ normalized := normalizeAbsolutePath(path)
+ for _, handle := range f.extra {
+ if handle.fileName == normalized {
+ return handle
+ }
+ }
+ panic("extra file not found: " + path)
+}
+
+// MonorepoPackageTemplate captures the reusable settings for a package.json scope:
+// the node_modules packages that exist alongside the package.json and the dependency
+// names that should be written into that package.json. When DependencyNames is empty,
+// all available node_modules packages in scope are used.
+type MonorepoPackageTemplate struct {
+ Name string
+ NodeModuleNames []string
+ DependencyNames []string
+}
+
+// MonorepoSetupConfig describes the monorepo root and packages to create.
+// The embedded MonorepoPackageTemplate describes the monorepo root package located at
+// Root. DependencyNames defaults to NodeModuleNames when empty.
+// Package.MonorepoPackageTemplate.DependencyNames defaults to the union of the root
+// node_modules packages and the package's own NodeModuleNames when empty.
+type MonorepoSetupConfig struct {
+ Root string
+ MonorepoPackageTemplate
+ Packages []MonorepoPackageConfig
+ ExtraFiles []TextFileSpec
+}
+
+type MonorepoPackageConfig struct {
+ FileCount int
+ MonorepoPackageTemplate
+}
+
+// TextFileSpec describes an additional file to place in the fixture.
+type TextFileSpec struct {
+ Path string
+ Content string
+}
+
+// SetupMonorepoLifecycleSession builds a monorepo workspace with root-level node_modules
+// and multiple packages, each potentially with their own node_modules.
+// The structure is:
+//
+// root/
+// ├── tsconfig.json (base config)
+// ├── package.json
+// ├── node_modules/
+// │ └──
+// └── packages/
+// ├── package-a/
+// │ ├── tsconfig.json
+// │ ├── package.json
+// │ ├── node_modules/
+// │ │ └──
+// │ └── *.ts files
+// └── package-b/
+// └── ...
+func SetupMonorepoLifecycleSession(t *testing.T, config MonorepoSetupConfig) *MonorepoFixture {
+ t.Helper()
+ builder := newFileMapBuilder(nil)
+
+ monorepoRoot := normalizeAbsolutePath(config.Root)
+ monorepoName := config.MonorepoPackageTemplate.Name
+ if monorepoName == "" {
+ monorepoName = "monorepo"
+ }
+
+ // Add root tsconfig.json
+ rootTSConfigPath := tspath.CombinePaths(monorepoRoot, "tsconfig.json")
+ rootTSConfigContent := "{\n \"compilerOptions\": {\n \"module\": \"esnext\",\n \"target\": \"esnext\",\n \"strict\": true,\n \"baseUrl\": \".\",\n \"allowJs\": true,\n \"checkJs\": true\n }\n}\n"
+ builder.AddTextFile(rootTSConfigPath, rootTSConfigContent)
+ rootTSConfig := FileHandle{fileName: rootTSConfigPath, content: rootTSConfigContent}
+
+ // Add root node_modules
+ rootNodeModulesDir := tspath.CombinePaths(monorepoRoot, "node_modules")
+ rootNodeModules := builder.AddNodeModulesPackagesWithNames(rootNodeModulesDir, config.NodeModuleNames)
+
+ // Add root package.json with dependencies (default to all root node_modules if unspecified)
+ rootDependencies := selectPackagesByName(rootNodeModules, config.DependencyNames)
+ rootPackageJSON := builder.addRootPackageJSON(monorepoRoot, monorepoName, rootDependencies)
+ rootDependencyNames := packageNames(rootDependencies)
+
+ // Build each package in packages/
+ packagesDir := tspath.CombinePaths(monorepoRoot, "packages")
+ packageHandles := make([]ProjectHandle, 0, len(config.Packages))
+ for _, pkg := range config.Packages {
+ pkgDir := tspath.CombinePaths(packagesDir, pkg.Name)
+ builder.AddLocalProject(pkgDir, pkg.FileCount)
+
+ var pkgNodeModules []NodeModulesPackageHandle
+ if len(pkg.NodeModuleNames) > 0 {
+ pkgNodeModulesDir := tspath.CombinePaths(pkgDir, "node_modules")
+ pkgNodeModules = builder.AddNodeModulesPackagesWithNames(pkgNodeModulesDir, pkg.NodeModuleNames)
+ }
+
+ availableDeps := append(slices.Clone(rootNodeModules), pkgNodeModules...)
+ selectedDeps := selectPackagesByName(availableDeps, pkg.DependencyNames)
+ if len(selectedDeps) > 0 {
+ builder.AddPackageJSONWithDependenciesNamed(pkgDir, pkg.Name, selectedDeps)
+ }
+ }
+
+ // Add arbitrary extra files
+ extraHandles := make([]FileHandle, 0, len(config.ExtraFiles))
+ for _, extra := range config.ExtraFiles {
+ builder.AddTextFile(extra.Path, extra.Content)
+ extraHandles = append(extraHandles, FileHandle{fileName: normalizeAbsolutePath(extra.Path), content: extra.Content})
+ }
+
+ // Build project handles after all packages are created
+ for _, pkg := range config.Packages {
+ pkgDir := tspath.CombinePaths(packagesDir, pkg.Name)
+ if record, ok := builder.projects[pkgDir]; ok {
+ packageHandles = append(packageHandles, record.toHandles())
+ }
+ }
+
+ session, sessionUtils := projecttestutil.Setup(builder.Files())
+ t.Cleanup(session.Close)
+
+ // Build root node_modules handle by looking at the project record for the workspace root
+ // (created as side effect of AddNodeModulesPackages)
+ var rootNodeModulesHandles []NodeModulesPackageHandle
+ if rootRecord, ok := builder.projects[monorepoRoot]; ok {
+ rootNodeModulesHandles = rootRecord.nodeModules
+ }
+
+ return &MonorepoFixture{
+ session: session,
+ utils: sessionUtils,
+ monorepo: MonorepoHandle{
+ root: monorepoRoot,
+ rootNodeModules: rootNodeModulesHandles,
+ rootDependencies: rootDependencyNames,
+ packages: packageHandles,
+ rootTSConfig: rootTSConfig,
+ rootPackageJSON: rootPackageJSON,
+ },
+ extra: extraHandles,
+ }
+}
+
+// SetupLifecycleSession builds a basic single-project workspace configured with the
+// requested number of TypeScript files and a single synthetic node_modules package.
+func SetupLifecycleSession(t *testing.T, projectRoot string, fileCount int) *Fixture {
+ t.Helper()
+ builder := newFileMapBuilder(nil)
+ builder.AddLocalProject(projectRoot, fileCount)
+ nodeModulesDir := tspath.CombinePaths(projectRoot, "node_modules")
+ deps := builder.AddNodeModulesPackages(nodeModulesDir, 1)
+ builder.AddPackageJSONWithDependencies(projectRoot, deps)
+ session, sessionUtils := projecttestutil.Setup(builder.Files())
+ t.Cleanup(session.Close)
+ return &Fixture{
+ session: session,
+ utils: sessionUtils,
+ projects: builder.projectHandles(),
+ }
+}
+
+type fileMapBuilder struct {
+ files map[string]any
+ nextPackageID int
+ nextProjectID int
+ projects map[string]*projectRecord
+}
+
+type projectRecord struct {
+ root string
+ sourceFiles []projectFile
+ tsconfig FileHandle
+ packageJSON *FileHandle
+ nodeModules []NodeModulesPackageHandle
+ dependencies []string
+}
+
+type projectFile struct {
+ FileName string
+ ExportIdentifier string
+ Content string
+}
+
+func newFileMapBuilder(initial map[string]any) *fileMapBuilder {
+ b := &fileMapBuilder{
+ files: make(map[string]any),
+ projects: make(map[string]*projectRecord),
+ }
+ if len(initial) == 0 {
+ return b
+ }
+ for path, content := range initial {
+ b.files[normalizeAbsolutePath(path)] = content
+ }
+ return b
+}
+
+func (b *fileMapBuilder) ensureProjectRecord(root string) *projectRecord {
+ if record, ok := b.projects[root]; ok {
+ return record
+ }
+ record := &projectRecord{root: root}
+ b.projects[root] = record
+ return record
+}
+
+func (b *fileMapBuilder) projectHandles() []ProjectHandle {
+ keys := slices.Collect(maps.Keys(b.projects))
+ slices.Sort(keys)
+ result := make([]ProjectHandle, 0, len(keys))
+ for _, key := range keys {
+ result = append(result, b.projects[key].toHandles())
+ }
+ return result
+}
+
+func (r *projectRecord) toHandles() ProjectHandle {
+ files := make([]ProjectFileHandle, len(r.sourceFiles))
+ for i, file := range r.sourceFiles {
+ files[i] = ProjectFileHandle{
+ FileHandle: FileHandle{fileName: file.FileName, content: file.Content},
+ exportIdentifier: file.ExportIdentifier,
+ }
+ }
+ packageJSON := FileHandle{}
+ if r.packageJSON != nil {
+ packageJSON = *r.packageJSON
+ }
+ return ProjectHandle{
+ root: r.root,
+ files: files,
+ tsconfig: r.tsconfig,
+ packageJSON: packageJSON,
+ nodeModules: slices.Clone(r.nodeModules),
+ dependencies: slices.Clone(r.dependencies),
+ }
+}
+
+func (b *fileMapBuilder) Files() map[string]any {
+ return maps.Clone(b.files)
+}
+
+func (b *fileMapBuilder) AddTextFile(path string, contents string) {
+ b.ensureFiles()
+ b.files[normalizeAbsolutePath(path)] = contents
+}
+
+func (b *fileMapBuilder) AddNodeModulesPackages(nodeModulesDir string, count int) []NodeModulesPackageHandle {
+ packages := make([]NodeModulesPackageHandle, 0, count)
+ for range count {
+ packages = append(packages, b.AddNodeModulesPackage(nodeModulesDir))
+ }
+ return packages
+}
+
+func (b *fileMapBuilder) AddNodeModulesPackagesWithNames(nodeModulesDir string, names []string) []NodeModulesPackageHandle {
+ if len(names) == 0 {
+ return nil
+ }
+ packages := make([]NodeModulesPackageHandle, 0, len(names))
+ for _, name := range names {
+ packages = append(packages, b.AddNamedNodeModulesPackage(nodeModulesDir, name))
+ }
+ return packages
+}
+
+func (b *fileMapBuilder) AddNodeModulesPackage(nodeModulesDir string) NodeModulesPackageHandle {
+ return b.AddNamedNodeModulesPackage(nodeModulesDir, "")
+}
+
+func (b *fileMapBuilder) AddNamedNodeModulesPackage(nodeModulesDir string, name string) NodeModulesPackageHandle {
+ b.ensureFiles()
+ normalizedDir := normalizeAbsolutePath(nodeModulesDir)
+ if tspath.GetBaseFileName(normalizedDir) != "node_modules" {
+ panic("nodeModulesDir must point to a node_modules directory: " + nodeModulesDir)
+ }
+ b.nextPackageID++
+ resolvedName := name
+ if resolvedName == "" {
+ resolvedName = fmt.Sprintf("pkg%d", b.nextPackageID)
+ }
+ exportName := sanitizeIdentifier(resolvedName) + "_value"
+ pkgDir := tspath.CombinePaths(normalizedDir, resolvedName)
+ packageJSONPath := tspath.CombinePaths(pkgDir, "package.json")
+ packageJSONContent := fmt.Sprintf(`{"name":"%s","types":"index.d.ts"}`, resolvedName)
+ b.files[packageJSONPath] = packageJSONContent
+ declarationPath := tspath.CombinePaths(pkgDir, "index.d.ts")
+ declarationContent := fmt.Sprintf("export declare const %s: number;\n", exportName)
+ b.files[declarationPath] = declarationContent
+ packageHandle := NodeModulesPackageHandle{
+ Name: resolvedName,
+ Directory: pkgDir,
+ packageJSON: FileHandle{fileName: packageJSONPath, content: packageJSONContent},
+ declaration: FileHandle{fileName: declarationPath, content: declarationContent},
+ }
+ projectRoot := tspath.GetDirectoryPath(normalizedDir)
+ record := b.ensureProjectRecord(projectRoot)
+ record.nodeModules = append(record.nodeModules, packageHandle)
+ return packageHandle
+}
+
+func (b *fileMapBuilder) AddLocalProject(projectDir string, fileCount int) {
+ b.ensureFiles()
+ if fileCount < 0 {
+ panic("fileCount must be non-negative")
+ }
+ dir := normalizeAbsolutePath(projectDir)
+ record := b.ensureProjectRecord(dir)
+ b.nextProjectID++
+ tsConfigPath := tspath.CombinePaths(dir, "tsconfig.json")
+ tsConfigContent := "{\n \"compilerOptions\": {\n \"module\": \"esnext\",\n \"target\": \"esnext\",\n \"strict\": true,\n \"allowJs\": true,\n \"checkJs\": true\n }\n}\n"
+ b.files[tsConfigPath] = tsConfigContent
+ record.tsconfig = FileHandle{fileName: tsConfigPath, content: tsConfigContent}
+ for i := 1; i <= fileCount; i++ {
+ path := tspath.CombinePaths(dir, fmt.Sprintf("file%d.ts", i))
+ exportName := fmt.Sprintf("localExport%d_%d", b.nextProjectID, i)
+ content := fmt.Sprintf("export const %s = %d;\n", exportName, i)
+ b.files[path] = content
+ record.sourceFiles = append(record.sourceFiles, projectFile{FileName: path, ExportIdentifier: exportName, Content: content})
+ }
+}
+
+func (b *fileMapBuilder) AddPackageJSONWithDependencies(projectDir string, deps []NodeModulesPackageHandle) FileHandle {
+ b.nextProjectID++
+ return b.AddPackageJSONWithDependenciesNamed(projectDir, fmt.Sprintf("local-project-%d", b.nextProjectID), deps)
+}
+
+func (b *fileMapBuilder) AddPackageJSONWithDependenciesNamed(projectDir string, packageName string, deps []NodeModulesPackageHandle) FileHandle {
+ b.ensureFiles()
+ dir := normalizeAbsolutePath(projectDir)
+ packageJSONPath := tspath.CombinePaths(dir, "package.json")
+ dependencyLines := make([]string, 0, len(deps))
+ for _, dep := range deps {
+ dependencyLines = append(dependencyLines, fmt.Sprintf("\"%s\": \"*\"", dep.Name))
+ }
+ var builder strings.Builder
+ name := packageName
+ if name == "" {
+ b.nextProjectID++
+ name = fmt.Sprintf("local-project-%d", b.nextProjectID)
+ }
+ builder.WriteString(fmt.Sprintf("{\n \"name\": \"%s\"", name))
+ if len(dependencyLines) > 0 {
+ builder.WriteString(",\n \"dependencies\": {\n ")
+ builder.WriteString(strings.Join(dependencyLines, ",\n "))
+ builder.WriteString("\n }\n")
+ } else {
+ builder.WriteString("\n")
+ }
+ builder.WriteString("}\n")
+ content := builder.String()
+ b.files[packageJSONPath] = content
+ record := b.ensureProjectRecord(dir)
+ packageHandle := FileHandle{fileName: packageJSONPath, content: content}
+ record.packageJSON = &packageHandle
+ record.dependencies = packageNames(deps)
+ return packageHandle
+}
+
+// addRootPackageJSON creates a root package.json for a monorepo without creating a project record.
+// This is used to set up the root workspace config without treating it as a project.
+func (b *fileMapBuilder) addRootPackageJSON(rootDir string, packageName string, deps []NodeModulesPackageHandle) FileHandle {
+ b.ensureFiles()
+ dir := normalizeAbsolutePath(rootDir)
+ packageJSONPath := tspath.CombinePaths(dir, "package.json")
+ dependencyLines := make([]string, 0, len(deps))
+ for _, dep := range deps {
+ dependencyLines = append(dependencyLines, fmt.Sprintf("\"%s\": \"*\"", dep.Name))
+ }
+ var builder strings.Builder
+ pkgName := packageName
+ if pkgName == "" {
+ pkgName = "monorepo-root"
+ }
+ builder.WriteString(fmt.Sprintf("{\n \"name\": \"%s\",\n \"private\": true", pkgName))
+ if len(dependencyLines) > 0 {
+ builder.WriteString(",\n \"dependencies\": {\n ")
+ builder.WriteString(strings.Join(dependencyLines, ",\n "))
+ builder.WriteString("\n }\n")
+ } else {
+ builder.WriteString("\n")
+ }
+ builder.WriteString("}\n")
+ content := builder.String()
+ b.files[packageJSONPath] = content
+ return FileHandle{fileName: packageJSONPath, content: content}
+}
+
+func selectPackagesByName(available []NodeModulesPackageHandle, names []string) []NodeModulesPackageHandle {
+ if len(names) == 0 {
+ return slices.Clone(available)
+ }
+ result := make([]NodeModulesPackageHandle, 0, len(names))
+ for _, name := range names {
+ found := false
+ for _, candidate := range available {
+ if candidate.Name == name {
+ result = append(result, candidate)
+ found = true
+ break
+ }
+ }
+ if !found {
+ panic("dependency not found: " + name)
+ }
+ }
+ return result
+}
+
+func packageNames(deps []NodeModulesPackageHandle) []string {
+ if len(deps) == 0 {
+ return nil
+ }
+ names := make([]string, 0, len(deps))
+ for _, dep := range deps {
+ names = append(names, dep.Name)
+ }
+ return names
+}
+
+func sanitizeIdentifier(name string) string {
+ sanitized := strings.Map(func(r rune) rune {
+ if r >= 'a' && r <= 'z' {
+ return r
+ }
+ if r >= 'A' && r <= 'Z' {
+ return r
+ }
+ if r >= '0' && r <= '9' {
+ return r
+ }
+ if r == '_' || r == '-' {
+ return '_'
+ }
+ return -1
+ }, name)
+ if sanitized == "" {
+ return "pkg"
+ }
+ return sanitized
+}
+
+func (b *fileMapBuilder) ensureFiles() {
+ if b.files == nil {
+ b.files = make(map[string]any)
+ }
+}
+
+func normalizeAbsolutePath(path string) string {
+ normalized := tspath.NormalizePath(path)
+ if !tspath.PathIsAbsolute(normalized) {
+ panic("paths used in lifecycle tests must be absolute: " + path)
+ }
+ return normalized
+}
diff --git a/internal/testutil/projecttestutil/projecttestutil.go b/internal/testutil/projecttestutil/projecttestutil.go
index 476212b4e3..e5f6a37ddc 100644
--- a/internal/testutil/projecttestutil/projecttestutil.go
+++ b/internal/testutil/projecttestutil/projecttestutil.go
@@ -3,6 +3,7 @@ package projecttestutil
import (
"context"
"fmt"
+ "os"
"slices"
"strings"
"sync"
@@ -14,8 +15,10 @@ import (
"github.com/microsoft/typescript-go/internal/project"
"github.com/microsoft/typescript-go/internal/project/logging"
"github.com/microsoft/typescript-go/internal/testutil/baseline"
+ "github.com/microsoft/typescript-go/internal/tspath"
"github.com/microsoft/typescript-go/internal/vfs"
"github.com/microsoft/typescript-go/internal/vfs/iovfs"
+ "github.com/microsoft/typescript-go/internal/vfs/osvfs"
"github.com/microsoft/typescript-go/internal/vfs/vfstest"
)
@@ -35,12 +38,13 @@ type TypingsInstallerOptions struct {
}
type SessionUtils struct {
- fsFromFileMap iovfs.FsWithSys
- fs vfs.FS
- client *ClientMock
- npmExecutor *NpmExecutorMock
- tiOptions *TypingsInstallerOptions
- logger logging.LogCollector
+ currentDirectory string
+ fsFromFileMap iovfs.FsWithSys
+ fs vfs.FS
+ client *ClientMock
+ npmExecutor *NpmExecutorMock
+ tiOptions *TypingsInstallerOptions
+ logger logging.LogCollector
}
func (h *SessionUtils) FsFromFileMap() iovfs.FsWithSys {
@@ -105,6 +109,10 @@ func (h *SessionUtils) SetupNpmExecutorForTypingsInstaller() {
}
}
+func (h *SessionUtils) ToPath(fileName string) tspath.Path {
+ return tspath.ToPath(fileName, h.currentDirectory, h.fs.UseCaseSensitiveFileNames())
+}
+
func (h *SessionUtils) FS() vfs.FS {
return h.fs
}
@@ -189,6 +197,39 @@ func Setup(files map[string]any) (*project.Session, *SessionUtils) {
return SetupWithTypingsInstaller(files, &TypingsInstallerOptions{})
}
+func SetupWithRealFS() (*project.Session, *SessionUtils) {
+ fs := bundled.WrapFS(osvfs.FS())
+ clientMock := &ClientMock{}
+ npmExecutorMock := &NpmExecutorMock{}
+ wd, err := os.Getwd()
+ if err != nil {
+ panic(err)
+ }
+
+ sessionUtils := &SessionUtils{
+ currentDirectory: wd,
+ fs: fs,
+ client: clientMock,
+ npmExecutor: npmExecutorMock,
+ logger: logging.NewTestLogger(),
+ }
+
+ return project.NewSession(&project.SessionInit{
+ FS: fs,
+ Client: clientMock,
+ NpmExecutor: npmExecutorMock,
+ Logger: sessionUtils.logger,
+ Options: &project.SessionOptions{
+ CurrentDirectory: wd,
+ DefaultLibraryPath: bundled.LibPath(),
+ PositionEncoding: lsproto.PositionEncodingKindUTF8,
+ WatchEnabled: true,
+ LoggingEnabled: true,
+ PushDiagnosticsEnabled: true,
+ },
+ }), sessionUtils
+}
+
func SetupWithOptions(files map[string]any, options *project.SessionOptions) (*project.Session, *SessionUtils) {
return SetupWithOptionsAndTypingsInstaller(files, options, &TypingsInstallerOptions{})
}
@@ -214,12 +255,13 @@ func GetSessionInitOptions(files map[string]any, options *project.SessionOptions
clientMock := &ClientMock{}
npmExecutorMock := &NpmExecutorMock{}
sessionUtils := &SessionUtils{
- fsFromFileMap: fsFromFileMap.(iovfs.FsWithSys),
- fs: fs,
- client: clientMock,
- npmExecutor: npmExecutorMock,
- tiOptions: tiOptions,
- logger: logging.NewTestLogger(),
+ currentDirectory: "/",
+ fsFromFileMap: fsFromFileMap.(iovfs.FsWithSys),
+ fs: fs,
+ client: clientMock,
+ npmExecutor: npmExecutorMock,
+ tiOptions: tiOptions,
+ logger: logging.NewTestLogger(),
}
// Configure the npm executor mock to handle typings installation
diff --git a/internal/tspath/extension.go b/internal/tspath/extension.go
index 1b4422e791..8303f0657d 100644
--- a/internal/tspath/extension.go
+++ b/internal/tspath/extension.go
@@ -23,8 +23,8 @@ const (
)
var (
- supportedDeclarationExtensions = []string{ExtensionDts, ExtensionDcts, ExtensionDmts}
- supportedTSImplementationExtensions = []string{ExtensionTs, ExtensionTsx, ExtensionMts, ExtensionCts}
+ SupportedDeclarationExtensions = []string{ExtensionDts, ExtensionDcts, ExtensionDmts}
+ SupportedTSImplementationExtensions = []string{ExtensionTs, ExtensionTsx, ExtensionMts, ExtensionCts}
supportedTSExtensionsForExtractExtension = []string{ExtensionDts, ExtensionDcts, ExtensionDmts, ExtensionTs, ExtensionTsx, ExtensionMts, ExtensionCts}
AllSupportedExtensions = [][]string{{ExtensionTs, ExtensionTsx, ExtensionDts, ExtensionJs, ExtensionJsx}, {ExtensionCts, ExtensionDcts, ExtensionCjs}, {ExtensionMts, ExtensionDmts, ExtensionMjs}}
SupportedTSExtensions = [][]string{{ExtensionTs, ExtensionTsx, ExtensionDts}, {ExtensionCts, ExtensionDcts}, {ExtensionMts, ExtensionDmts}}
@@ -90,7 +90,7 @@ func HasTSFileExtension(path string) bool {
}
func HasImplementationTSFileExtension(path string) bool {
- return FileExtensionIsOneOf(path, supportedTSImplementationExtensions) && !IsDeclarationFileName(path)
+ return FileExtensionIsOneOf(path, SupportedTSImplementationExtensions) && !IsDeclarationFileName(path)
}
func HasJSFileExtension(path string) bool {
@@ -111,7 +111,7 @@ func ExtensionIsOneOf(ext string, extensions []string) bool {
func GetDeclarationFileExtension(fileName string) string {
base := GetBaseFileName(fileName)
- for _, ext := range supportedDeclarationExtensions {
+ for _, ext := range SupportedDeclarationExtensions {
if strings.HasSuffix(base, ext) {
return ext
}
@@ -138,26 +138,28 @@ func GetDeclarationEmitExtensionForPath(path string) string {
}
}
-// changeAnyExtension changes the extension of a path to the provided extension if it has one of the provided extensions.
+// ChangeAnyExtension changes the extension of a path to the provided extension if it has one of the provided extensions.
//
-// changeAnyExtension("/path/to/file.ext", ".js", ".ext") === "/path/to/file.js"
-// changeAnyExtension("/path/to/file.ext", ".js", ".ts") === "/path/to/file.ext"
-// changeAnyExtension("/path/to/file.ext", ".js", [".ext", ".ts"]) === "/path/to/file.js"
-func changeAnyExtension(path string, ext string, extensions []string, ignoreCase bool) string {
+// ChangeAnyExtension("/path/to/file.ext", ".js", ".ext") === "/path/to/file.js"
+// ChangeAnyExtension("/path/to/file.ext", ".js", ".ts") === "/path/to/file.ext"
+// ChangeAnyExtension("/path/to/file.ext", ".js", [".ext", ".ts"]) === "/path/to/file.js"
+func ChangeAnyExtension(path string, ext string, extensions []string, ignoreCase bool) string {
pathext := GetAnyExtensionFromPath(path, extensions, ignoreCase)
if pathext != "" {
result := path[:len(path)-len(pathext)]
+ if ext == "" {
+ return result
+ }
if strings.HasPrefix(ext, ".") {
return result + ext
- } else {
- return result + "." + ext
}
+ return result + "." + ext
}
return path
}
func ChangeExtension(path string, newExtension string) string {
- return changeAnyExtension(path, newExtension, extensionsToRemove /*ignoreCase*/, false)
+ return ChangeAnyExtension(path, newExtension, extensionsToRemove /*ignoreCase*/, false)
}
// Like `changeAnyExtension`, but declaration file extensions are recognized
diff --git a/testdata/baselines/reference/fourslash/autoImports/autoImportErrorMixedExportKinds.baseline.md b/testdata/baselines/reference/fourslash/autoImports/autoImportErrorMixedExportKinds.baseline.md
new file mode 100644
index 0000000000..071262ccc3
--- /dev/null
+++ b/testdata/baselines/reference/fourslash/autoImports/autoImportErrorMixedExportKinds.baseline.md
@@ -0,0 +1,12 @@
+// === Auto Imports ===
+```ts
+// @FileName: /b.ts
+foo/**/
+
+``````ts
+import { foo } from "./a";
+
+foo
+
+```
+
diff --git a/testdata/baselines/reference/fourslash/autoImports/autoImportModuleAugmentation.baseline.md b/testdata/baselines/reference/fourslash/autoImports/autoImportModuleAugmentation.baseline.md
new file mode 100644
index 0000000000..169ca5b2a7
--- /dev/null
+++ b/testdata/baselines/reference/fourslash/autoImports/autoImportModuleAugmentation.baseline.md
@@ -0,0 +1,12 @@
+// === Auto Imports ===
+```ts
+// @FileName: /c.ts
+Foo/**/
+
+``````ts
+import { Foo } from "./a";
+
+Foo
+
+```
+