vendor tsgo
This commit is contained in:
256
tools/tsgo/internal/ls/autoimport/view.go
Normal file
256
tools/tsgo/internal/ls/autoimport/view.go
Normal file
@@ -0,0 +1,256 @@
|
||||
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/lsp/lsproto"
|
||||
"github.com/microsoft/typescript-go/internal/module"
|
||||
"github.com/microsoft/typescript-go/internal/modulespecifiers"
|
||||
"github.com/microsoft/typescript-go/internal/scanner"
|
||||
"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 {
|
||||
searchFn := 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")
|
||||
}
|
||||
}
|
||||
|
||||
return v.search(searchFn)
|
||||
}
|
||||
|
||||
func (v *View) SearchByExportID(id ExportID) []*Export {
|
||||
search := func(bucket *RegistryBucket) []*Export {
|
||||
return core.Filter(bucket.Index.entries, func(e *Export) bool {
|
||||
return e.ExportID == id
|
||||
})
|
||||
}
|
||||
|
||||
return v.search(search)
|
||||
}
|
||||
|
||||
func (v *View) search(searchFn func(*RegistryBucket) []*Export) []*Export {
|
||||
var results []*Export
|
||||
|
||||
if bucket, ok := v.registry.projects[v.projectKey]; ok {
|
||||
exports := searchFn(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)
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the set of packages accessible to the importing file.
|
||||
// This includes packages from package.json dependencies (aggregated from ancestor directories)
|
||||
// plus packages that are directly imported by the project's program files.
|
||||
// If no package.json is found, allowedPackages remains nil and all packages are allowed.
|
||||
var allowedPackages *collections.Set[string]
|
||||
tspath.ForEachAncestorDirectoryPath(v.importingFile.Path().GetDirectoryPath(), func(dirPath tspath.Path) (result any, stop bool) {
|
||||
if dir, ok := v.registry.directories[dirPath]; ok {
|
||||
if pj := dir.packageJson; pj.Exists() && pj.Contents.Parseable {
|
||||
// Initialize to empty set if this is the first package.json we've seen
|
||||
if allowedPackages == nil {
|
||||
allowedPackages = &collections.Set[string]{}
|
||||
}
|
||||
addPackageJsonDependencies(pj.Contents, allowedPackages)
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
})
|
||||
// If we found at least one package.json, also include packages directly imported by the project
|
||||
if allowedPackages != nil {
|
||||
if bucket, ok := v.registry.projects[v.projectKey]; ok {
|
||||
allowedPackages = allowedPackages.UnionedWith(bucket.ResolvedPackageNames)
|
||||
}
|
||||
}
|
||||
|
||||
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 := searchFn(nodeModulesBucket)
|
||||
results = slices.Grow(results, len(exports))
|
||||
for _, e := range exports {
|
||||
// Exclude packages found in lower node_modules (shadowing)
|
||||
if excludePackages.Has(e.PackageName) {
|
||||
continue
|
||||
}
|
||||
// If allowedPackages is nil, no package.json was found, so include all packages.
|
||||
// Otherwise, only include packages that are dependencies or directly imported.
|
||||
if allowedPackages != nil && !allowedPackages.Has(e.PackageName) {
|
||||
continue
|
||||
}
|
||||
results = append(results, e)
|
||||
}
|
||||
|
||||
// As we go up the directory tree, exclude packages found in lower node_modules
|
||||
for pkgName := range nodeModulesBucket.PackageFiles {
|
||||
excludePackages.Add(pkgName)
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
})
|
||||
return results
|
||||
}
|
||||
|
||||
type FixAndExport struct {
|
||||
Fix *Fix
|
||||
Export *Export
|
||||
}
|
||||
|
||||
func (v *View) GetCompletions(ctx context.Context, prefix string, position lsproto.Position, 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 !scanner.IsIdentifierText(name, core.LanguageVariantStandard) {
|
||||
continue
|
||||
}
|
||||
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,
|
||||
PackageName: e.PackageName,
|
||||
IsTypeOnly: e.IsTypeOnly || ex.IsTypeOnly,
|
||||
Syntax: min(e.Syntax, ex.Syntax),
|
||||
Flags: e.Flags | ex.Flags,
|
||||
ScriptElementKind: min(e.ScriptElementKind, ex.ScriptElementKind),
|
||||
ScriptElementKindModifiers: e.ScriptElementKindModifiers | ex.ScriptElementKindModifiers,
|
||||
localName: e.localName,
|
||||
Target: e.Target,
|
||||
Path: e.Path,
|
||||
})
|
||||
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, &position) {
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user