remove unused packages
This commit is contained in:
parent
5742ff6b75
commit
0cea2e734b
@ -1,44 +0,0 @@
|
|||||||
package ls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/astnav"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/checker"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrNoSourceFile = errors.New("source file not found")
|
|
||||||
ErrNoTokenAtPosition = errors.New("no token found at position")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (l *LanguageService) GetSymbolAtPosition(ctx context.Context, fileName string, position int) (*ast.Symbol, error) {
|
|
||||||
program, file := l.tryGetProgramAndFile(fileName)
|
|
||||||
if file == nil {
|
|
||||||
return nil, fmt.Errorf("%w: %s", ErrNoSourceFile, fileName)
|
|
||||||
}
|
|
||||||
node := astnav.GetTokenAtPosition(file, position)
|
|
||||||
if node == nil {
|
|
||||||
return nil, fmt.Errorf("%w: %s:%d", ErrNoTokenAtPosition, fileName, position)
|
|
||||||
}
|
|
||||||
checker, done := program.GetTypeCheckerForFile(ctx, file)
|
|
||||||
defer done()
|
|
||||||
return checker.GetSymbolAtLocation(node), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) GetSymbolAtLocation(ctx context.Context, node *ast.Node) *ast.Symbol {
|
|
||||||
program := l.GetProgram()
|
|
||||||
checker, done := program.GetTypeCheckerForFile(ctx, ast.GetSourceFileOfNode(node))
|
|
||||||
defer done()
|
|
||||||
return checker.GetSymbolAtLocation(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) GetTypeOfSymbol(ctx context.Context, symbol *ast.Symbol) *checker.Type {
|
|
||||||
program := l.GetProgram()
|
|
||||||
checker, done := program.GetTypeChecker(ctx)
|
|
||||||
defer done()
|
|
||||||
return checker.GetTypeOfSymbolAtLocation(symbol, nil)
|
|
||||||
}
|
|
||||||
@ -1,27 +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 {
|
|
||||||
if i < 0 || i >= ExportKind(len(_ExportKind_index)-1) {
|
|
||||||
return "ExportKind(" + strconv.FormatInt(int64(i), 10) + ")"
|
|
||||||
}
|
|
||||||
return _ExportKind_name[_ExportKind_index[i]:_ExportKind_index[i+1]]
|
|
||||||
}
|
|
||||||
@ -1,325 +0,0 @@
|
|||||||
package ls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/astnav"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/debug"
|
|
||||||
)
|
|
||||||
|
|
||||||
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 (ct *changeTracker) addNamespaceQualifier(sourceFile *ast.SourceFile, qualification *Qualification) {
|
|
||||||
ct.insertText(sourceFile, qualification.usagePosition, qualification.namespacePrefix+".")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ct *changeTracker) doAddExistingFix(
|
|
||||||
sourceFile *ast.SourceFile,
|
|
||||||
clause *ast.Node, // ImportClause | ObjectBindingPattern,
|
|
||||||
defaultImport *Import,
|
|
||||||
namedImports []*Import,
|
|
||||||
// removeExistingImportSpecifiers *core.Set[ImportSpecifier | BindingElement] // !!! remove imports not implemented
|
|
||||||
preferences *UserPreferences,
|
|
||||||
) {
|
|
||||||
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 {
|
|
||||||
ct.addElementToBindingPattern(sourceFile, clause, defaultImport.name, ptrTo("default"))
|
|
||||||
}
|
|
||||||
for _, specifier := range namedImports {
|
|
||||||
ct.addElementToBindingPattern(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), changeNodeOptions{suffix: ", "})
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(namedImports) > 0 {
|
|
||||||
// !!! OrganizeImports not yet implemented
|
|
||||||
// specifierComparer, isSorted := OrganizeImports.getNamedImportSpecifierComparerWithDetection(importClause.Parent, preferences, sourceFile);
|
|
||||||
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, preferences),
|
|
||||||
identifier,
|
|
||||||
ct.NodeFactory.NewIdentifier(namedImport.name),
|
|
||||||
)
|
|
||||||
}) // !!! sort with 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
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// } else if (len(existingSpecifiers) > 0 && isSorted != false) {
|
|
||||||
// !!! OrganizeImports not implemented
|
|
||||||
// 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.
|
|
||||||
// changed to check if existing specifiers are sorted
|
|
||||||
// if we're promoting the clause from type-only, we need to transform the existing imports before attempting to insert the new named imports
|
|
||||||
// transformedExistingSpecifiers := existingSpecifiers
|
|
||||||
// if promoteFromTypeOnly && existingSpecifiers {
|
|
||||||
// transformedExistingSpecifiers = ct.NodeFactory.updateNamedImports(
|
|
||||||
// importClause.NamedBindings.AsNamedImports(),
|
|
||||||
// core.SameMap(existingSpecifiers, func(e *ast.ImportSpecifier) *ast.ImportSpecifier {
|
|
||||||
// return ct.NodeFactory.updateImportSpecifier(e, /*isTypeOnly*/ true, e.propertyName, e.name)
|
|
||||||
// }),
|
|
||||||
// ).elements
|
|
||||||
// }
|
|
||||||
// for _, spec := range newSpecifiers {
|
|
||||||
// insertionIndex := OrganizeImports.getImportSpecifierInsertionIndex(transformedExistingSpecifiers, spec, specifierComparer);
|
|
||||||
// ct.insertImportSpecifierAtIndex(sourceFile, spec, importClause.namedBindings as NamedImports, insertionIndex);
|
|
||||||
// }
|
|
||||||
// } else
|
|
||||||
if len(existingSpecifiers) > 0 {
|
|
||||||
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 {
|
|
||||||
// !!! promote type-only imports not implemented
|
|
||||||
|
|
||||||
// ct.delete(sourceFile, getTypeKeywordOfTypeOnlyImport(clause, sourceFile));
|
|
||||||
// if (existingSpecifiers) {
|
|
||||||
// // We used to convert existing specifiers to type-only only if compiler options indicated that
|
|
||||||
// // would be meaningful (see the `importNameElisionDisabled` utility function), but user
|
|
||||||
// // feedback indicated a preference for preserving the type-onlyness of existing specifiers
|
|
||||||
// // regardless of whether it would make a difference in emit.
|
|
||||||
// for _, specifier := range existingSpecifiers {
|
|
||||||
// ct.insertModifierBefore(sourceFile, SyntaxKind.TypeKeyword, specifier);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
panic("Unsupported clause kind: " + clause.Kind.String() + "for doAddExistingFix")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ct *changeTracker) addElementToBindingPattern(sourceFile *ast.SourceFile, bindingPattern *ast.Node, name string, propertyName *string) {
|
|
||||||
element := ct.newBindingElementFromNameAndPropertyName(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 (ct *changeTracker) newBindingElementFromNameAndPropertyName(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 (ct *changeTracker) insertImports(sourceFile *ast.SourceFile, imports []*ast.Statement, blankLineBetween bool, preferences *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)
|
|
||||||
}
|
|
||||||
// !!! OrganizeImports
|
|
||||||
// { comparer, isSorted } := OrganizeImports.getOrganizeImportsStringComparerWithDetection(existingImportStatements, preferences);
|
|
||||||
// sortedNewImports := isArray(imports) ? toSorted(imports, (a, b) => OrganizeImports.compareImportsOrRequireStatements(a, b, comparer)) : [imports];
|
|
||||||
sortedNewImports := imports
|
|
||||||
// !!! 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 {
|
|
||||||
// for _, newImport := range sortedNewImports {
|
|
||||||
// insertionIndex := OrganizeImports.getImportDeclarationInsertionIndex(existingImportStatements, newImport, comparer)
|
|
||||||
// if insertionIndex == 0 {
|
|
||||||
// // If the first import is top-of-file, insert after the leading comment which is likely the header.
|
|
||||||
// options := existingImportStatements[0] == sourceFile.statements[0] ? { leadingTriviaOption: textchanges.LeadingTriviaOption.Exclude } : {};
|
|
||||||
// ct.insertNodeBefore(sourceFile, existingImportStatements[0], newImport, /*blankLineBetween*/ false, options);
|
|
||||||
// } else {
|
|
||||||
// prevImport := existingImportStatements[insertionIndex - 1]
|
|
||||||
// ct.insertNodeAfter(sourceFile, prevImport, newImport);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
if len(existingImportStatements) > 0 {
|
|
||||||
ct.insertNodesAfter(sourceFile, existingImportStatements[len(existingImportStatements)-1], sortedNewImports)
|
|
||||||
} else {
|
|
||||||
ct.insertAtTopOfFile(sourceFile, sortedNewImports, blankLineBetween)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ct *changeTracker) makeImport(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 (ct *changeTracker) getNewImports(
|
|
||||||
moduleSpecifier string,
|
|
||||||
// quotePreference quotePreference, // !!! quotePreference
|
|
||||||
defaultImport *Import,
|
|
||||||
namedImports []*Import,
|
|
||||||
namespaceLikeImport *Import, // { importKind: ImportKind.CommonJS | ImportKind.Namespace; }
|
|
||||||
compilerOptions *core.CompilerOptions,
|
|
||||||
preferences *UserPreferences,
|
|
||||||
) []*ast.Statement {
|
|
||||||
moduleSpecifierStringLiteral := ct.NodeFactory.NewStringLiteral(moduleSpecifier)
|
|
||||||
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() || preferences.PreferTypeOnlyAutoImports) &&
|
|
||||||
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, ct.makeImport(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, preferences),
|
|
||||||
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, 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 needsTypeOnly(addAsTypeOnly AddAsTypeOnly) bool {
|
|
||||||
return addAsTypeOnly == AddAsTypeOnlyRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
func shouldUseTypeOnly(addAsTypeOnly AddAsTypeOnly, preferences *UserPreferences) bool {
|
|
||||||
return needsTypeOnly(addAsTypeOnly) || addAsTypeOnly != AddAsTypeOnlyNotAllowed && preferences.PreferTypeOnlyAutoImports
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,181 +0,0 @@
|
|||||||
package ls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/checker"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/scanner"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (l *LanguageService) getExportInfos(
|
|
||||||
ctx context.Context,
|
|
||||||
ch *checker.Checker,
|
|
||||||
importingFile *ast.SourceFile,
|
|
||||||
preferences *UserPreferences,
|
|
||||||
exportMapKey ExportInfoMapKey,
|
|
||||||
) []*SymbolExportInfo {
|
|
||||||
expInfoMap := NewExportInfoMap(l.GetProgram().GetGlobalTypingsCacheLocation())
|
|
||||||
moduleCount := 0
|
|
||||||
symbolNameMatch := func(symbolName string) bool {
|
|
||||||
return symbolName == exportMapKey.SymbolName
|
|
||||||
}
|
|
||||||
forEachExternalModuleToImportFrom(
|
|
||||||
ch,
|
|
||||||
l.GetProgram(),
|
|
||||||
preferences,
|
|
||||||
// /*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 && 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,
|
|
||||||
preferences *UserPreferences,
|
|
||||||
isForImportStatementCompletion bool,
|
|
||||||
isRightOfOpenTag bool,
|
|
||||||
isTypeOnlyLocation bool,
|
|
||||||
lowerCaseTokenText string,
|
|
||||||
action func([]*SymbolExportInfo, string, bool, 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(),
|
|
||||||
preferences,
|
|
||||||
// /*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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,215 +0,0 @@
|
|||||||
package ls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/checker"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,336 +0,0 @@
|
|||||||
package ls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"slices"
|
|
||||||
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/astnav"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/format"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/printer"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/scanner"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/stringutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
type changeNodeOptions struct {
|
|
||||||
// Text to be inserted before the new node
|
|
||||||
prefix string
|
|
||||||
|
|
||||||
// Text to be inserted after the new node
|
|
||||||
suffix string
|
|
||||||
|
|
||||||
// Text of inserted node will be formatted with this indentation, otherwise indentation will be inferred from the old node
|
|
||||||
indentation *int
|
|
||||||
|
|
||||||
// Text of inserted node will be formatted with this delta, otherwise delta will be inferred from the new node kind
|
|
||||||
delta *int
|
|
||||||
|
|
||||||
leadingTriviaOption
|
|
||||||
trailingTriviaOption
|
|
||||||
joiner string
|
|
||||||
}
|
|
||||||
|
|
||||||
type leadingTriviaOption int
|
|
||||||
|
|
||||||
const (
|
|
||||||
leadingTriviaOptionNone leadingTriviaOption = 0
|
|
||||||
leadingTriviaOptionExclude leadingTriviaOption = 1
|
|
||||||
leadingTriviaOptionIncludeAll leadingTriviaOption = 2
|
|
||||||
leadingTriviaOptionJSDoc leadingTriviaOption = 3
|
|
||||||
leadingTriviaOptionStartLine leadingTriviaOption = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
type trailingTriviaOption int
|
|
||||||
|
|
||||||
const (
|
|
||||||
trailingTriviaOptionNone trailingTriviaOption = 0
|
|
||||||
trailingTriviaOptionExclude trailingTriviaOption = 1
|
|
||||||
trailingTriviaOptionExcludeWhitespace trailingTriviaOption = 2
|
|
||||||
trailingTriviaOptionInclude trailingTriviaOption = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
type trackerEditKind int
|
|
||||||
|
|
||||||
const (
|
|
||||||
trackerEditKindText trackerEditKind = 1
|
|
||||||
trackerEditKindRemove trackerEditKind = 2
|
|
||||||
trackerEditKindReplaceWithSingleNode trackerEditKind = 3
|
|
||||||
trackerEditKindReplaceWithMultipleNodes trackerEditKind = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
type trackerEdit struct {
|
|
||||||
kind trackerEditKind
|
|
||||||
lsproto.Range
|
|
||||||
|
|
||||||
NewText string // kind == text
|
|
||||||
|
|
||||||
*ast.Node // single
|
|
||||||
nodes []*ast.Node // multiple
|
|
||||||
options changeNodeOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
type changeTracker struct {
|
|
||||||
// initialized with
|
|
||||||
formatSettings *format.FormatCodeSettings
|
|
||||||
newLine string
|
|
||||||
ls *LanguageService
|
|
||||||
ctx context.Context
|
|
||||||
*printer.EmitContext
|
|
||||||
|
|
||||||
*ast.NodeFactory
|
|
||||||
changes *collections.MultiMap[*ast.SourceFile, *trackerEdit]
|
|
||||||
|
|
||||||
// created during call to getChanges
|
|
||||||
writer *printer.ChangeTrackerWriter
|
|
||||||
// printer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ls *LanguageService) newChangeTracker(ctx context.Context) *changeTracker {
|
|
||||||
emitContext := printer.NewEmitContext()
|
|
||||||
newLine := ls.GetProgram().Options().NewLine.GetNewLineCharacter()
|
|
||||||
formatCodeSettings := format.GetDefaultFormatCodeSettings(newLine) // !!! format.GetFormatCodeSettingsFromContext(ctx),
|
|
||||||
ctx = format.WithFormatCodeSettings(ctx, formatCodeSettings, newLine)
|
|
||||||
return &changeTracker{
|
|
||||||
ls: ls,
|
|
||||||
EmitContext: emitContext,
|
|
||||||
NodeFactory: &emitContext.Factory.NodeFactory,
|
|
||||||
changes: &collections.MultiMap[*ast.SourceFile, *trackerEdit]{},
|
|
||||||
ctx: ctx,
|
|
||||||
formatSettings: formatCodeSettings,
|
|
||||||
newLine: newLine,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// !!! address strada note
|
|
||||||
// - Note: after calling this, the TextChanges object must be discarded!
|
|
||||||
func (ct *changeTracker) getChanges() map[string][]*lsproto.TextEdit {
|
|
||||||
// !!! finishDeleteDeclarations
|
|
||||||
// !!! finishClassesWithNodesInsertedAtStart
|
|
||||||
changes := ct.getTextChangesFromChanges()
|
|
||||||
// !!! changes for new files
|
|
||||||
return changes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ct *changeTracker) replaceNode(sourceFile *ast.SourceFile, oldNode *ast.Node, newNode *ast.Node, options *changeNodeOptions) {
|
|
||||||
if options == nil {
|
|
||||||
// defaults to `useNonAdjustedPositions`
|
|
||||||
options = &changeNodeOptions{
|
|
||||||
leadingTriviaOption: leadingTriviaOptionExclude,
|
|
||||||
trailingTriviaOption: trailingTriviaOptionExclude,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ct.replaceRange(sourceFile, ct.getAdjustedRange(sourceFile, oldNode, oldNode, options.leadingTriviaOption, options.trailingTriviaOption), newNode, *options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ct *changeTracker) replaceRange(sourceFile *ast.SourceFile, lsprotoRange lsproto.Range, newNode *ast.Node, options changeNodeOptions) {
|
|
||||||
ct.changes.Add(sourceFile, &trackerEdit{kind: trackerEditKindReplaceWithSingleNode, Range: lsprotoRange, options: options, Node: newNode})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ct *changeTracker) replaceRangeWithText(sourceFile *ast.SourceFile, lsprotoRange lsproto.Range, text string) {
|
|
||||||
ct.changes.Add(sourceFile, &trackerEdit{kind: trackerEditKindText, Range: lsprotoRange, NewText: text})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ct *changeTracker) replaceRangeWithNodes(sourceFile *ast.SourceFile, lsprotoRange lsproto.Range, newNodes []*ast.Node, options changeNodeOptions) {
|
|
||||||
if len(newNodes) == 1 {
|
|
||||||
ct.replaceRange(sourceFile, lsprotoRange, newNodes[0], options)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ct.changes.Add(sourceFile, &trackerEdit{kind: trackerEditKindReplaceWithMultipleNodes, Range: lsprotoRange, nodes: newNodes, options: options})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ct *changeTracker) insertText(sourceFile *ast.SourceFile, pos lsproto.Position, text string) {
|
|
||||||
ct.replaceRangeWithText(sourceFile, lsproto.Range{Start: pos, End: pos}, text)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ct *changeTracker) insertNodeAt(sourceFile *ast.SourceFile, pos core.TextPos, newNode *ast.Node, options changeNodeOptions) {
|
|
||||||
lsPos := ct.ls.converters.PositionToLineAndCharacter(sourceFile, pos)
|
|
||||||
ct.replaceRange(sourceFile, lsproto.Range{Start: lsPos, End: lsPos}, newNode, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ct *changeTracker) insertNodesAt(sourceFile *ast.SourceFile, pos core.TextPos, newNodes []*ast.Node, options changeNodeOptions) {
|
|
||||||
lsPos := ct.ls.converters.PositionToLineAndCharacter(sourceFile, pos)
|
|
||||||
ct.replaceRangeWithNodes(sourceFile, lsproto.Range{Start: lsPos, End: lsPos}, newNodes, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ct *changeTracker) insertNodeAfter(sourceFile *ast.SourceFile, after *ast.Node, newNode *ast.Node) {
|
|
||||||
endPosition := ct.endPosForInsertNodeAfter(sourceFile, after, newNode)
|
|
||||||
ct.insertNodeAt(sourceFile, endPosition, newNode, ct.getInsertNodeAfterOptions(sourceFile, after))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ct *changeTracker) insertNodesAfter(sourceFile *ast.SourceFile, after *ast.Node, newNodes []*ast.Node) {
|
|
||||||
endPosition := ct.endPosForInsertNodeAfter(sourceFile, after, newNodes[0])
|
|
||||||
ct.insertNodesAt(sourceFile, endPosition, newNodes, ct.getInsertNodeAfterOptions(sourceFile, after))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ct *changeTracker) endPosForInsertNodeAfter(sourceFile *ast.SourceFile, after *ast.Node, newNode *ast.Node) core.TextPos {
|
|
||||||
if (needSemicolonBetween(after, newNode)) && (rune(sourceFile.Text()[after.End()-1]) != ';') {
|
|
||||||
// check if previous statement ends with semicolon
|
|
||||||
// if not - insert semicolon to preserve the code from changing the meaning due to ASI
|
|
||||||
endPos := ct.ls.converters.PositionToLineAndCharacter(sourceFile, core.TextPos(after.End()))
|
|
||||||
ct.replaceRange(sourceFile,
|
|
||||||
lsproto.Range{Start: endPos, End: endPos},
|
|
||||||
sourceFile.GetOrCreateToken(ast.KindSemicolonToken, after.End(), after.End(), after.Parent),
|
|
||||||
changeNodeOptions{},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return core.TextPos(ct.getAdjustedEndPosition(sourceFile, after, trailingTriviaOptionNone))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function should be used to insert nodes in lists when nodes don't carry separators as the part of the node range,
|
|
||||||
* i.e. arguments in arguments lists, parameters in parameter lists etc.
|
|
||||||
* Note that separators are part of the node in statements and class elements.
|
|
||||||
*/
|
|
||||||
func (ct *changeTracker) insertNodeInListAfter(sourceFile *ast.SourceFile, after *ast.Node, newNode *ast.Node, containingList []*ast.Node) {
|
|
||||||
if len(containingList) == 0 {
|
|
||||||
containingList = format.GetContainingList(after, sourceFile).Nodes
|
|
||||||
}
|
|
||||||
index := slices.Index(containingList, after)
|
|
||||||
if index < 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if index != len(containingList)-1 {
|
|
||||||
// any element except the last one
|
|
||||||
// use next sibling as an anchor
|
|
||||||
if nextToken := astnav.GetTokenAtPosition(sourceFile, after.End()); nextToken != nil && isSeparator(after, nextToken) {
|
|
||||||
// for list
|
|
||||||
// a, b, c
|
|
||||||
// create change for adding 'e' after 'a' as
|
|
||||||
// - find start of next element after a (it is b)
|
|
||||||
// - use next element start as start and end position in final change
|
|
||||||
// - build text of change by formatting the text of node + whitespace trivia of b
|
|
||||||
|
|
||||||
// in multiline case it will work as
|
|
||||||
// a,
|
|
||||||
// b,
|
|
||||||
// c,
|
|
||||||
// result - '*' denotes leading trivia that will be inserted after new text (displayed as '#')
|
|
||||||
// a,
|
|
||||||
// insertedtext<separator>#
|
|
||||||
// ###b,
|
|
||||||
// c,
|
|
||||||
nextNode := containingList[index+1]
|
|
||||||
startPos := scanner.SkipTriviaEx(sourceFile.Text(), nextNode.Pos(), &scanner.SkipTriviaOptions{StopAfterLineBreak: true, StopAtComments: false})
|
|
||||||
|
|
||||||
// write separator and leading trivia of the next element as suffix
|
|
||||||
suffix := scanner.TokenToString(nextToken.Kind) + sourceFile.Text()[nextNode.End():startPos]
|
|
||||||
ct.insertNodeAt(sourceFile, core.TextPos(startPos), newNode, changeNodeOptions{suffix: suffix})
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
afterStart := astnav.GetStartOfNode(after, sourceFile, false)
|
|
||||||
afterStartLinePosition := format.GetLineStartPositionForPosition(afterStart, sourceFile)
|
|
||||||
|
|
||||||
// insert element after the last element in the list that has more than one item
|
|
||||||
// pick the element preceding the after element to:
|
|
||||||
// - pick the separator
|
|
||||||
// - determine if list is a multiline
|
|
||||||
multilineList := false
|
|
||||||
|
|
||||||
// if list has only one element then we'll format is as multiline if node has comment in trailing trivia, or as singleline otherwise
|
|
||||||
// i.e. var x = 1 // this is x
|
|
||||||
// | new element will be inserted at this position
|
|
||||||
separator := ast.KindCommaToken // SyntaxKind.CommaToken | SyntaxKind.SemicolonToken
|
|
||||||
if len(containingList) != 1 {
|
|
||||||
// otherwise, if list has more than one element, pick separator from the list
|
|
||||||
tokenBeforeInsertPosition := astnav.FindPrecedingToken(sourceFile, after.Pos())
|
|
||||||
separator = core.IfElse(isSeparator(after, tokenBeforeInsertPosition), tokenBeforeInsertPosition.Kind, ast.KindCommaToken)
|
|
||||||
// determine if list is multiline by checking lines of after element and element that precedes it.
|
|
||||||
afterMinusOneStartLinePosition := format.GetLineStartPositionForPosition(astnav.GetStartOfNode(containingList[index-1], sourceFile, false), sourceFile)
|
|
||||||
multilineList = afterMinusOneStartLinePosition != afterStartLinePosition
|
|
||||||
}
|
|
||||||
if hasCommentsBeforeLineBreak(sourceFile.Text(), after.End()) || printer.GetLinesBetweenPositions(sourceFile, containingList[0].Pos(), containingList[len(containingList)-1].End()) != 0 {
|
|
||||||
// in this case we'll always treat containing list as multiline
|
|
||||||
multilineList = true
|
|
||||||
}
|
|
||||||
|
|
||||||
separatorString := scanner.TokenToString(separator)
|
|
||||||
end := ct.ls.converters.PositionToLineAndCharacter(sourceFile, core.TextPos(after.End()))
|
|
||||||
if !multilineList {
|
|
||||||
ct.replaceRange(sourceFile, lsproto.Range{Start: end, End: end}, newNode, changeNodeOptions{prefix: separatorString})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert separator immediately following the 'after' node to preserve comments in trailing trivia
|
|
||||||
// !!! formatcontext
|
|
||||||
ct.replaceRange(sourceFile, lsproto.Range{Start: end, End: end}, sourceFile.GetOrCreateToken(separator, after.End(), after.End()+len(separatorString), after.Parent), changeNodeOptions{})
|
|
||||||
// use the same indentation as 'after' item
|
|
||||||
indentation := format.FindFirstNonWhitespaceColumn(afterStartLinePosition, afterStart, sourceFile, ct.formatSettings)
|
|
||||||
// insert element before the line break on the line that contains 'after' element
|
|
||||||
insertPos := scanner.SkipTriviaEx(sourceFile.Text(), after.End(), &scanner.SkipTriviaOptions{StopAfterLineBreak: true, StopAtComments: false})
|
|
||||||
// find position before "\n" or "\r\n"
|
|
||||||
for insertPos != after.End() && stringutil.IsLineBreak(rune(sourceFile.Text()[insertPos-1])) {
|
|
||||||
insertPos--
|
|
||||||
}
|
|
||||||
insertLSPos := ct.ls.converters.PositionToLineAndCharacter(sourceFile, core.TextPos(insertPos))
|
|
||||||
ct.replaceRange(
|
|
||||||
sourceFile,
|
|
||||||
lsproto.Range{Start: insertLSPos, End: insertLSPos},
|
|
||||||
newNode,
|
|
||||||
changeNodeOptions{
|
|
||||||
indentation: ptrTo(indentation),
|
|
||||||
prefix: ct.newLine,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ct *changeTracker) insertAtTopOfFile(sourceFile *ast.SourceFile, insert []*ast.Statement, blankLineBetween bool) {
|
|
||||||
if len(insert) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pos := ct.getInsertionPositionAtSourceFileTop(sourceFile)
|
|
||||||
options := changeNodeOptions{}
|
|
||||||
if pos != 0 {
|
|
||||||
options.prefix = ct.newLine
|
|
||||||
}
|
|
||||||
if !stringutil.IsLineBreak(rune(sourceFile.Text()[pos])) {
|
|
||||||
options.suffix = ct.newLine
|
|
||||||
}
|
|
||||||
if blankLineBetween {
|
|
||||||
options.suffix += ct.newLine
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(insert) == 1 {
|
|
||||||
ct.insertNodeAt(sourceFile, core.TextPos(pos), insert[0], options)
|
|
||||||
} else {
|
|
||||||
ct.insertNodesAt(sourceFile, core.TextPos(pos), insert, options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ct *changeTracker) getInsertNodeAfterOptions(sourceFile *ast.SourceFile, node *ast.Node) changeNodeOptions {
|
|
||||||
newLineChar := ct.newLine
|
|
||||||
var options changeNodeOptions
|
|
||||||
switch node.Kind {
|
|
||||||
case ast.KindParameter:
|
|
||||||
// default opts
|
|
||||||
options = changeNodeOptions{}
|
|
||||||
case ast.KindClassDeclaration, ast.KindModuleDeclaration:
|
|
||||||
options = changeNodeOptions{prefix: newLineChar, suffix: newLineChar}
|
|
||||||
|
|
||||||
case ast.KindVariableDeclaration, ast.KindStringLiteral, ast.KindIdentifier:
|
|
||||||
options = changeNodeOptions{prefix: ", "}
|
|
||||||
|
|
||||||
case ast.KindPropertyAssignment:
|
|
||||||
options = changeNodeOptions{suffix: "," + newLineChar}
|
|
||||||
|
|
||||||
case ast.KindExportKeyword:
|
|
||||||
options = changeNodeOptions{prefix: " "}
|
|
||||||
|
|
||||||
default:
|
|
||||||
if !(ast.IsStatement(node) || ast.IsClassOrTypeElement(node)) {
|
|
||||||
// Else we haven't handled this kind of node yet -- add it
|
|
||||||
panic("unimplemented node type " + node.Kind.String() + " in changeTracker.getInsertNodeAfterOptions")
|
|
||||||
}
|
|
||||||
options = changeNodeOptions{suffix: newLineChar}
|
|
||||||
}
|
|
||||||
if node.End() == sourceFile.End() && ast.IsStatement(node) {
|
|
||||||
options.prefix = "\n" + options.prefix
|
|
||||||
}
|
|
||||||
|
|
||||||
return options
|
|
||||||
}
|
|
||||||
@ -1,413 +0,0 @@
|
|||||||
package ls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/astnav"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/format"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/parser"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/printer"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/scanner"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/stringutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (ct *changeTracker) getTextChangesFromChanges() map[string][]*lsproto.TextEdit {
|
|
||||||
changes := map[string][]*lsproto.TextEdit{}
|
|
||||||
for sourceFile, changesInFile := range ct.changes.M {
|
|
||||||
// order changes by start position
|
|
||||||
// If the start position is the same, put the shorter range first, since an empty range (x, x) may precede (x, y) but not vice-versa.
|
|
||||||
slices.SortStableFunc(changesInFile, func(a, b *trackerEdit) int { return CompareRanges(ptrTo(a.Range), ptrTo(b.Range)) })
|
|
||||||
// verify that change intervals do not overlap, except possibly at end points.
|
|
||||||
for i := range len(changesInFile) - 1 {
|
|
||||||
if ComparePositions(changesInFile[i].Range.End, changesInFile[i+1].Range.Start) > 0 {
|
|
||||||
// assert change[i].End <= change[i + 1].Start
|
|
||||||
panic(fmt.Sprintf("changes overlap: %v and %v", changesInFile[i].Range, changesInFile[i+1].Range))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
textChanges := core.MapNonNil(changesInFile, func(change *trackerEdit) *lsproto.TextEdit {
|
|
||||||
// !!! targetSourceFile
|
|
||||||
|
|
||||||
newText := ct.computeNewText(change, sourceFile, sourceFile)
|
|
||||||
// span := createTextSpanFromRange(c.Range)
|
|
||||||
// !!!
|
|
||||||
// Filter out redundant changes.
|
|
||||||
// if (span.length == newText.length && stringContainsAt(targetSourceFile.text, newText, span.start)) { return nil }
|
|
||||||
|
|
||||||
return &lsproto.TextEdit{
|
|
||||||
NewText: newText,
|
|
||||||
Range: change.Range,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if len(textChanges) > 0 {
|
|
||||||
changes[sourceFile.FileName()] = textChanges
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return changes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ct *changeTracker) computeNewText(change *trackerEdit, targetSourceFile *ast.SourceFile, sourceFile *ast.SourceFile) string {
|
|
||||||
switch change.kind {
|
|
||||||
case trackerEditKindRemove:
|
|
||||||
return ""
|
|
||||||
case trackerEditKindText:
|
|
||||||
return change.NewText
|
|
||||||
}
|
|
||||||
|
|
||||||
pos := int(ct.ls.converters.LineAndCharacterToPosition(sourceFile, change.Range.Start))
|
|
||||||
formatNode := func(n *ast.Node) string {
|
|
||||||
return ct.getFormattedTextOfNode(n, targetSourceFile, sourceFile, pos, change.options)
|
|
||||||
}
|
|
||||||
|
|
||||||
var text string
|
|
||||||
switch change.kind {
|
|
||||||
|
|
||||||
case trackerEditKindReplaceWithMultipleNodes:
|
|
||||||
if change.options.joiner == "" {
|
|
||||||
change.options.joiner = ct.newLine
|
|
||||||
}
|
|
||||||
text = strings.Join(core.Map(change.nodes, func(n *ast.Node) string { return strings.TrimSuffix(formatNode(n), ct.newLine) }), change.options.joiner)
|
|
||||||
case trackerEditKindReplaceWithSingleNode:
|
|
||||||
text = formatNode(change.Node)
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("change kind %d should have been handled earlier", change.kind))
|
|
||||||
}
|
|
||||||
// strip initial indentation (spaces or tabs) if text will be inserted in the middle of the line
|
|
||||||
noIndent := text
|
|
||||||
if !(change.options.indentation != nil && *change.options.indentation != 0 || format.GetLineStartPositionForPosition(pos, targetSourceFile) == pos) {
|
|
||||||
noIndent = strings.TrimLeftFunc(text, unicode.IsSpace)
|
|
||||||
}
|
|
||||||
return change.options.prefix + noIndent // !!! +((!options.suffix || endsWith(noIndent, options.suffix)) ? "" : options.suffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Note: this may mutate `nodeIn`. */
|
|
||||||
func (ct *changeTracker) getFormattedTextOfNode(nodeIn *ast.Node, targetSourceFile *ast.SourceFile, sourceFile *ast.SourceFile, pos int, options changeNodeOptions) string {
|
|
||||||
text, sourceFileLike := ct.getNonformattedText(nodeIn, targetSourceFile)
|
|
||||||
// !!! if (validate) validate(node, text);
|
|
||||||
formatOptions := getFormatCodeSettingsForWriting(ct.formatSettings, targetSourceFile)
|
|
||||||
|
|
||||||
var initialIndentation, delta int
|
|
||||||
if options.indentation == nil {
|
|
||||||
// !!! indentation for position
|
|
||||||
// initialIndentation = format.GetIndentationForPos(pos, sourceFile, formatOptions, options.prefix == ct.newLine || scanner.GetLineStartPositionForPosition(pos, targetFileLineMap) == pos);
|
|
||||||
} else {
|
|
||||||
initialIndentation = *options.indentation
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.delta != nil {
|
|
||||||
delta = *options.delta
|
|
||||||
} else if formatOptions.IndentSize != 0 && format.ShouldIndentChildNode(formatOptions, nodeIn, nil, nil) {
|
|
||||||
delta = formatOptions.IndentSize
|
|
||||||
}
|
|
||||||
|
|
||||||
changes := format.FormatNodeGivenIndentation(ct.ctx, sourceFileLike, sourceFileLike.AsSourceFile(), targetSourceFile.LanguageVariant, initialIndentation, delta)
|
|
||||||
return core.ApplyBulkEdits(text, changes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFormatCodeSettingsForWriting(options *format.FormatCodeSettings, sourceFile *ast.SourceFile) *format.FormatCodeSettings {
|
|
||||||
shouldAutoDetectSemicolonPreference := options.Semicolons == format.SemicolonPreferenceIgnore
|
|
||||||
shouldRemoveSemicolons := options.Semicolons == format.SemicolonPreferenceRemove || shouldAutoDetectSemicolonPreference && !probablyUsesSemicolons(sourceFile)
|
|
||||||
if shouldRemoveSemicolons {
|
|
||||||
options.Semicolons = format.SemicolonPreferenceRemove
|
|
||||||
}
|
|
||||||
|
|
||||||
return options
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Note: output node may be mutated input node. */
|
|
||||||
func (ct *changeTracker) getNonformattedText(node *ast.Node, sourceFile *ast.SourceFile) (string, *ast.Node) {
|
|
||||||
nodeIn := node
|
|
||||||
eofToken := ct.Factory.NewToken(ast.KindEndOfFile)
|
|
||||||
if ast.IsStatement(node) {
|
|
||||||
nodeIn = ct.Factory.NewSourceFile(
|
|
||||||
ast.SourceFileParseOptions{FileName: sourceFile.FileName(), Path: sourceFile.Path()},
|
|
||||||
"",
|
|
||||||
ct.Factory.NewNodeList([]*ast.Node{node}),
|
|
||||||
ct.Factory.NewToken(ast.KindEndOfFile),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
writer := printer.NewChangeTrackerWriter(ct.newLine)
|
|
||||||
printer.NewPrinter(
|
|
||||||
printer.PrinterOptions{
|
|
||||||
NewLine: core.GetNewLineKind(ct.newLine),
|
|
||||||
NeverAsciiEscape: true,
|
|
||||||
PreserveSourceNewlines: true,
|
|
||||||
TerminateUnterminatedLiterals: true,
|
|
||||||
},
|
|
||||||
writer.GetPrintHandlers(),
|
|
||||||
ct.EmitContext,
|
|
||||||
).Write(nodeIn, sourceFile, writer, nil)
|
|
||||||
|
|
||||||
text := writer.String()
|
|
||||||
|
|
||||||
nodeOut := writer.AssignPositionsToNode(nodeIn, ct.NodeFactory)
|
|
||||||
var sourceFileLike *ast.Node
|
|
||||||
if !ast.IsStatement(node) {
|
|
||||||
nodeList := ct.Factory.NewNodeList([]*ast.Node{nodeOut})
|
|
||||||
nodeList.Loc = nodeOut.Loc
|
|
||||||
eofToken.Loc = core.NewTextRange(nodeOut.End(), nodeOut.End())
|
|
||||||
sourceFileLike = ct.Factory.NewSourceFile(
|
|
||||||
ast.SourceFileParseOptions{FileName: sourceFile.FileName(), Path: sourceFile.Path()},
|
|
||||||
text,
|
|
||||||
nodeList,
|
|
||||||
eofToken,
|
|
||||||
)
|
|
||||||
sourceFileLike.ForEachChild(func(child *ast.Node) bool {
|
|
||||||
child.Parent = sourceFileLike
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
sourceFileLike.Loc = nodeOut.Loc
|
|
||||||
} else {
|
|
||||||
sourceFileLike = nodeOut
|
|
||||||
}
|
|
||||||
return text, sourceFileLike
|
|
||||||
}
|
|
||||||
|
|
||||||
// method on the changeTracker because use of converters
|
|
||||||
func (ct *changeTracker) getAdjustedRange(sourceFile *ast.SourceFile, startNode *ast.Node, endNode *ast.Node, leadingOption leadingTriviaOption, trailingOption trailingTriviaOption) lsproto.Range {
|
|
||||||
return *ct.ls.createLspRangeFromBounds(
|
|
||||||
ct.getAdjustedStartPosition(sourceFile, startNode, leadingOption, false),
|
|
||||||
ct.getAdjustedEndPosition(sourceFile, endNode, trailingOption),
|
|
||||||
sourceFile,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// method on the changeTracker because use of converters
|
|
||||||
func (ct *changeTracker) getAdjustedStartPosition(sourceFile *ast.SourceFile, node *ast.Node, leadingOption leadingTriviaOption, hasTrailingComment bool) int {
|
|
||||||
if leadingOption == leadingTriviaOptionJSDoc {
|
|
||||||
if JSDocComments := parser.GetJSDocCommentRanges(ct.NodeFactory, nil, node, sourceFile.Text()); len(JSDocComments) > 0 {
|
|
||||||
return format.GetLineStartPositionForPosition(JSDocComments[0].Pos(), sourceFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
start := astnav.GetStartOfNode(node, sourceFile, false)
|
|
||||||
startOfLinePos := format.GetLineStartPositionForPosition(start, sourceFile)
|
|
||||||
|
|
||||||
switch leadingOption {
|
|
||||||
case leadingTriviaOptionExclude:
|
|
||||||
return start
|
|
||||||
case leadingTriviaOptionStartLine:
|
|
||||||
if node.Loc.ContainsInclusive(startOfLinePos) {
|
|
||||||
return startOfLinePos
|
|
||||||
}
|
|
||||||
return start
|
|
||||||
}
|
|
||||||
|
|
||||||
fullStart := node.Pos()
|
|
||||||
if fullStart == start {
|
|
||||||
return start
|
|
||||||
}
|
|
||||||
lineStarts := sourceFile.ECMALineMap()
|
|
||||||
fullStartLineIndex := scanner.ComputeLineOfPosition(lineStarts, fullStart)
|
|
||||||
fullStartLinePos := int(lineStarts[fullStartLineIndex])
|
|
||||||
if startOfLinePos == fullStartLinePos {
|
|
||||||
// full start and start of the node are on the same line
|
|
||||||
// a, b;
|
|
||||||
// ^ ^
|
|
||||||
// | start
|
|
||||||
// fullstart
|
|
||||||
// when b is replaced - we usually want to keep the leading trvia
|
|
||||||
// when b is deleted - we delete it
|
|
||||||
if leadingOption == leadingTriviaOptionIncludeAll {
|
|
||||||
return fullStart
|
|
||||||
}
|
|
||||||
return start
|
|
||||||
}
|
|
||||||
|
|
||||||
// if node has a trailing comments, use comment end position as the text has already been included.
|
|
||||||
if hasTrailingComment {
|
|
||||||
// Check first for leading comments as if the node is the first import, we want to exclude the trivia;
|
|
||||||
// otherwise we get the trailing comments.
|
|
||||||
comments := slices.Collect(scanner.GetLeadingCommentRanges(ct.NodeFactory, sourceFile.Text(), fullStart))
|
|
||||||
if len(comments) == 0 {
|
|
||||||
comments = slices.Collect(scanner.GetTrailingCommentRanges(ct.NodeFactory, sourceFile.Text(), fullStart))
|
|
||||||
}
|
|
||||||
if len(comments) > 0 {
|
|
||||||
return scanner.SkipTriviaEx(sourceFile.Text(), comments[0].End(), &scanner.SkipTriviaOptions{StopAfterLineBreak: true, StopAtComments: true})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get start position of the line following the line that contains fullstart position
|
|
||||||
// (but only if the fullstart isn't the very beginning of the file)
|
|
||||||
nextLineStart := core.IfElse(fullStart > 0, 1, 0)
|
|
||||||
adjustedStartPosition := int(lineStarts[fullStartLineIndex+nextLineStart])
|
|
||||||
// skip whitespaces/newlines
|
|
||||||
adjustedStartPosition = scanner.SkipTriviaEx(sourceFile.Text(), adjustedStartPosition, &scanner.SkipTriviaOptions{StopAtComments: true})
|
|
||||||
return int(lineStarts[scanner.ComputeLineOfPosition(lineStarts, adjustedStartPosition)])
|
|
||||||
}
|
|
||||||
|
|
||||||
// method on the changeTracker because of converters
|
|
||||||
// Return the end position of a multiline comment of it is on another line; otherwise returns `undefined`;
|
|
||||||
func (ct *changeTracker) getEndPositionOfMultilineTrailingComment(sourceFile *ast.SourceFile, node *ast.Node, trailingOpt trailingTriviaOption) int {
|
|
||||||
if trailingOpt == trailingTriviaOptionInclude {
|
|
||||||
// If the trailing comment is a multiline comment that extends to the next lines,
|
|
||||||
// return the end of the comment and track it for the next nodes to adjust.
|
|
||||||
lineStarts := sourceFile.ECMALineMap()
|
|
||||||
nodeEndLine := scanner.ComputeLineOfPosition(lineStarts, node.End())
|
|
||||||
for comment := range scanner.GetTrailingCommentRanges(ct.NodeFactory, sourceFile.Text(), node.End()) {
|
|
||||||
// Single line can break the loop as trivia will only be this line.
|
|
||||||
// Comments on subsequest lines are also ignored.
|
|
||||||
if comment.Kind == ast.KindSingleLineCommentTrivia || scanner.ComputeLineOfPosition(lineStarts, comment.Pos()) > nodeEndLine {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the end line of the comment and compare against the end line of the node.
|
|
||||||
// If the comment end line position and the multiline comment extends to multiple lines,
|
|
||||||
// then is safe to return the end position.
|
|
||||||
if commentEndLine := scanner.ComputeLineOfPosition(lineStarts, comment.End()); commentEndLine > nodeEndLine {
|
|
||||||
return scanner.SkipTriviaEx(sourceFile.Text(), comment.End(), &scanner.SkipTriviaOptions{StopAfterLineBreak: true, StopAtComments: true})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// method on the changeTracker because of converters
|
|
||||||
func (ct *changeTracker) getAdjustedEndPosition(sourceFile *ast.SourceFile, node *ast.Node, trailingTriviaOption trailingTriviaOption) int {
|
|
||||||
if trailingTriviaOption == trailingTriviaOptionExclude {
|
|
||||||
return node.End()
|
|
||||||
}
|
|
||||||
if trailingTriviaOption == trailingTriviaOptionExcludeWhitespace {
|
|
||||||
if comments := slices.AppendSeq(
|
|
||||||
slices.Collect(scanner.GetTrailingCommentRanges(ct.NodeFactory, sourceFile.Text(), node.End())),
|
|
||||||
scanner.GetLeadingCommentRanges(ct.NodeFactory, sourceFile.Text(), node.End()),
|
|
||||||
); len(comments) > 0 {
|
|
||||||
if realEnd := comments[len(comments)-1].End(); realEnd != 0 {
|
|
||||||
return realEnd
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return node.End()
|
|
||||||
}
|
|
||||||
|
|
||||||
if multilineEndPosition := ct.getEndPositionOfMultilineTrailingComment(sourceFile, node, trailingTriviaOption); multilineEndPosition != 0 {
|
|
||||||
return multilineEndPosition
|
|
||||||
}
|
|
||||||
|
|
||||||
newEnd := scanner.SkipTriviaEx(sourceFile.Text(), node.End(), &scanner.SkipTriviaOptions{StopAfterLineBreak: true})
|
|
||||||
|
|
||||||
if newEnd != node.End() && (trailingTriviaOption == trailingTriviaOptionInclude || stringutil.IsLineBreak(rune(sourceFile.Text()[newEnd-1]))) {
|
|
||||||
return newEnd
|
|
||||||
}
|
|
||||||
return node.End()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============= utilities =============
|
|
||||||
|
|
||||||
func hasCommentsBeforeLineBreak(text string, start int) bool {
|
|
||||||
for _, ch := range []rune(text[start:]) {
|
|
||||||
if !stringutil.IsWhiteSpaceSingleLine(ch) {
|
|
||||||
return ch == '/'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func needSemicolonBetween(a, b *ast.Node) bool {
|
|
||||||
return (ast.IsPropertySignatureDeclaration(a) || ast.IsPropertyDeclaration(a)) &&
|
|
||||||
ast.IsClassOrTypeElement(b) &&
|
|
||||||
b.Name().Kind == ast.KindComputedPropertyName ||
|
|
||||||
ast.IsStatementButNotDeclaration(a) &&
|
|
||||||
ast.IsStatementButNotDeclaration(b) // TODO: only if b would start with a `(` or `[`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ct *changeTracker) getInsertionPositionAtSourceFileTop(sourceFile *ast.SourceFile) int {
|
|
||||||
var lastPrologue *ast.Node
|
|
||||||
for _, node := range sourceFile.Statements.Nodes {
|
|
||||||
if ast.IsPrologueDirective(node) {
|
|
||||||
lastPrologue = node
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
position := 0
|
|
||||||
text := sourceFile.Text()
|
|
||||||
advancePastLineBreak := func() {
|
|
||||||
if position >= len(text) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if char := rune(text[position]); stringutil.IsLineBreak(char) {
|
|
||||||
position++
|
|
||||||
if position < len(text) && char == '\r' && rune(text[position]) == '\n' {
|
|
||||||
position++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if lastPrologue != nil {
|
|
||||||
position = lastPrologue.End()
|
|
||||||
advancePastLineBreak()
|
|
||||||
return position
|
|
||||||
}
|
|
||||||
|
|
||||||
shebang := scanner.GetShebang(text)
|
|
||||||
if shebang != "" {
|
|
||||||
position = len(shebang)
|
|
||||||
advancePastLineBreak()
|
|
||||||
}
|
|
||||||
|
|
||||||
ranges := slices.Collect(scanner.GetLeadingCommentRanges(ct.NodeFactory, text, position))
|
|
||||||
if len(ranges) == 0 {
|
|
||||||
return position
|
|
||||||
}
|
|
||||||
// Find the first attached comment to the first node and add before it
|
|
||||||
var lastComment *ast.CommentRange
|
|
||||||
pinnedOrTripleSlash := false
|
|
||||||
firstNodeLine := -1
|
|
||||||
|
|
||||||
lenStatements := len(sourceFile.Statements.Nodes)
|
|
||||||
lineMap := sourceFile.ECMALineMap()
|
|
||||||
for _, r := range ranges {
|
|
||||||
if r.Kind == ast.KindMultiLineCommentTrivia {
|
|
||||||
if printer.IsPinnedComment(text, r) {
|
|
||||||
lastComment = &r
|
|
||||||
pinnedOrTripleSlash = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else if printer.IsRecognizedTripleSlashComment(text, r) {
|
|
||||||
lastComment = &r
|
|
||||||
pinnedOrTripleSlash = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if lastComment != nil {
|
|
||||||
// Always insert after pinned or triple slash comments
|
|
||||||
if pinnedOrTripleSlash {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// There was a blank line between the last comment and this comment.
|
|
||||||
// This comment is not part of the copyright comments
|
|
||||||
commentLine := scanner.ComputeLineOfPosition(lineMap, r.Pos())
|
|
||||||
lastCommentEndLine := scanner.ComputeLineOfPosition(lineMap, lastComment.End())
|
|
||||||
if commentLine >= lastCommentEndLine+2 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if lenStatements > 0 {
|
|
||||||
if firstNodeLine == -1 {
|
|
||||||
firstNodeLine = scanner.ComputeLineOfPosition(lineMap, astnav.GetStartOfNode(sourceFile.Statements.Nodes[0], sourceFile, false))
|
|
||||||
}
|
|
||||||
commentEndLine := scanner.ComputeLineOfPosition(lineMap, r.End())
|
|
||||||
if firstNodeLine < commentEndLine+2 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastComment = &r
|
|
||||||
pinnedOrTripleSlash = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if lastComment != nil {
|
|
||||||
position = lastComment.End()
|
|
||||||
advancePastLineBreak()
|
|
||||||
}
|
|
||||||
return position
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,6 +0,0 @@
|
|||||||
package ls
|
|
||||||
|
|
||||||
const (
|
|
||||||
moduleSpecifierResolutionLimit = 100
|
|
||||||
moduleSpecifierResolutionCacheAttemptLimit = 1000
|
|
||||||
)
|
|
||||||
@ -1,199 +0,0 @@
|
|||||||
package ls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
"unicode/utf16"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Converters struct {
|
|
||||||
getLineMap func(fileName string) *LSPLineMap
|
|
||||||
positionEncoding lsproto.PositionEncodingKind
|
|
||||||
}
|
|
||||||
|
|
||||||
type Script interface {
|
|
||||||
FileName() string
|
|
||||||
Text() string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConverters(positionEncoding lsproto.PositionEncodingKind, getLineMap func(fileName string) *LSPLineMap) *Converters {
|
|
||||||
return &Converters{
|
|
||||||
getLineMap: getLineMap,
|
|
||||||
positionEncoding: positionEncoding,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Converters) ToLSPRange(script Script, textRange core.TextRange) lsproto.Range {
|
|
||||||
return lsproto.Range{
|
|
||||||
Start: c.PositionToLineAndCharacter(script, core.TextPos(textRange.Pos())),
|
|
||||||
End: c.PositionToLineAndCharacter(script, core.TextPos(textRange.End())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Converters) FromLSPRange(script Script, textRange lsproto.Range) core.TextRange {
|
|
||||||
return core.NewTextRange(
|
|
||||||
int(c.LineAndCharacterToPosition(script, textRange.Start)),
|
|
||||||
int(c.LineAndCharacterToPosition(script, textRange.End)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Converters) FromLSPTextChange(script Script, change *lsproto.TextDocumentContentChangePartial) core.TextChange {
|
|
||||||
return core.TextChange{
|
|
||||||
TextRange: c.FromLSPRange(script, change.Range),
|
|
||||||
NewText: change.Text,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Converters) ToLSPLocation(script Script, rng core.TextRange) lsproto.Location {
|
|
||||||
return lsproto.Location{
|
|
||||||
Uri: FileNameToDocumentURI(script.FileName()),
|
|
||||||
Range: c.ToLSPRange(script, rng),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func LanguageKindToScriptKind(languageID lsproto.LanguageKind) core.ScriptKind {
|
|
||||||
switch languageID {
|
|
||||||
case "typescript":
|
|
||||||
return core.ScriptKindTS
|
|
||||||
case "typescriptreact":
|
|
||||||
return core.ScriptKindTSX
|
|
||||||
case "javascript":
|
|
||||||
return core.ScriptKindJS
|
|
||||||
case "javascriptreact":
|
|
||||||
return core.ScriptKindJSX
|
|
||||||
case "json":
|
|
||||||
return core.ScriptKindJSON
|
|
||||||
default:
|
|
||||||
return core.ScriptKindUnknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/microsoft/vscode-uri/blob/edfdccd976efaf4bb8fdeca87e97c47257721729/src/uri.ts#L455
|
|
||||||
var extraEscapeReplacer = strings.NewReplacer(
|
|
||||||
":", "%3A",
|
|
||||||
"/", "%2F",
|
|
||||||
"?", "%3F",
|
|
||||||
"#", "%23",
|
|
||||||
"[", "%5B",
|
|
||||||
"]", "%5D",
|
|
||||||
"@", "%40",
|
|
||||||
|
|
||||||
"!", "%21",
|
|
||||||
"$", "%24",
|
|
||||||
"&", "%26",
|
|
||||||
"'", "%27",
|
|
||||||
"(", "%28",
|
|
||||||
")", "%29",
|
|
||||||
"*", "%2A",
|
|
||||||
"+", "%2B",
|
|
||||||
",", "%2C",
|
|
||||||
";", "%3B",
|
|
||||||
"=", "%3D",
|
|
||||||
|
|
||||||
" ", "%20",
|
|
||||||
)
|
|
||||||
|
|
||||||
func FileNameToDocumentURI(fileName string) lsproto.DocumentUri {
|
|
||||||
if strings.HasPrefix(fileName, "^/") {
|
|
||||||
scheme, rest, ok := strings.Cut(fileName[2:], "/")
|
|
||||||
if !ok {
|
|
||||||
panic("invalid file name: " + fileName)
|
|
||||||
}
|
|
||||||
authority, path, ok := strings.Cut(rest, "/")
|
|
||||||
if !ok {
|
|
||||||
panic("invalid file name: " + fileName)
|
|
||||||
}
|
|
||||||
if authority == "ts-nul-authority" {
|
|
||||||
return lsproto.DocumentUri(scheme + ":" + path)
|
|
||||||
}
|
|
||||||
return lsproto.DocumentUri(scheme + "://" + authority + "/" + path)
|
|
||||||
}
|
|
||||||
|
|
||||||
volume, fileName, _ := tspath.SplitVolumePath(fileName)
|
|
||||||
if volume != "" {
|
|
||||||
volume = "/" + extraEscapeReplacer.Replace(volume)
|
|
||||||
}
|
|
||||||
|
|
||||||
fileName = strings.TrimPrefix(fileName, "//")
|
|
||||||
|
|
||||||
parts := strings.Split(fileName, "/")
|
|
||||||
for i, part := range parts {
|
|
||||||
parts[i] = extraEscapeReplacer.Replace(url.PathEscape(part))
|
|
||||||
}
|
|
||||||
|
|
||||||
return lsproto.DocumentUri("file://" + volume + strings.Join(parts, "/"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Converters) LineAndCharacterToPosition(script Script, lineAndCharacter lsproto.Position) core.TextPos {
|
|
||||||
// UTF-8/16 0-indexed line and character to UTF-8 offset
|
|
||||||
|
|
||||||
lineMap := c.getLineMap(script.FileName())
|
|
||||||
|
|
||||||
line := core.TextPos(lineAndCharacter.Line)
|
|
||||||
char := core.TextPos(lineAndCharacter.Character)
|
|
||||||
|
|
||||||
if line < 0 || int(line) >= len(lineMap.LineStarts) {
|
|
||||||
panic(fmt.Sprintf("bad line number. Line: %d, lineMap length: %d", line, len(lineMap.LineStarts)))
|
|
||||||
}
|
|
||||||
|
|
||||||
start := lineMap.LineStarts[line]
|
|
||||||
if lineMap.AsciiOnly || c.positionEncoding == lsproto.PositionEncodingKindUTF8 {
|
|
||||||
return start + char
|
|
||||||
}
|
|
||||||
|
|
||||||
var utf8Char core.TextPos
|
|
||||||
var utf16Char core.TextPos
|
|
||||||
|
|
||||||
for i, r := range script.Text()[start:] {
|
|
||||||
u16Len := core.TextPos(utf16.RuneLen(r))
|
|
||||||
if utf16Char+u16Len > char {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
utf16Char += u16Len
|
|
||||||
utf8Char = core.TextPos(i + utf8.RuneLen(r))
|
|
||||||
}
|
|
||||||
|
|
||||||
return start + utf8Char
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Converters) PositionToLineAndCharacter(script Script, position core.TextPos) lsproto.Position {
|
|
||||||
// UTF-8 offset to UTF-8/16 0-indexed line and character
|
|
||||||
|
|
||||||
lineMap := c.getLineMap(script.FileName())
|
|
||||||
|
|
||||||
line, isLineStart := slices.BinarySearch(lineMap.LineStarts, position)
|
|
||||||
if !isLineStart {
|
|
||||||
line--
|
|
||||||
}
|
|
||||||
line = max(0, line)
|
|
||||||
|
|
||||||
// The current line ranges from lineMap.LineStarts[line] (or 0) to lineMap.LineStarts[line+1] (or len(text)).
|
|
||||||
|
|
||||||
start := lineMap.LineStarts[line]
|
|
||||||
|
|
||||||
var character core.TextPos
|
|
||||||
if lineMap.AsciiOnly || c.positionEncoding == lsproto.PositionEncodingKindUTF8 {
|
|
||||||
character = position - start
|
|
||||||
} else {
|
|
||||||
// We need to rescan the text as UTF-16 to find the character offset.
|
|
||||||
for _, r := range script.Text()[start:position] {
|
|
||||||
character += core.TextPos(utf16.RuneLen(r))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return lsproto.Position{
|
|
||||||
Line: uint32(line),
|
|
||||||
Character: uint32(character),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ptrTo[T any](v T) *T {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
@ -1,83 +0,0 @@
|
|||||||
package ls_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ls"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto"
|
|
||||||
"gotest.tools/v3/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDocumentURIToFileName(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
uri lsproto.DocumentUri
|
|
||||||
fileName string
|
|
||||||
}{
|
|
||||||
{"file:///path/to/file.ts", "/path/to/file.ts"},
|
|
||||||
{"file://server/share/file.ts", "//server/share/file.ts"},
|
|
||||||
{"file:///d%3A/work/tsgo932/lib/utils.ts", "d:/work/tsgo932/lib/utils.ts"},
|
|
||||||
{"file:///D%3A/work/tsgo932/lib/utils.ts", "d:/work/tsgo932/lib/utils.ts"},
|
|
||||||
{"file:///d%3A/work/tsgo932/app/%28test%29/comp/comp-test.tsx", "d:/work/tsgo932/app/(test)/comp/comp-test.tsx"},
|
|
||||||
{"file:///path/to/file.ts#section", "/path/to/file.ts"},
|
|
||||||
{"file:///c:/test/me", "c:/test/me"},
|
|
||||||
{"file://shares/files/c%23/p.cs", "//shares/files/c#/p.cs"},
|
|
||||||
{"file:///c:/Source/Z%C3%BCrich%20or%20Zurich%20(%CB%88zj%CA%8A%C9%99r%C9%AAk,/Code/resources/app/plugins/c%23/plugin.json", "c:/Source/Zürich or Zurich (ˈzjʊərɪk,/Code/resources/app/plugins/c#/plugin.json"},
|
|
||||||
{"file:///c:/test %25/path", "c:/test %/path"},
|
|
||||||
// {"file:?q", "/"},
|
|
||||||
{"file:///_:/path", "/_:/path"},
|
|
||||||
{"file:///users/me/c%23-projects/", "/users/me/c#-projects/"},
|
|
||||||
{"file://localhost/c%24/GitDevelopment/express", "//localhost/c$/GitDevelopment/express"},
|
|
||||||
{"file:///c%3A/test%20with%20%2525/c%23code", "c:/test with %25/c#code"},
|
|
||||||
|
|
||||||
{"untitled:Untitled-1", "^/untitled/ts-nul-authority/Untitled-1"},
|
|
||||||
{"untitled:Untitled-1#fragment", "^/untitled/ts-nul-authority/Untitled-1#fragment"},
|
|
||||||
{"untitled:c:/Users/jrieken/Code/abc.txt", "^/untitled/ts-nul-authority/c:/Users/jrieken/Code/abc.txt"},
|
|
||||||
{"untitled:C:/Users/jrieken/Code/abc.txt", "^/untitled/ts-nul-authority/C:/Users/jrieken/Code/abc.txt"},
|
|
||||||
{"untitled://wsl%2Bubuntu/home/jabaile/work/TypeScript-go/newfile.ts", "^/untitled/wsl%2Bubuntu/home/jabaile/work/TypeScript-go/newfile.ts"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(string(test.uri), func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
assert.Equal(t, test.uri.FileName(), test.fileName)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFileNameToDocumentURI(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
fileName string
|
|
||||||
uri lsproto.DocumentUri
|
|
||||||
}{
|
|
||||||
{"/path/to/file.ts", "file:///path/to/file.ts"},
|
|
||||||
{"//server/share/file.ts", "file://server/share/file.ts"},
|
|
||||||
{"d:/work/tsgo932/lib/utils.ts", "file:///d%3A/work/tsgo932/lib/utils.ts"},
|
|
||||||
{"d:/work/tsgo932/lib/utils.ts", "file:///d%3A/work/tsgo932/lib/utils.ts"},
|
|
||||||
{"d:/work/tsgo932/app/(test)/comp/comp-test.tsx", "file:///d%3A/work/tsgo932/app/%28test%29/comp/comp-test.tsx"},
|
|
||||||
{"/path/to/file.ts", "file:///path/to/file.ts"},
|
|
||||||
{"c:/test/me", "file:///c%3A/test/me"},
|
|
||||||
{"//shares/files/c#/p.cs", "file://shares/files/c%23/p.cs"},
|
|
||||||
{"c:/Source/Zürich or Zurich (ˈzjʊərɪk,/Code/resources/app/plugins/c#/plugin.json", "file:///c%3A/Source/Z%C3%BCrich%20or%20Zurich%20%28%CB%88zj%CA%8A%C9%99r%C9%AAk%2C/Code/resources/app/plugins/c%23/plugin.json"},
|
|
||||||
{"c:/test %/path", "file:///c%3A/test%20%25/path"},
|
|
||||||
{"/", "file:///"},
|
|
||||||
{"/_:/path", "file:///_%3A/path"},
|
|
||||||
{"/users/me/c#-projects/", "file:///users/me/c%23-projects/"},
|
|
||||||
{"//localhost/c$/GitDevelopment/express", "file://localhost/c%24/GitDevelopment/express"},
|
|
||||||
{"c:/test with %25/c#code", "file:///c%3A/test%20with%20%2525/c%23code"},
|
|
||||||
|
|
||||||
{"^/untitled/ts-nul-authority/Untitled-1", "untitled:Untitled-1"},
|
|
||||||
{"^/untitled/ts-nul-authority/c:/Users/jrieken/Code/abc.txt", "untitled:c:/Users/jrieken/Code/abc.txt"},
|
|
||||||
{"^/untitled/ts-nul-authority///wsl%2Bubuntu/home/jabaile/work/TypeScript-go/newfile.ts", "untitled://wsl%2Bubuntu/home/jabaile/work/TypeScript-go/newfile.ts"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.fileName, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
assert.Equal(t, ls.FileNameToDocumentURI(test.fileName), test.uri)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,234 +0,0 @@
|
|||||||
package ls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"slices"
|
|
||||||
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/astnav"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/checker"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/scanner"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (l *LanguageService) ProvideDefinition(ctx context.Context, documentURI lsproto.DocumentUri, position lsproto.Position) (lsproto.DefinitionResponse, error) {
|
|
||||||
program, file := l.getProgramAndFile(documentURI)
|
|
||||||
node := astnav.GetTouchingPropertyName(file, int(l.converters.LineAndCharacterToPosition(file, position)))
|
|
||||||
if node.Kind == ast.KindSourceFile {
|
|
||||||
return lsproto.LocationOrLocationsOrDefinitionLinksOrNull{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
c, done := program.GetTypeCheckerForFile(ctx, file)
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
if node.Kind == ast.KindOverrideKeyword {
|
|
||||||
if sym := getSymbolForOverriddenMember(c, node); sym != nil {
|
|
||||||
return l.createLocationsFromDeclarations(sym.Declarations), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ast.IsJumpStatementTarget(node) {
|
|
||||||
if label := getTargetLabel(node.Parent, node.Text()); label != nil {
|
|
||||||
return l.createLocationsFromDeclarations([]*ast.Node{label}), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if node.Kind == ast.KindCaseKeyword || node.Kind == ast.KindDefaultKeyword && ast.IsDefaultClause(node.Parent) {
|
|
||||||
if stmt := ast.FindAncestor(node.Parent, ast.IsSwitchStatement); stmt != nil {
|
|
||||||
file := ast.GetSourceFileOfNode(stmt)
|
|
||||||
return l.createLocationFromFileAndRange(file, scanner.GetRangeOfTokenAtPosition(file, stmt.Pos())), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if node.Kind == ast.KindReturnKeyword || node.Kind == ast.KindYieldKeyword || node.Kind == ast.KindAwaitKeyword {
|
|
||||||
if fn := ast.FindAncestor(node, ast.IsFunctionLikeDeclaration); fn != nil {
|
|
||||||
return l.createLocationsFromDeclarations([]*ast.Node{fn}), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declarations := getDeclarationsFromLocation(c, node)
|
|
||||||
calledDeclaration := tryGetSignatureDeclaration(c, node)
|
|
||||||
if calledDeclaration != nil {
|
|
||||||
// If we can resolve a call signature, remove all function-like declarations and add that signature.
|
|
||||||
nonFunctionDeclarations := core.Filter(slices.Clip(declarations), func(node *ast.Node) bool { return !ast.IsFunctionLike(node) })
|
|
||||||
declarations = append(nonFunctionDeclarations, calledDeclaration)
|
|
||||||
}
|
|
||||||
return l.createLocationsFromDeclarations(declarations), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) ProvideTypeDefinition(ctx context.Context, documentURI lsproto.DocumentUri, position lsproto.Position) (lsproto.DefinitionResponse, error) {
|
|
||||||
program, file := l.getProgramAndFile(documentURI)
|
|
||||||
node := astnav.GetTouchingPropertyName(file, int(l.converters.LineAndCharacterToPosition(file, position)))
|
|
||||||
if node.Kind == ast.KindSourceFile {
|
|
||||||
return lsproto.LocationOrLocationsOrDefinitionLinksOrNull{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
c, done := program.GetTypeCheckerForFile(ctx, file)
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
node = getDeclarationNameForKeyword(node)
|
|
||||||
|
|
||||||
if symbol := c.GetSymbolAtLocation(node); symbol != nil {
|
|
||||||
symbolType := getTypeOfSymbolAtLocation(c, symbol, node)
|
|
||||||
declarations := getDeclarationsFromType(symbolType)
|
|
||||||
if typeArgument := c.GetFirstTypeArgumentFromKnownType(symbolType); typeArgument != nil {
|
|
||||||
declarations = core.Concatenate(getDeclarationsFromType(typeArgument), declarations)
|
|
||||||
}
|
|
||||||
if len(declarations) != 0 {
|
|
||||||
return l.createLocationsFromDeclarations(declarations), nil
|
|
||||||
}
|
|
||||||
if symbol.Flags&ast.SymbolFlagsValue == 0 && symbol.Flags&ast.SymbolFlagsType != 0 {
|
|
||||||
return l.createLocationsFromDeclarations(symbol.Declarations), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return lsproto.LocationOrLocationsOrDefinitionLinksOrNull{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDeclarationNameForKeyword(node *ast.Node) *ast.Node {
|
|
||||||
if node.Kind >= ast.KindFirstKeyword && node.Kind <= ast.KindLastKeyword {
|
|
||||||
if ast.IsVariableDeclarationList(node.Parent) {
|
|
||||||
if decl := core.FirstOrNil(node.Parent.AsVariableDeclarationList().Declarations.Nodes); decl != nil && decl.Name() != nil {
|
|
||||||
return decl.Name()
|
|
||||||
}
|
|
||||||
} else if node.Parent.DeclarationData() != nil && node.Parent.Name() != nil && node.Pos() < node.Parent.Name().Pos() {
|
|
||||||
return node.Parent.Name()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) createLocationsFromDeclarations(declarations []*ast.Node) lsproto.DefinitionResponse {
|
|
||||||
locations := make([]lsproto.Location, 0, len(declarations))
|
|
||||||
for _, decl := range declarations {
|
|
||||||
file := ast.GetSourceFileOfNode(decl)
|
|
||||||
name := core.OrElse(ast.GetNameOfDeclaration(decl), decl)
|
|
||||||
nodeRange := createRangeFromNode(name, file)
|
|
||||||
mappedLocation := l.getMappedLocation(file.FileName(), nodeRange)
|
|
||||||
locations = core.AppendIfUnique(locations, mappedLocation)
|
|
||||||
}
|
|
||||||
return lsproto.LocationOrLocationsOrDefinitionLinksOrNull{Locations: &locations}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) createLocationFromFileAndRange(file *ast.SourceFile, textRange core.TextRange) lsproto.DefinitionResponse {
|
|
||||||
mappedLocation := l.getMappedLocation(file.FileName(), textRange)
|
|
||||||
return lsproto.LocationOrLocationsOrDefinitionLinksOrNull{
|
|
||||||
Location: &mappedLocation,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDeclarationsFromLocation(c *checker.Checker, node *ast.Node) []*ast.Node {
|
|
||||||
if ast.IsIdentifier(node) && ast.IsShorthandPropertyAssignment(node.Parent) {
|
|
||||||
return c.GetResolvedSymbol(node).Declarations
|
|
||||||
}
|
|
||||||
node = getDeclarationNameForKeyword(node)
|
|
||||||
if symbol := c.GetSymbolAtLocation(node); symbol != nil {
|
|
||||||
if symbol.Flags&ast.SymbolFlagsClass != 0 && symbol.Flags&(ast.SymbolFlagsFunction|ast.SymbolFlagsVariable) == 0 && node.Kind == ast.KindConstructorKeyword {
|
|
||||||
if constructor := symbol.Members[ast.InternalSymbolNameConstructor]; constructor != nil {
|
|
||||||
symbol = constructor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if symbol.Flags&ast.SymbolFlagsAlias != 0 {
|
|
||||||
if resolved, ok := c.ResolveAlias(symbol); ok {
|
|
||||||
symbol = resolved
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if symbol.Flags&(ast.SymbolFlagsProperty|ast.SymbolFlagsMethod|ast.SymbolFlagsAccessor) != 0 && symbol.Parent != nil && symbol.Parent.Flags&ast.SymbolFlagsObjectLiteral != 0 {
|
|
||||||
if objectLiteral := core.FirstOrNil(symbol.Parent.Declarations); objectLiteral != nil {
|
|
||||||
if declarations := c.GetContextualDeclarationsForObjectLiteralElement(objectLiteral, symbol.Name); len(declarations) != 0 {
|
|
||||||
return declarations
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return symbol.Declarations
|
|
||||||
}
|
|
||||||
if indexInfos := c.GetIndexSignaturesAtLocation(node); len(indexInfos) != 0 {
|
|
||||||
return indexInfos
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a CallLikeExpression where `node` is the target being invoked.
|
|
||||||
func getAncestorCallLikeExpression(node *ast.Node) *ast.Node {
|
|
||||||
target := ast.FindAncestor(node, func(n *ast.Node) bool {
|
|
||||||
return !isRightSideOfPropertyAccess(n)
|
|
||||||
})
|
|
||||||
callLike := target.Parent
|
|
||||||
if callLike != nil && ast.IsCallLikeExpression(callLike) && ast.GetInvokedExpression(callLike) == target {
|
|
||||||
return callLike
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func tryGetSignatureDeclaration(typeChecker *checker.Checker, node *ast.Node) *ast.Node {
|
|
||||||
var signature *checker.Signature
|
|
||||||
callLike := getAncestorCallLikeExpression(node)
|
|
||||||
if callLike != nil {
|
|
||||||
signature = typeChecker.GetResolvedSignature(callLike)
|
|
||||||
}
|
|
||||||
// Don't go to a function type, go to the value having that type.
|
|
||||||
var declaration *ast.Node
|
|
||||||
if signature != nil && signature.Declaration() != nil {
|
|
||||||
declaration = signature.Declaration()
|
|
||||||
if ast.IsFunctionLike(declaration) && !ast.IsFunctionTypeNode(declaration) {
|
|
||||||
return declaration
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSymbolForOverriddenMember(typeChecker *checker.Checker, node *ast.Node) *ast.Symbol {
|
|
||||||
classElement := ast.FindAncestor(node, ast.IsClassElement)
|
|
||||||
if classElement == nil || classElement.Name() == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
baseDeclaration := ast.FindAncestor(classElement, ast.IsClassLike)
|
|
||||||
if baseDeclaration == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
baseTypeNode := ast.GetClassExtendsHeritageElement(baseDeclaration)
|
|
||||||
if baseTypeNode == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
expression := ast.SkipParentheses(baseTypeNode.Expression())
|
|
||||||
var base *ast.Symbol
|
|
||||||
if ast.IsClassExpression(expression) {
|
|
||||||
base = expression.Symbol()
|
|
||||||
} else {
|
|
||||||
base = typeChecker.GetSymbolAtLocation(expression)
|
|
||||||
}
|
|
||||||
if base == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
name := ast.GetTextOfPropertyName(classElement.Name())
|
|
||||||
if ast.HasStaticModifier(classElement) {
|
|
||||||
return typeChecker.GetPropertyOfType(typeChecker.GetTypeOfSymbol(base), name)
|
|
||||||
}
|
|
||||||
return typeChecker.GetPropertyOfType(typeChecker.GetDeclaredTypeOfSymbol(base), name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTypeOfSymbolAtLocation(c *checker.Checker, symbol *ast.Symbol, node *ast.Node) *checker.Type {
|
|
||||||
t := c.GetTypeOfSymbolAtLocation(symbol, node)
|
|
||||||
// If the type is just a function's inferred type, go-to-type should go to the return type instead since
|
|
||||||
// go-to-definition takes you to the function anyway.
|
|
||||||
if t.Symbol() == symbol || t.Symbol() != nil && symbol.ValueDeclaration != nil && ast.IsVariableDeclaration(symbol.ValueDeclaration) && symbol.ValueDeclaration.Initializer() == t.Symbol().ValueDeclaration {
|
|
||||||
sigs := c.GetCallSignatures(t)
|
|
||||||
if len(sigs) == 1 {
|
|
||||||
return c.GetReturnTypeOfSignature(sigs[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDeclarationsFromType(t *checker.Type) []*ast.Node {
|
|
||||||
var result []*ast.Node
|
|
||||||
for _, t := range t.Distributed() {
|
|
||||||
if t.Symbol() != nil {
|
|
||||||
for _, decl := range t.Symbol().Declarations {
|
|
||||||
result = core.AppendIfUnique(result, decl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
@ -1,109 +0,0 @@
|
|||||||
package ls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/diagnostics"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/diagnosticwriter"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (l *LanguageService) ProvideDiagnostics(ctx context.Context, uri lsproto.DocumentUri) (lsproto.DocumentDiagnosticResponse, error) {
|
|
||||||
program, file := l.getProgramAndFile(uri)
|
|
||||||
|
|
||||||
diagnostics := make([][]*ast.Diagnostic, 0, 4)
|
|
||||||
diagnostics = append(diagnostics, program.GetSyntacticDiagnostics(ctx, file))
|
|
||||||
diagnostics = append(diagnostics, program.GetSemanticDiagnostics(ctx, file))
|
|
||||||
// !!! user preference for suggestion diagnostics; keep only unnecessary/deprecated?
|
|
||||||
// See: https://github.com/microsoft/vscode/blob/3dbc74129aaae102e5cb485b958fa5360e8d3e7a/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts#L114
|
|
||||||
diagnostics = append(diagnostics, program.GetSuggestionDiagnostics(ctx, file))
|
|
||||||
if program.Options().GetEmitDeclarations() {
|
|
||||||
diagnostics = append(diagnostics, program.GetDeclarationDiagnostics(ctx, file))
|
|
||||||
}
|
|
||||||
|
|
||||||
return lsproto.RelatedFullDocumentDiagnosticReportOrUnchangedDocumentDiagnosticReport{
|
|
||||||
FullDocumentDiagnosticReport: &lsproto.RelatedFullDocumentDiagnosticReport{
|
|
||||||
Items: toLSPDiagnostics(l.converters, diagnostics...),
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func toLSPDiagnostics(converters *Converters, diagnostics ...[]*ast.Diagnostic) []*lsproto.Diagnostic {
|
|
||||||
size := 0
|
|
||||||
for _, diagSlice := range diagnostics {
|
|
||||||
size += len(diagSlice)
|
|
||||||
}
|
|
||||||
lspDiagnostics := make([]*lsproto.Diagnostic, 0, size)
|
|
||||||
for _, diagSlice := range diagnostics {
|
|
||||||
for _, diag := range diagSlice {
|
|
||||||
lspDiagnostics = append(lspDiagnostics, toLSPDiagnostic(converters, diag))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return lspDiagnostics
|
|
||||||
}
|
|
||||||
|
|
||||||
func toLSPDiagnostic(converters *Converters, diagnostic *ast.Diagnostic) *lsproto.Diagnostic {
|
|
||||||
var severity lsproto.DiagnosticSeverity
|
|
||||||
switch diagnostic.Category() {
|
|
||||||
case diagnostics.CategorySuggestion:
|
|
||||||
severity = lsproto.DiagnosticSeverityHint
|
|
||||||
case diagnostics.CategoryMessage:
|
|
||||||
severity = lsproto.DiagnosticSeverityInformation
|
|
||||||
case diagnostics.CategoryWarning:
|
|
||||||
severity = lsproto.DiagnosticSeverityWarning
|
|
||||||
default:
|
|
||||||
severity = lsproto.DiagnosticSeverityError
|
|
||||||
}
|
|
||||||
|
|
||||||
relatedInformation := make([]*lsproto.DiagnosticRelatedInformation, 0, len(diagnostic.RelatedInformation()))
|
|
||||||
for _, related := range diagnostic.RelatedInformation() {
|
|
||||||
relatedInformation = append(relatedInformation, &lsproto.DiagnosticRelatedInformation{
|
|
||||||
Location: lsproto.Location{
|
|
||||||
Uri: FileNameToDocumentURI(related.File().FileName()),
|
|
||||||
Range: converters.ToLSPRange(related.File(), related.Loc()),
|
|
||||||
},
|
|
||||||
Message: related.Message(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var tags []lsproto.DiagnosticTag
|
|
||||||
if diagnostic.ReportsUnnecessary() || diagnostic.ReportsDeprecated() {
|
|
||||||
tags = make([]lsproto.DiagnosticTag, 0, 2)
|
|
||||||
if diagnostic.ReportsUnnecessary() {
|
|
||||||
tags = append(tags, lsproto.DiagnosticTagUnnecessary)
|
|
||||||
}
|
|
||||||
if diagnostic.ReportsDeprecated() {
|
|
||||||
tags = append(tags, lsproto.DiagnosticTagDeprecated)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &lsproto.Diagnostic{
|
|
||||||
Range: converters.ToLSPRange(diagnostic.File(), diagnostic.Loc()),
|
|
||||||
Code: &lsproto.IntegerOrString{
|
|
||||||
Integer: ptrTo(diagnostic.Code()),
|
|
||||||
},
|
|
||||||
Severity: &severity,
|
|
||||||
Message: messageChainToString(diagnostic),
|
|
||||||
Source: ptrTo("ts"),
|
|
||||||
RelatedInformation: ptrToSliceIfNonEmpty(relatedInformation),
|
|
||||||
Tags: ptrToSliceIfNonEmpty(tags),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func messageChainToString(diagnostic *ast.Diagnostic) string {
|
|
||||||
if len(diagnostic.MessageChain()) == 0 {
|
|
||||||
return diagnostic.Message()
|
|
||||||
}
|
|
||||||
var b strings.Builder
|
|
||||||
diagnosticwriter.WriteFlattenedDiagnosticMessage(&b, diagnostic, "\n")
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func ptrToSliceIfNonEmpty[T any](s []T) *[]T {
|
|
||||||
if len(s) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &s
|
|
||||||
}
|
|
||||||
@ -1,690 +0,0 @@
|
|||||||
package ls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/astnav"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/compiler"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsutil"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/scanner"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/stringutil"
|
|
||||||
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (l *LanguageService) ProvideDocumentHighlights(ctx context.Context, documentUri lsproto.DocumentUri, documentPosition lsproto.Position) (lsproto.DocumentHighlightResponse, error) {
|
|
||||||
program, sourceFile := l.getProgramAndFile(documentUri)
|
|
||||||
position := int(l.converters.LineAndCharacterToPosition(sourceFile, documentPosition))
|
|
||||||
node := astnav.GetTouchingPropertyName(sourceFile, position)
|
|
||||||
if node.Parent != nil && (node.Parent.Kind == ast.KindJsxClosingElement || (node.Parent.Kind == ast.KindJsxOpeningElement && node.Parent.TagName() == node)) {
|
|
||||||
var openingElement, closingElement *ast.Node
|
|
||||||
if ast.IsJsxElement(node.Parent.Parent) {
|
|
||||||
openingElement = node.Parent.Parent.AsJsxElement().OpeningElement
|
|
||||||
closingElement = node.Parent.Parent.AsJsxElement().ClosingElement
|
|
||||||
}
|
|
||||||
var documentHighlights []*lsproto.DocumentHighlight
|
|
||||||
kind := lsproto.DocumentHighlightKindRead
|
|
||||||
if openingElement != nil {
|
|
||||||
documentHighlights = append(documentHighlights, &lsproto.DocumentHighlight{
|
|
||||||
Range: *l.createLspRangeFromNode(openingElement, sourceFile),
|
|
||||||
Kind: &kind,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if closingElement != nil {
|
|
||||||
documentHighlights = append(documentHighlights, &lsproto.DocumentHighlight{
|
|
||||||
Range: *l.createLspRangeFromNode(closingElement, sourceFile),
|
|
||||||
Kind: &kind,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return lsproto.DocumentHighlightsOrNull{
|
|
||||||
DocumentHighlights: &documentHighlights,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
documentHighlights := l.getSemanticDocumentHighlights(ctx, position, node, program, sourceFile)
|
|
||||||
if len(documentHighlights) == 0 {
|
|
||||||
documentHighlights = l.getSyntacticDocumentHighlights(node, sourceFile)
|
|
||||||
}
|
|
||||||
// if nil is passed here we never generate an error, just pass an empty higlight
|
|
||||||
return lsproto.DocumentHighlightsOrNull{DocumentHighlights: &documentHighlights}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) getSemanticDocumentHighlights(ctx context.Context, position int, node *ast.Node, program *compiler.Program, sourceFile *ast.SourceFile) []*lsproto.DocumentHighlight {
|
|
||||||
options := refOptions{use: referenceUseReferences}
|
|
||||||
referenceEntries := l.getReferencedSymbolsForNode(ctx, position, node, program, []*ast.SourceFile{sourceFile}, options, &collections.Set[string]{})
|
|
||||||
if referenceEntries == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var highlights []*lsproto.DocumentHighlight
|
|
||||||
for _, entry := range referenceEntries {
|
|
||||||
for _, ref := range entry.references {
|
|
||||||
if ref.node != nil {
|
|
||||||
fileName, highlight := l.toDocumentHighlight(ref)
|
|
||||||
if fileName == sourceFile.FileName() {
|
|
||||||
highlights = append(highlights, highlight)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return highlights
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) toDocumentHighlight(entry *referenceEntry) (string, *lsproto.DocumentHighlight) {
|
|
||||||
entry = l.resolveEntry(entry)
|
|
||||||
|
|
||||||
kind := lsproto.DocumentHighlightKindRead
|
|
||||||
if entry.kind == entryKindRange {
|
|
||||||
return entry.fileName, &lsproto.DocumentHighlight{
|
|
||||||
Range: *entry.textRange,
|
|
||||||
Kind: &kind,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine write access for node references.
|
|
||||||
if ast.IsWriteAccessForReference(entry.node) {
|
|
||||||
kind = lsproto.DocumentHighlightKindWrite
|
|
||||||
}
|
|
||||||
|
|
||||||
dh := &lsproto.DocumentHighlight{
|
|
||||||
Range: *entry.textRange,
|
|
||||||
Kind: &kind,
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry.fileName, dh
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) getSyntacticDocumentHighlights(node *ast.Node, sourceFile *ast.SourceFile) []*lsproto.DocumentHighlight {
|
|
||||||
switch node.Kind {
|
|
||||||
case ast.KindIfKeyword, ast.KindElseKeyword:
|
|
||||||
if ast.IsIfStatement(node.Parent) {
|
|
||||||
return l.getIfElseOccurrences(node.Parent.AsIfStatement(), sourceFile)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case ast.KindReturnKeyword:
|
|
||||||
return l.useParent(node.Parent, ast.IsReturnStatement, getReturnOccurrences, sourceFile)
|
|
||||||
case ast.KindThrowKeyword:
|
|
||||||
return l.useParent(node.Parent, ast.IsThrowStatement, getThrowOccurrences, sourceFile)
|
|
||||||
case ast.KindTryKeyword, ast.KindCatchKeyword, ast.KindFinallyKeyword:
|
|
||||||
var tryStatement *ast.Node
|
|
||||||
if node.Kind == ast.KindCatchKeyword {
|
|
||||||
tryStatement = node.Parent.Parent
|
|
||||||
} else {
|
|
||||||
tryStatement = node.Parent
|
|
||||||
}
|
|
||||||
return l.useParent(tryStatement, ast.IsTryStatement, getTryCatchFinallyOccurrences, sourceFile)
|
|
||||||
case ast.KindSwitchKeyword:
|
|
||||||
return l.useParent(node.Parent, ast.IsSwitchStatement, getSwitchCaseDefaultOccurrences, sourceFile)
|
|
||||||
case ast.KindCaseKeyword, ast.KindDefaultKeyword:
|
|
||||||
if ast.IsDefaultClause(node.Parent) || ast.IsCaseClause(node.Parent) {
|
|
||||||
return l.useParent(node.Parent.Parent.Parent, ast.IsSwitchStatement, getSwitchCaseDefaultOccurrences, sourceFile)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case ast.KindBreakKeyword, ast.KindContinueKeyword:
|
|
||||||
return l.useParent(node.Parent, ast.IsBreakOrContinueStatement, getBreakOrContinueStatementOccurrences, sourceFile)
|
|
||||||
case ast.KindForKeyword, ast.KindWhileKeyword, ast.KindDoKeyword:
|
|
||||||
return l.useParent(node.Parent, func(n *ast.Node) bool {
|
|
||||||
return ast.IsIterationStatement(n, true)
|
|
||||||
}, getLoopBreakContinueOccurrences, sourceFile)
|
|
||||||
case ast.KindConstructorKeyword:
|
|
||||||
return l.getFromAllDeclarations(ast.IsConstructorDeclaration, []ast.Kind{ast.KindConstructorKeyword}, node, sourceFile)
|
|
||||||
case ast.KindGetKeyword, ast.KindSetKeyword:
|
|
||||||
return l.getFromAllDeclarations(ast.IsAccessor, []ast.Kind{ast.KindGetKeyword, ast.KindSetKeyword}, node, sourceFile)
|
|
||||||
case ast.KindAwaitKeyword:
|
|
||||||
return l.useParent(node.Parent, ast.IsAwaitExpression, getAsyncAndAwaitOccurrences, sourceFile)
|
|
||||||
case ast.KindAsyncKeyword:
|
|
||||||
return l.highlightSpans(getAsyncAndAwaitOccurrences(node, sourceFile), sourceFile)
|
|
||||||
case ast.KindYieldKeyword:
|
|
||||||
return l.highlightSpans(getYieldOccurrences(node, sourceFile), sourceFile)
|
|
||||||
case ast.KindInKeyword, ast.KindOutKeyword:
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
if ast.IsModifierKind(node.Kind) && (ast.IsDeclaration(node.Parent) || ast.IsVariableStatement(node.Parent)) {
|
|
||||||
return l.highlightSpans(getModifierOccurrences(node.Kind, node.Parent, sourceFile), sourceFile)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) useParent(node *ast.Node, nodeTest func(*ast.Node) bool, getNodes func(*ast.Node, *ast.SourceFile) []*ast.Node, sourceFile *ast.SourceFile) []*lsproto.DocumentHighlight {
|
|
||||||
if nodeTest(node) {
|
|
||||||
return l.highlightSpans(getNodes(node, sourceFile), sourceFile)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) highlightSpans(nodes []*ast.Node, sourceFile *ast.SourceFile) []*lsproto.DocumentHighlight {
|
|
||||||
if len(nodes) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var highlights []*lsproto.DocumentHighlight
|
|
||||||
kind := lsproto.DocumentHighlightKindRead
|
|
||||||
for _, node := range nodes {
|
|
||||||
if node != nil {
|
|
||||||
highlights = append(highlights, &lsproto.DocumentHighlight{
|
|
||||||
Range: *l.createLspRangeFromNode(node, sourceFile),
|
|
||||||
Kind: &kind,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return highlights
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) getFromAllDeclarations(nodeTest func(*ast.Node) bool, keywords []ast.Kind, node *ast.Node, sourceFile *ast.SourceFile) []*lsproto.DocumentHighlight {
|
|
||||||
return l.useParent(node.Parent, nodeTest, func(decl *ast.Node, sf *ast.SourceFile) []*ast.Node {
|
|
||||||
var symbolDecls []*ast.Node
|
|
||||||
if ast.CanHaveSymbol(decl) {
|
|
||||||
if symbol := decl.Symbol(); symbol != nil {
|
|
||||||
for _, d := range symbol.Declarations {
|
|
||||||
if nodeTest(d) {
|
|
||||||
outer:
|
|
||||||
for _, c := range getChildrenFromNonJSDocNode(d, sourceFile) {
|
|
||||||
for _, k := range keywords {
|
|
||||||
if c.Kind == k {
|
|
||||||
symbolDecls = append(symbolDecls, c)
|
|
||||||
break outer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return symbolDecls
|
|
||||||
}, sourceFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) getIfElseOccurrences(ifStatement *ast.IfStatement, sourceFile *ast.SourceFile) []*lsproto.DocumentHighlight {
|
|
||||||
keywords := getIfElseKeywords(ifStatement, sourceFile)
|
|
||||||
kind := lsproto.DocumentHighlightKindRead
|
|
||||||
var highlights []*lsproto.DocumentHighlight
|
|
||||||
|
|
||||||
// We'd like to highlight else/ifs together if they are only separated by whitespace
|
|
||||||
// (i.e. the keywords are separated by no comments, no newlines).
|
|
||||||
for i := 0; i < len(keywords); i++ {
|
|
||||||
if keywords[i].Kind == ast.KindElseKeyword && i < len(keywords)-1 {
|
|
||||||
elseKeyword := keywords[i]
|
|
||||||
ifKeyword := keywords[i+1] // this *should* always be an 'if' keyword.
|
|
||||||
shouldCombine := true
|
|
||||||
|
|
||||||
// Avoid recalculating getStart() by iterating backwards.
|
|
||||||
ifTokenStart := scanner.GetTokenPosOfNode(ifKeyword, sourceFile, false)
|
|
||||||
if ifTokenStart < 0 {
|
|
||||||
ifTokenStart = ifKeyword.Pos()
|
|
||||||
}
|
|
||||||
for j := ifTokenStart - 1; j >= elseKeyword.End(); j-- {
|
|
||||||
if !stringutil.IsWhiteSpaceSingleLine(rune(sourceFile.Text()[j])) {
|
|
||||||
shouldCombine = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if shouldCombine {
|
|
||||||
highlights = append(highlights, &lsproto.DocumentHighlight{
|
|
||||||
Range: *l.createLspRangeFromBounds(scanner.SkipTrivia(sourceFile.Text(), elseKeyword.Pos()), ifKeyword.End(), sourceFile),
|
|
||||||
Kind: &kind,
|
|
||||||
})
|
|
||||||
i++ // skip the next keyword
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Ordinary case: just highlight the keyword.
|
|
||||||
highlights = append(highlights, &lsproto.DocumentHighlight{
|
|
||||||
Range: *l.createLspRangeFromNode(keywords[i], sourceFile),
|
|
||||||
Kind: &kind,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return highlights
|
|
||||||
}
|
|
||||||
|
|
||||||
func getIfElseKeywords(ifStatement *ast.IfStatement, sourceFile *ast.SourceFile) []*ast.Node {
|
|
||||||
// Traverse upwards through all parent if-statements linked by their else-branches.
|
|
||||||
// Is this cast error safe or should i be checking if elseStatement exists first?
|
|
||||||
for ast.IsIfStatement(ifStatement.Parent) && ifStatement.Parent.AsIfStatement().ElseStatement.AsIfStatement() == ifStatement {
|
|
||||||
ifStatement = ifStatement.Parent.AsIfStatement()
|
|
||||||
}
|
|
||||||
|
|
||||||
var keywords []*ast.Node
|
|
||||||
|
|
||||||
// Traverse back down through the else branches, aggregating if/else keywords of if-statements.
|
|
||||||
for {
|
|
||||||
children := getChildrenFromNonJSDocNode(ifStatement.AsNode(), sourceFile)
|
|
||||||
if len(children) > 0 && children[0].Kind == ast.KindIfKeyword {
|
|
||||||
keywords = append(keywords, children[0])
|
|
||||||
}
|
|
||||||
// Generally the 'else' keyword is second-to-last, so traverse backwards.
|
|
||||||
for i := len(children) - 1; i >= 0; i-- {
|
|
||||||
if children[i].Kind == ast.KindElseKeyword {
|
|
||||||
keywords = append(keywords, children[i])
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elseStatement := ifStatement.ElseStatement
|
|
||||||
if elseStatement == nil || !ast.IsIfStatement(elseStatement) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
ifStatement = elseStatement.AsIfStatement()
|
|
||||||
}
|
|
||||||
return keywords
|
|
||||||
}
|
|
||||||
|
|
||||||
func getReturnOccurrences(node *ast.Node, sourceFile *ast.SourceFile) []*ast.Node {
|
|
||||||
funcNode := ast.FindAncestor(node.Parent, ast.IsFunctionLike)
|
|
||||||
if funcNode == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var keywords []*ast.Node
|
|
||||||
body := funcNode.Body()
|
|
||||||
if body != nil {
|
|
||||||
ast.ForEachReturnStatement(body, func(ret *ast.Node) bool {
|
|
||||||
keyword := findChildOfKind(ret, ast.KindReturnKeyword, sourceFile)
|
|
||||||
if keyword != nil {
|
|
||||||
keywords = append(keywords, keyword)
|
|
||||||
}
|
|
||||||
return false // continue traversal
|
|
||||||
})
|
|
||||||
|
|
||||||
// Get all throw statements not in a try block
|
|
||||||
throwStatements := aggregateOwnedThrowStatements(body, sourceFile)
|
|
||||||
for _, throw := range throwStatements {
|
|
||||||
keyword := findChildOfKind(throw, ast.KindThrowKeyword, sourceFile)
|
|
||||||
if keyword != nil {
|
|
||||||
keywords = append(keywords, keyword)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return keywords
|
|
||||||
}
|
|
||||||
|
|
||||||
func aggregateOwnedThrowStatements(node *ast.Node, sourceFile *ast.SourceFile) []*ast.Node {
|
|
||||||
if ast.IsThrowStatement(node) {
|
|
||||||
return []*ast.Node{node}
|
|
||||||
}
|
|
||||||
if ast.IsTryStatement(node) {
|
|
||||||
// Exceptions thrown within a try block lacking a catch clause are "owned" in the current context.
|
|
||||||
statement := node.AsTryStatement()
|
|
||||||
tryBlock := statement.TryBlock
|
|
||||||
catchClause := statement.CatchClause
|
|
||||||
finallyBlock := statement.FinallyBlock
|
|
||||||
|
|
||||||
var result []*ast.Node
|
|
||||||
if catchClause != nil {
|
|
||||||
result = aggregateOwnedThrowStatements(catchClause, sourceFile)
|
|
||||||
} else if tryBlock != nil {
|
|
||||||
result = aggregateOwnedThrowStatements(tryBlock, sourceFile)
|
|
||||||
}
|
|
||||||
if finallyBlock != nil {
|
|
||||||
result = append(result, aggregateOwnedThrowStatements(finallyBlock, sourceFile)...)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
// Do not cross function boundaries.
|
|
||||||
if ast.IsFunctionLike(node) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return flatMapChildren(node, sourceFile, aggregateOwnedThrowStatements)
|
|
||||||
}
|
|
||||||
|
|
||||||
func flatMapChildren[T any](node *ast.Node, sourceFile *ast.SourceFile, cb func(child *ast.Node, sourceFile *ast.SourceFile) []T) []T {
|
|
||||||
var result []T
|
|
||||||
|
|
||||||
node.ForEachChild(func(child *ast.Node) bool {
|
|
||||||
value := cb(child, sourceFile)
|
|
||||||
if value != nil {
|
|
||||||
result = append(result, value...)
|
|
||||||
}
|
|
||||||
return false // continue traversal
|
|
||||||
})
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func getThrowOccurrences(node *ast.Node, sourceFile *ast.SourceFile) []*ast.Node {
|
|
||||||
owner := getThrowStatementOwner(node)
|
|
||||||
if owner == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var keywords []*ast.Node
|
|
||||||
|
|
||||||
// Aggregate all throw statements "owned" by this owner.
|
|
||||||
throwStatements := aggregateOwnedThrowStatements(owner, sourceFile)
|
|
||||||
for _, throw := range throwStatements {
|
|
||||||
keyword := findChildOfKind(throw, ast.KindThrowKeyword, sourceFile)
|
|
||||||
if keyword != nil {
|
|
||||||
keywords = append(keywords, keyword)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the "owner" is a function, then we equate 'return' and 'throw' statements in their
|
|
||||||
// ability to "jump out" of the function, and include occurrences for both
|
|
||||||
if ast.IsFunctionBlock(owner) {
|
|
||||||
ast.ForEachReturnStatement(owner, func(ret *ast.Node) bool {
|
|
||||||
keyword := findChildOfKind(ret, ast.KindReturnKeyword, sourceFile)
|
|
||||||
if keyword != nil {
|
|
||||||
keywords = append(keywords, keyword)
|
|
||||||
}
|
|
||||||
return false // continue traversal
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return keywords
|
|
||||||
}
|
|
||||||
|
|
||||||
// For lack of a better name, this function takes a throw statement and returns the
|
|
||||||
// nearest ancestor that is a try-block (whose try statement has a catch clause),
|
|
||||||
// function-block, or source file.
|
|
||||||
func getThrowStatementOwner(throwStatement *ast.Node) *ast.Node {
|
|
||||||
child := throwStatement
|
|
||||||
for child.Parent != nil {
|
|
||||||
parent := child.Parent
|
|
||||||
|
|
||||||
if ast.IsFunctionBlock(parent) || parent.Kind == ast.KindSourceFile {
|
|
||||||
return parent
|
|
||||||
}
|
|
||||||
|
|
||||||
// A throw-statement is only owned by a try-statement if the try-statement has
|
|
||||||
// a catch clause, and if the throw-statement occurs within the try block.
|
|
||||||
if ast.IsTryStatement(parent) {
|
|
||||||
tryStatement := parent.AsTryStatement()
|
|
||||||
if tryStatement.TryBlock == child && tryStatement.CatchClause != nil {
|
|
||||||
return child
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
child = parent
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTryCatchFinallyOccurrences(node *ast.Node, sourceFile *ast.SourceFile) []*ast.Node {
|
|
||||||
tryStatement := node.AsTryStatement()
|
|
||||||
|
|
||||||
var keywords []*ast.Node
|
|
||||||
token := lsutil.GetFirstToken(node, sourceFile)
|
|
||||||
if token.Kind == ast.KindTryKeyword {
|
|
||||||
keywords = append(keywords, token)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tryStatement.CatchClause != nil {
|
|
||||||
catchToken := lsutil.GetFirstToken(tryStatement.CatchClause.AsNode(), sourceFile)
|
|
||||||
if catchToken.Kind == ast.KindCatchKeyword {
|
|
||||||
keywords = append(keywords, catchToken)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tryStatement.FinallyBlock != nil {
|
|
||||||
finallyKeyword := findChildOfKind(node, ast.KindFinallyKeyword, sourceFile)
|
|
||||||
if finallyKeyword.Kind == ast.KindFinallyKeyword {
|
|
||||||
keywords = append(keywords, finallyKeyword)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return keywords
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSwitchCaseDefaultOccurrences(node *ast.Node, sourceFile *ast.SourceFile) []*ast.Node {
|
|
||||||
switchStatement := node.AsSwitchStatement()
|
|
||||||
|
|
||||||
var keywords []*ast.Node
|
|
||||||
token := lsutil.GetFirstToken(node, sourceFile)
|
|
||||||
if token.Kind == ast.KindSwitchKeyword {
|
|
||||||
keywords = append(keywords, token)
|
|
||||||
}
|
|
||||||
|
|
||||||
clauses := switchStatement.CaseBlock.AsCaseBlock().Clauses
|
|
||||||
for _, clause := range clauses.Nodes {
|
|
||||||
clauseToken := lsutil.GetFirstToken(clause.AsNode(), sourceFile)
|
|
||||||
if clauseToken.Kind == ast.KindCaseKeyword || clauseToken.Kind == ast.KindDefaultKeyword {
|
|
||||||
keywords = append(keywords, clauseToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
breakAndContinueStatements := aggregateAllBreakAndContinueStatements(clause, sourceFile)
|
|
||||||
for _, statement := range breakAndContinueStatements {
|
|
||||||
if statement.Kind == ast.KindBreakStatement && ownsBreakOrContinueStatement(switchStatement.AsNode(), statement) {
|
|
||||||
keywords = append(keywords, lsutil.GetFirstToken(statement, sourceFile))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return keywords
|
|
||||||
}
|
|
||||||
|
|
||||||
func aggregateAllBreakAndContinueStatements(node *ast.Node, sourceFile *ast.SourceFile) []*ast.Node {
|
|
||||||
if ast.IsBreakOrContinueStatement(node) {
|
|
||||||
return []*ast.Node{node}
|
|
||||||
}
|
|
||||||
if ast.IsFunctionLike(node) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return flatMapChildren(node, sourceFile, aggregateAllBreakAndContinueStatements)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ownsBreakOrContinueStatement(owner *ast.Node, statement *ast.Node) bool {
|
|
||||||
actualOwner := getBreakOrContinueOwner(statement)
|
|
||||||
if actualOwner == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return actualOwner == owner
|
|
||||||
}
|
|
||||||
|
|
||||||
func getBreakOrContinueOwner(statement *ast.Node) *ast.Node {
|
|
||||||
return ast.FindAncestorOrQuit(statement, func(node *ast.Node) ast.FindAncestorResult {
|
|
||||||
switch node.Kind {
|
|
||||||
case ast.KindSwitchStatement:
|
|
||||||
if statement.Kind == ast.KindContinueStatement {
|
|
||||||
return ast.FindAncestorFalse
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
case ast.KindForStatement,
|
|
||||||
ast.KindForInStatement,
|
|
||||||
ast.KindForOfStatement,
|
|
||||||
ast.KindWhileStatement,
|
|
||||||
ast.KindDoStatement:
|
|
||||||
// If the statement is labeled, check if the node is labeled by the statement's label.
|
|
||||||
if statement.Label() == nil || isLabeledBy(node, statement.Label().Text()) {
|
|
||||||
return ast.FindAncestorTrue
|
|
||||||
}
|
|
||||||
return ast.FindAncestorFalse
|
|
||||||
default:
|
|
||||||
// Don't cross function boundaries.
|
|
||||||
if ast.IsFunctionLike(node) {
|
|
||||||
return ast.FindAncestorQuit
|
|
||||||
}
|
|
||||||
return ast.FindAncestorFalse
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Whether or not a 'node' is preceded by a label of the given string.
|
|
||||||
// Note: 'node' cannot be a SourceFile.
|
|
||||||
func isLabeledBy(node *ast.Node, labelName string) bool {
|
|
||||||
return ast.FindAncestorOrQuit(node.Parent, func(owner *ast.Node) ast.FindAncestorResult {
|
|
||||||
if !ast.IsLabeledStatement(owner) {
|
|
||||||
return ast.FindAncestorQuit
|
|
||||||
}
|
|
||||||
if owner.Label().Text() == labelName {
|
|
||||||
return ast.FindAncestorTrue
|
|
||||||
}
|
|
||||||
return ast.FindAncestorFalse
|
|
||||||
}) != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getBreakOrContinueStatementOccurrences(node *ast.Node, sourceFile *ast.SourceFile) []*ast.Node {
|
|
||||||
if owner := getBreakOrContinueOwner(node); owner != nil {
|
|
||||||
switch owner.Kind {
|
|
||||||
case ast.KindForStatement, ast.KindForInStatement, ast.KindForOfStatement, ast.KindDoStatement, ast.KindWhileStatement:
|
|
||||||
return getLoopBreakContinueOccurrences(owner, sourceFile)
|
|
||||||
case ast.KindSwitchStatement:
|
|
||||||
return getSwitchCaseDefaultOccurrences(owner, sourceFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getLoopBreakContinueOccurrences(node *ast.Node, sourceFile *ast.SourceFile) []*ast.Node {
|
|
||||||
var keywords []*ast.Node
|
|
||||||
|
|
||||||
token := lsutil.GetFirstToken(node, sourceFile)
|
|
||||||
if token.Kind == ast.KindForKeyword || token.Kind == ast.KindDoKeyword || token.Kind == ast.KindWhileKeyword {
|
|
||||||
keywords = append(keywords, token)
|
|
||||||
if node.Kind == ast.KindDoStatement {
|
|
||||||
loopTokens := getChildrenFromNonJSDocNode(node, sourceFile)
|
|
||||||
for i := len(loopTokens) - 1; i >= 0; i-- {
|
|
||||||
if loopTokens[i].Kind == ast.KindWhileKeyword {
|
|
||||||
keywords = append(keywords, loopTokens[i])
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
breakAndContinueStatements := aggregateAllBreakAndContinueStatements(node, sourceFile)
|
|
||||||
for _, statement := range breakAndContinueStatements {
|
|
||||||
token := lsutil.GetFirstToken(statement, sourceFile)
|
|
||||||
if ownsBreakOrContinueStatement(node, statement) && (token.Kind == ast.KindBreakKeyword || token.Kind == ast.KindContinueKeyword) {
|
|
||||||
keywords = append(keywords, token)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return keywords
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAsyncAndAwaitOccurrences(node *ast.Node, sourceFile *ast.SourceFile) []*ast.Node {
|
|
||||||
fun := ast.GetContainingFunction(node)
|
|
||||||
if fun == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var keywords []*ast.Node
|
|
||||||
|
|
||||||
modifiers := fun.Modifiers()
|
|
||||||
if modifiers != nil {
|
|
||||||
for _, modifier := range modifiers.Nodes {
|
|
||||||
if modifier.Kind == ast.KindAsyncKeyword {
|
|
||||||
keywords = append(keywords, modifier)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun.ForEachChild(func(child *ast.Node) bool {
|
|
||||||
traverseWithoutCrossingFunction(child, sourceFile, func(child *ast.Node) {
|
|
||||||
if ast.IsAwaitExpression(child) {
|
|
||||||
token := lsutil.GetFirstToken(child, sourceFile)
|
|
||||||
if token.Kind == ast.KindAwaitKeyword {
|
|
||||||
keywords = append(keywords, token)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return false // continue traversal
|
|
||||||
})
|
|
||||||
|
|
||||||
return keywords
|
|
||||||
}
|
|
||||||
|
|
||||||
func getYieldOccurrences(node *ast.Node, sourceFile *ast.SourceFile) []*ast.Node {
|
|
||||||
parentFunc := ast.FindAncestor(node.Parent, ast.IsFunctionLike).AsFunctionDeclaration()
|
|
||||||
if parentFunc == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var keywords []*ast.Node
|
|
||||||
|
|
||||||
parentFunc.ForEachChild(func(child *ast.Node) bool {
|
|
||||||
traverseWithoutCrossingFunction(child, sourceFile, func(child *ast.Node) {
|
|
||||||
if ast.IsYieldExpression(child) {
|
|
||||||
token := lsutil.GetFirstToken(child, sourceFile)
|
|
||||||
if token.Kind == ast.KindYieldKeyword {
|
|
||||||
keywords = append(keywords, token)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return false // continue traversal
|
|
||||||
})
|
|
||||||
|
|
||||||
return keywords
|
|
||||||
}
|
|
||||||
|
|
||||||
func traverseWithoutCrossingFunction(node *ast.Node, sourceFile *ast.SourceFile, cb func(*ast.Node)) {
|
|
||||||
cb(node)
|
|
||||||
if !ast.IsFunctionLike(node) && !ast.IsClassLike(node) && !ast.IsInterfaceDeclaration(node) && !ast.IsModuleDeclaration(node) && !ast.IsTypeAliasDeclaration(node) && !ast.IsTypeNode(node) {
|
|
||||||
node.ForEachChild(func(child *ast.Node) bool {
|
|
||||||
traverseWithoutCrossingFunction(child, sourceFile, cb)
|
|
||||||
return false // continue traversal
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getModifierOccurrences(kind ast.Kind, node *ast.Node, sourceFile *ast.SourceFile) []*ast.Node {
|
|
||||||
var result []*ast.Node
|
|
||||||
|
|
||||||
nodesToSearch := getNodesToSearchForModifier(node, ast.ModifierToFlag(kind))
|
|
||||||
for _, n := range nodesToSearch {
|
|
||||||
modifier := findModifier(n, kind)
|
|
||||||
if modifier != nil {
|
|
||||||
result = append(result, modifier)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNodesToSearchForModifier(declaration *ast.Node, modifierFlag ast.ModifierFlags) []*ast.Node {
|
|
||||||
var result []*ast.Node
|
|
||||||
|
|
||||||
container := declaration.Parent
|
|
||||||
if container == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Types of node whose children might have modifiers.
|
|
||||||
switch container.Kind {
|
|
||||||
case ast.KindModuleBlock, ast.KindSourceFile, ast.KindBlock, ast.KindCaseClause, ast.KindDefaultClause:
|
|
||||||
// Container is either a class declaration or the declaration is a classDeclaration
|
|
||||||
if (modifierFlag&ast.ModifierFlagsAbstract) != 0 && ast.IsClassDeclaration(declaration) {
|
|
||||||
return append(append(result, declaration.Members()...), declaration)
|
|
||||||
} else {
|
|
||||||
return append(result, container.Statements()...)
|
|
||||||
}
|
|
||||||
case ast.KindConstructor, ast.KindMethodDeclaration, ast.KindFunctionDeclaration:
|
|
||||||
// Parameters and, if inside a class, also class members
|
|
||||||
result = append(result, container.Parameters()...)
|
|
||||||
if ast.IsClassLike(container.Parent) {
|
|
||||||
result = append(result, container.Parent.Members()...)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
case ast.KindClassDeclaration, ast.KindClassExpression, ast.KindInterfaceDeclaration, ast.KindTypeLiteral:
|
|
||||||
nodes := container.Members()
|
|
||||||
result = append(result, nodes...)
|
|
||||||
// If we're an accessibility modifier, we're in an instance member and should search
|
|
||||||
// the constructor's parameter list for instance members as well.
|
|
||||||
if (modifierFlag & (ast.ModifierFlagsAccessibilityModifier | ast.ModifierFlagsReadonly)) != 0 {
|
|
||||||
var constructor *ast.Node
|
|
||||||
|
|
||||||
for _, member := range nodes {
|
|
||||||
if ast.IsConstructorDeclaration(member) {
|
|
||||||
constructor = member
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if constructor != nil {
|
|
||||||
result = append(result, constructor.Parameters()...)
|
|
||||||
}
|
|
||||||
} else if (modifierFlag & ast.ModifierFlagsAbstract) != 0 {
|
|
||||||
result = append(result, container)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
default:
|
|
||||||
// Syntactically invalid positions or unsupported containers
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func findModifier(node *ast.Node, kind ast.Kind) *ast.Node {
|
|
||||||
if modifiers := node.Modifiers(); modifiers != nil {
|
|
||||||
for _, modifier := range modifiers.Nodes {
|
|
||||||
if modifier.Kind == kind {
|
|
||||||
return modifier
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,181 +0,0 @@
|
|||||||
package ls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"iter"
|
|
||||||
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/astnav"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/format"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/scanner"
|
|
||||||
)
|
|
||||||
|
|
||||||
func toFormatCodeSettings(opt *lsproto.FormattingOptions, newLine string) *format.FormatCodeSettings {
|
|
||||||
initial := format.GetDefaultFormatCodeSettings(newLine)
|
|
||||||
initial.TabSize = int(opt.TabSize)
|
|
||||||
initial.IndentSize = int(opt.TabSize)
|
|
||||||
initial.ConvertTabsToSpaces = opt.InsertSpaces
|
|
||||||
if opt.TrimTrailingWhitespace != nil {
|
|
||||||
initial.TrimTrailingWhitespace = *opt.TrimTrailingWhitespace
|
|
||||||
}
|
|
||||||
|
|
||||||
// !!! get format settings
|
|
||||||
// TODO: We support a _lot_ more options than this
|
|
||||||
return initial
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) toLSProtoTextEdits(file *ast.SourceFile, changes []core.TextChange) []*lsproto.TextEdit {
|
|
||||||
result := make([]*lsproto.TextEdit, 0, len(changes))
|
|
||||||
for _, c := range changes {
|
|
||||||
result = append(result, &lsproto.TextEdit{
|
|
||||||
NewText: c.NewText,
|
|
||||||
Range: *l.createLspRangeFromBounds(c.Pos(), c.End(), file),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) ProvideFormatDocument(
|
|
||||||
ctx context.Context,
|
|
||||||
documentURI lsproto.DocumentUri,
|
|
||||||
options *lsproto.FormattingOptions,
|
|
||||||
) (lsproto.DocumentFormattingResponse, error) {
|
|
||||||
_, file := l.getProgramAndFile(documentURI)
|
|
||||||
edits := l.toLSProtoTextEdits(file, l.getFormattingEditsForDocument(
|
|
||||||
ctx,
|
|
||||||
file,
|
|
||||||
toFormatCodeSettings(options, l.GetProgram().Options().NewLine.GetNewLineCharacter()),
|
|
||||||
))
|
|
||||||
return lsproto.TextEditsOrNull{TextEdits: &edits}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) ProvideFormatDocumentRange(
|
|
||||||
ctx context.Context,
|
|
||||||
documentURI lsproto.DocumentUri,
|
|
||||||
options *lsproto.FormattingOptions,
|
|
||||||
r lsproto.Range,
|
|
||||||
) (lsproto.DocumentRangeFormattingResponse, error) {
|
|
||||||
_, file := l.getProgramAndFile(documentURI)
|
|
||||||
edits := l.toLSProtoTextEdits(file, l.getFormattingEditsForRange(
|
|
||||||
ctx,
|
|
||||||
file,
|
|
||||||
toFormatCodeSettings(options, l.GetProgram().Options().NewLine.GetNewLineCharacter()),
|
|
||||||
l.converters.FromLSPRange(file, r),
|
|
||||||
))
|
|
||||||
return lsproto.TextEditsOrNull{TextEdits: &edits}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) ProvideFormatDocumentOnType(
|
|
||||||
ctx context.Context,
|
|
||||||
documentURI lsproto.DocumentUri,
|
|
||||||
options *lsproto.FormattingOptions,
|
|
||||||
position lsproto.Position,
|
|
||||||
character string,
|
|
||||||
) (lsproto.DocumentOnTypeFormattingResponse, error) {
|
|
||||||
_, file := l.getProgramAndFile(documentURI)
|
|
||||||
edits := l.toLSProtoTextEdits(file, l.getFormattingEditsAfterKeystroke(
|
|
||||||
ctx,
|
|
||||||
file,
|
|
||||||
toFormatCodeSettings(options, l.GetProgram().Options().NewLine.GetNewLineCharacter()),
|
|
||||||
int(l.converters.LineAndCharacterToPosition(file, position)),
|
|
||||||
character,
|
|
||||||
))
|
|
||||||
return lsproto.TextEditsOrNull{TextEdits: &edits}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) getFormattingEditsForRange(
|
|
||||||
ctx context.Context,
|
|
||||||
file *ast.SourceFile,
|
|
||||||
options *format.FormatCodeSettings,
|
|
||||||
r core.TextRange,
|
|
||||||
) []core.TextChange {
|
|
||||||
ctx = format.WithFormatCodeSettings(ctx, options, options.NewLineCharacter)
|
|
||||||
return format.FormatSelection(ctx, file, r.Pos(), r.End())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) getFormattingEditsForDocument(
|
|
||||||
ctx context.Context,
|
|
||||||
file *ast.SourceFile,
|
|
||||||
options *format.FormatCodeSettings,
|
|
||||||
) []core.TextChange {
|
|
||||||
ctx = format.WithFormatCodeSettings(ctx, options, options.NewLineCharacter)
|
|
||||||
return format.FormatDocument(ctx, file)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) getFormattingEditsAfterKeystroke(
|
|
||||||
ctx context.Context,
|
|
||||||
file *ast.SourceFile,
|
|
||||||
options *format.FormatCodeSettings,
|
|
||||||
position int,
|
|
||||||
key string,
|
|
||||||
) []core.TextChange {
|
|
||||||
ctx = format.WithFormatCodeSettings(ctx, options, options.NewLineCharacter)
|
|
||||||
|
|
||||||
if isInComment(file, position, nil) == nil {
|
|
||||||
switch key {
|
|
||||||
case "{":
|
|
||||||
return format.FormatOnOpeningCurly(ctx, file, position)
|
|
||||||
case "}":
|
|
||||||
return format.FormatOnClosingCurly(ctx, file, position)
|
|
||||||
case ";":
|
|
||||||
return format.FormatOnSemicolon(ctx, file, position)
|
|
||||||
case "\n":
|
|
||||||
return format.FormatOnEnter(ctx, file, position)
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unlike the TS implementation, this function *will not* compute default values for
|
|
||||||
// `precedingToken` and `tokenAtPosition`.
|
|
||||||
// It is the caller's responsibility to call `astnav.GetTokenAtPosition` to compute a default `tokenAtPosition`,
|
|
||||||
// or `astnav.FindPrecedingToken` to compute a default `precedingToken`.
|
|
||||||
func getRangeOfEnclosingComment(
|
|
||||||
file *ast.SourceFile,
|
|
||||||
position int,
|
|
||||||
precedingToken *ast.Node,
|
|
||||||
tokenAtPosition *ast.Node,
|
|
||||||
) *ast.CommentRange {
|
|
||||||
jsdoc := ast.FindAncestor(tokenAtPosition, (*ast.Node).IsJSDoc)
|
|
||||||
if jsdoc != nil {
|
|
||||||
tokenAtPosition = jsdoc.Parent
|
|
||||||
}
|
|
||||||
tokenStart := astnav.GetStartOfNode(tokenAtPosition, file, false /*includeJSDoc*/)
|
|
||||||
if tokenStart <= position && position < tokenAtPosition.End() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Between two consecutive tokens, all comments are either trailing on the former
|
|
||||||
// or leading on the latter (and none are in both lists).
|
|
||||||
var trailingRangesOfPreviousToken iter.Seq[ast.CommentRange]
|
|
||||||
if precedingToken != nil {
|
|
||||||
trailingRangesOfPreviousToken = scanner.GetTrailingCommentRanges(&ast.NodeFactory{}, file.Text(), precedingToken.End())
|
|
||||||
}
|
|
||||||
leadingRangesOfNextToken := getLeadingCommentRangesOfNode(tokenAtPosition, file)
|
|
||||||
commentRanges := core.ConcatenateSeq(trailingRangesOfPreviousToken, leadingRangesOfNextToken)
|
|
||||||
for commentRange := range commentRanges {
|
|
||||||
// The end marker of a single-line comment does not include the newline character.
|
|
||||||
// In the following case where the cursor is at `^`, we are inside a comment:
|
|
||||||
//
|
|
||||||
// // asdf ^\n
|
|
||||||
//
|
|
||||||
// But for closed multi-line comments, we don't want to be inside the comment in the following case:
|
|
||||||
//
|
|
||||||
// /* asdf */^
|
|
||||||
//
|
|
||||||
// Internally, we represent the end of the comment prior to the newline and at the '/', respectively.
|
|
||||||
//
|
|
||||||
// However, unterminated multi-line comments lack a `/`, end at the end of the file, and *do* contain their end.
|
|
||||||
//
|
|
||||||
if commentRange.ContainsExclusive(position) ||
|
|
||||||
position == commentRange.End() &&
|
|
||||||
(commentRange.Kind == ast.KindSingleLineCommentTrivia || position == len(file.Text())) {
|
|
||||||
return &commentRange
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
package ls
|
|
||||||
|
|
||||||
import "efprojects.com/kitten-ipc/kitcom/internal/tsgo/sourcemap"
|
|
||||||
|
|
||||||
type Host interface {
|
|
||||||
UseCaseSensitiveFileNames() bool
|
|
||||||
ReadFile(path string) (contents string, ok bool)
|
|
||||||
Converters() *Converters
|
|
||||||
GetECMALineInfo(fileName string) *sourcemap.ECMALineInfo
|
|
||||||
}
|
|
||||||
@ -1,488 +0,0 @@
|
|||||||
package ls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/astnav"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/checker"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
symbolFormatFlags = checker.SymbolFormatFlagsWriteTypeParametersOrArguments | checker.SymbolFormatFlagsUseOnlyExternalAliasing | checker.SymbolFormatFlagsAllowAnyNodeKind | checker.SymbolFormatFlagsUseAliasDefinedOutsideCurrentScope
|
|
||||||
typeFormatFlags = checker.TypeFormatFlagsUseAliasDefinedOutsideCurrentScope
|
|
||||||
)
|
|
||||||
|
|
||||||
func (l *LanguageService) ProvideHover(ctx context.Context, documentURI lsproto.DocumentUri, position lsproto.Position) (lsproto.HoverResponse, error) {
|
|
||||||
program, file := l.getProgramAndFile(documentURI)
|
|
||||||
node := astnav.GetTouchingPropertyName(file, int(l.converters.LineAndCharacterToPosition(file, position)))
|
|
||||||
if node.Kind == ast.KindSourceFile {
|
|
||||||
// Avoid giving quickInfo for the sourceFile as a whole.
|
|
||||||
return lsproto.HoverOrNull{}, nil
|
|
||||||
}
|
|
||||||
c, done := program.GetTypeCheckerForFile(ctx, file)
|
|
||||||
defer done()
|
|
||||||
quickInfo, documentation := getQuickInfoAndDocumentation(c, node)
|
|
||||||
if quickInfo == "" {
|
|
||||||
return lsproto.HoverOrNull{}, nil
|
|
||||||
}
|
|
||||||
return lsproto.HoverOrNull{
|
|
||||||
Hover: &lsproto.Hover{
|
|
||||||
Contents: lsproto.MarkupContentOrStringOrMarkedStringWithLanguageOrMarkedStrings{
|
|
||||||
MarkupContent: &lsproto.MarkupContent{
|
|
||||||
Kind: lsproto.MarkupKindMarkdown,
|
|
||||||
Value: formatQuickInfo(quickInfo) + documentation,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getQuickInfoAndDocumentation(c *checker.Checker, node *ast.Node) (string, string) {
|
|
||||||
return getQuickInfoAndDocumentationForSymbol(c, c.GetSymbolAtLocation(node), getNodeForQuickInfo(node))
|
|
||||||
}
|
|
||||||
|
|
||||||
func getQuickInfoAndDocumentationForSymbol(c *checker.Checker, symbol *ast.Symbol, node *ast.Node) (string, string) {
|
|
||||||
quickInfo, declaration := getQuickInfoAndDeclarationAtLocation(c, symbol, node)
|
|
||||||
if quickInfo == "" {
|
|
||||||
return "", ""
|
|
||||||
}
|
|
||||||
var b strings.Builder
|
|
||||||
if declaration != nil {
|
|
||||||
if jsdoc := getJSDocOrTag(declaration); jsdoc != nil && !containsTypedefTag(jsdoc) {
|
|
||||||
writeComments(&b, jsdoc.Comments())
|
|
||||||
if jsdoc.Kind == ast.KindJSDoc {
|
|
||||||
if tags := jsdoc.AsJSDoc().Tags; tags != nil {
|
|
||||||
for _, tag := range tags.Nodes {
|
|
||||||
if tag.Kind == ast.KindJSDocTypeTag {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
b.WriteString("\n\n*@")
|
|
||||||
b.WriteString(tag.TagName().Text())
|
|
||||||
b.WriteString("*")
|
|
||||||
switch tag.Kind {
|
|
||||||
case ast.KindJSDocParameterTag, ast.KindJSDocPropertyTag:
|
|
||||||
writeOptionalEntityName(&b, tag.Name())
|
|
||||||
case ast.KindJSDocAugmentsTag:
|
|
||||||
writeOptionalEntityName(&b, tag.AsJSDocAugmentsTag().ClassName)
|
|
||||||
case ast.KindJSDocSeeTag:
|
|
||||||
writeOptionalEntityName(&b, tag.AsJSDocSeeTag().NameExpression)
|
|
||||||
case ast.KindJSDocTemplateTag:
|
|
||||||
for i, tp := range tag.TypeParameters() {
|
|
||||||
if i != 0 {
|
|
||||||
b.WriteString(",")
|
|
||||||
}
|
|
||||||
writeOptionalEntityName(&b, tp.Name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
comments := tag.Comments()
|
|
||||||
if len(comments) != 0 {
|
|
||||||
if commentHasPrefix(comments, "```") {
|
|
||||||
b.WriteString("\n")
|
|
||||||
} else {
|
|
||||||
b.WriteString(" ")
|
|
||||||
if !commentHasPrefix(comments, "-") {
|
|
||||||
b.WriteString("— ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writeComments(&b, comments)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return quickInfo, b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatQuickInfo(quickInfo string) string {
|
|
||||||
var b strings.Builder
|
|
||||||
b.Grow(32)
|
|
||||||
writeCode(&b, "tsx", quickInfo)
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getQuickInfoAndDeclarationAtLocation(c *checker.Checker, symbol *ast.Symbol, node *ast.Node) (string, *ast.Node) {
|
|
||||||
isAlias := symbol != nil && symbol.Flags&ast.SymbolFlagsAlias != 0
|
|
||||||
if isAlias {
|
|
||||||
symbol = c.GetAliasedSymbol(symbol)
|
|
||||||
}
|
|
||||||
if symbol == nil || symbol == c.GetUnknownSymbol() {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
declaration := symbol.ValueDeclaration
|
|
||||||
if symbol.Flags&ast.SymbolFlagsClass != 0 && inConstructorContext(node) {
|
|
||||||
if s := symbol.Members[ast.InternalSymbolNameConstructor]; s != nil {
|
|
||||||
symbol = s
|
|
||||||
declaration = core.Find(symbol.Declarations, func(d *ast.Node) bool {
|
|
||||||
return ast.IsConstructorDeclaration(d) || ast.IsConstructSignatureDeclaration(d)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
flags := symbol.Flags
|
|
||||||
if flags&ast.SymbolFlagsType != 0 && (ast.IsPartOfTypeNode(node) || ast.IsTypeDeclarationName(node)) {
|
|
||||||
// If the symbol has a type meaning and we're in a type context, remove value-only meanings
|
|
||||||
flags &^= ast.SymbolFlagsVariable | ast.SymbolFlagsFunction
|
|
||||||
}
|
|
||||||
container := getContainerNode(node)
|
|
||||||
var b strings.Builder
|
|
||||||
if isAlias {
|
|
||||||
b.WriteString("(alias) ")
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case flags&(ast.SymbolFlagsVariable|ast.SymbolFlagsProperty|ast.SymbolFlagsAccessor) != 0:
|
|
||||||
switch {
|
|
||||||
case flags&ast.SymbolFlagsProperty != 0:
|
|
||||||
b.WriteString("(property) ")
|
|
||||||
case flags&ast.SymbolFlagsAccessor != 0:
|
|
||||||
b.WriteString("(accessor) ")
|
|
||||||
default:
|
|
||||||
decl := symbol.ValueDeclaration
|
|
||||||
if decl != nil {
|
|
||||||
switch {
|
|
||||||
case ast.IsParameter(decl):
|
|
||||||
b.WriteString("(parameter) ")
|
|
||||||
case ast.IsVarLet(decl):
|
|
||||||
b.WriteString("let ")
|
|
||||||
case ast.IsVarConst(decl):
|
|
||||||
b.WriteString("const ")
|
|
||||||
case ast.IsVarUsing(decl):
|
|
||||||
b.WriteString("using ")
|
|
||||||
case ast.IsVarAwaitUsing(decl):
|
|
||||||
b.WriteString("await using ")
|
|
||||||
default:
|
|
||||||
b.WriteString("var ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b.WriteString(c.SymbolToStringEx(symbol, container, ast.SymbolFlagsNone, symbolFormatFlags))
|
|
||||||
b.WriteString(": ")
|
|
||||||
b.WriteString(c.TypeToStringEx(c.GetTypeOfSymbolAtLocation(symbol, node), container, typeFormatFlags))
|
|
||||||
case flags&ast.SymbolFlagsEnumMember != 0:
|
|
||||||
b.WriteString("(enum member) ")
|
|
||||||
t := c.GetTypeOfSymbol(symbol)
|
|
||||||
b.WriteString(c.TypeToStringEx(t, container, typeFormatFlags))
|
|
||||||
if t.Flags()&checker.TypeFlagsLiteral != 0 {
|
|
||||||
b.WriteString(" = ")
|
|
||||||
b.WriteString(t.AsLiteralType().String())
|
|
||||||
}
|
|
||||||
case flags&(ast.SymbolFlagsFunction|ast.SymbolFlagsMethod) != 0:
|
|
||||||
signatures := getSignaturesAtLocation(c, symbol, checker.SignatureKindCall, node)
|
|
||||||
if len(signatures) == 1 && signatures[0].Declaration() != nil {
|
|
||||||
declaration = signatures[0].Declaration()
|
|
||||||
}
|
|
||||||
prefix := core.IfElse(symbol.Flags&ast.SymbolFlagsMethod != 0, "(method) ", "function ")
|
|
||||||
writeSignatures(&b, c, signatures, container, prefix, symbol)
|
|
||||||
case flags&ast.SymbolFlagsConstructor != 0:
|
|
||||||
signatures := getSignaturesAtLocation(c, symbol.Parent, checker.SignatureKindConstruct, node)
|
|
||||||
if len(signatures) == 1 && signatures[0].Declaration() != nil {
|
|
||||||
declaration = signatures[0].Declaration()
|
|
||||||
}
|
|
||||||
writeSignatures(&b, c, signatures, container, "constructor ", symbol.Parent)
|
|
||||||
case flags&(ast.SymbolFlagsClass|ast.SymbolFlagsInterface) != 0:
|
|
||||||
if node.Kind == ast.KindThisKeyword || ast.IsThisInTypeQuery(node) {
|
|
||||||
b.WriteString("this")
|
|
||||||
} else {
|
|
||||||
b.WriteString(core.IfElse(symbol.Flags&ast.SymbolFlagsClass != 0, "class ", "interface "))
|
|
||||||
b.WriteString(c.SymbolToStringEx(symbol, container, ast.SymbolFlagsNone, symbolFormatFlags))
|
|
||||||
params := c.GetDeclaredTypeOfSymbol(symbol).AsInterfaceType().LocalTypeParameters()
|
|
||||||
writeTypeParams(&b, c, params)
|
|
||||||
}
|
|
||||||
if flags&ast.SymbolFlagsInterface != 0 {
|
|
||||||
declaration = core.Find(symbol.Declarations, ast.IsInterfaceDeclaration)
|
|
||||||
}
|
|
||||||
case flags&ast.SymbolFlagsEnum != 0:
|
|
||||||
b.WriteString("enum ")
|
|
||||||
b.WriteString(c.SymbolToStringEx(symbol, container, ast.SymbolFlagsNone, symbolFormatFlags))
|
|
||||||
case flags&ast.SymbolFlagsModule != 0:
|
|
||||||
b.WriteString(core.IfElse(symbol.ValueDeclaration != nil && ast.IsSourceFile(symbol.ValueDeclaration), "module ", "namespace "))
|
|
||||||
b.WriteString(c.SymbolToStringEx(symbol, container, ast.SymbolFlagsNone, symbolFormatFlags))
|
|
||||||
case flags&ast.SymbolFlagsTypeParameter != 0:
|
|
||||||
b.WriteString("(type parameter) ")
|
|
||||||
tp := c.GetDeclaredTypeOfSymbol(symbol)
|
|
||||||
b.WriteString(c.SymbolToStringEx(symbol, container, ast.SymbolFlagsNone, symbolFormatFlags))
|
|
||||||
cons := c.GetConstraintOfTypeParameter(tp)
|
|
||||||
if cons != nil {
|
|
||||||
b.WriteString(" extends ")
|
|
||||||
b.WriteString(c.TypeToStringEx(cons, container, typeFormatFlags))
|
|
||||||
}
|
|
||||||
declaration = core.Find(symbol.Declarations, ast.IsTypeParameterDeclaration)
|
|
||||||
case flags&ast.SymbolFlagsTypeAlias != 0:
|
|
||||||
b.WriteString("type ")
|
|
||||||
b.WriteString(c.SymbolToStringEx(symbol, container, ast.SymbolFlagsNone, symbolFormatFlags))
|
|
||||||
writeTypeParams(&b, c, c.GetTypeAliasTypeParameters(symbol))
|
|
||||||
if len(symbol.Declarations) != 0 {
|
|
||||||
b.WriteString(" = ")
|
|
||||||
b.WriteString(c.TypeToStringEx(c.GetDeclaredTypeOfSymbol(symbol), container, typeFormatFlags|checker.TypeFormatFlagsInTypeAlias))
|
|
||||||
}
|
|
||||||
declaration = core.Find(symbol.Declarations, ast.IsTypeAliasDeclaration)
|
|
||||||
case flags&ast.SymbolFlagsAlias != 0:
|
|
||||||
b.WriteString("import ")
|
|
||||||
b.WriteString(c.SymbolToStringEx(symbol, container, ast.SymbolFlagsNone, symbolFormatFlags))
|
|
||||||
default:
|
|
||||||
b.WriteString(c.TypeToStringEx(c.GetTypeOfSymbol(symbol), container, typeFormatFlags))
|
|
||||||
}
|
|
||||||
return b.String(), declaration
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNodeForQuickInfo(node *ast.Node) *ast.Node {
|
|
||||||
if node.Parent == nil {
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
if ast.IsNewExpression(node.Parent) && node.Pos() == node.Parent.Pos() {
|
|
||||||
return node.Parent.Expression()
|
|
||||||
}
|
|
||||||
if ast.IsNamedTupleMember(node.Parent) && node.Pos() == node.Parent.Pos() {
|
|
||||||
return node.Parent
|
|
||||||
}
|
|
||||||
if ast.IsImportMeta(node.Parent) && node.Parent.Name() == node {
|
|
||||||
return node.Parent
|
|
||||||
}
|
|
||||||
if ast.IsJsxNamespacedName(node.Parent) {
|
|
||||||
return node.Parent
|
|
||||||
}
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
func inConstructorContext(node *ast.Node) bool {
|
|
||||||
if node.Kind == ast.KindConstructorKeyword {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if ast.IsIdentifier(node) {
|
|
||||||
for ast.IsRightSideOfQualifiedNameOrPropertyAccess(node) {
|
|
||||||
node = node.Parent
|
|
||||||
}
|
|
||||||
if ast.IsNewExpression(node.Parent) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSignaturesAtLocation(c *checker.Checker, symbol *ast.Symbol, kind checker.SignatureKind, node *ast.Node) []*checker.Signature {
|
|
||||||
signatures := c.GetSignaturesOfType(c.GetTypeOfSymbol(symbol), kind)
|
|
||||||
if len(signatures) > 1 || len(signatures) == 1 && len(signatures[0].TypeParameters()) != 0 {
|
|
||||||
if callNode := getCallOrNewExpression(node); callNode != nil {
|
|
||||||
signature := c.GetResolvedSignature(callNode)
|
|
||||||
// If we have a resolved signature, make sure it isn't a synthetic signature
|
|
||||||
if signature != nil && (slices.Contains(signatures, signature) || signature.Target() != nil && slices.Contains(signatures, signature.Target())) {
|
|
||||||
return []*checker.Signature{signature}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return signatures
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCallOrNewExpression(node *ast.Node) *ast.Node {
|
|
||||||
if ast.IsSourceFile(node) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if ast.IsPropertyAccessExpression(node.Parent) && node.Parent.Name() == node {
|
|
||||||
node = node.Parent
|
|
||||||
}
|
|
||||||
if ast.IsCallExpression(node.Parent) || ast.IsNewExpression(node.Parent) {
|
|
||||||
return node.Parent
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeTypeParams(b *strings.Builder, c *checker.Checker, params []*checker.Type) {
|
|
||||||
if len(params) > 0 {
|
|
||||||
b.WriteString("<")
|
|
||||||
for i, tp := range params {
|
|
||||||
if i != 0 {
|
|
||||||
b.WriteString(", ")
|
|
||||||
}
|
|
||||||
symbol := tp.Symbol()
|
|
||||||
b.WriteString(c.SymbolToStringEx(symbol, nil, ast.SymbolFlagsNone, symbolFormatFlags))
|
|
||||||
cons := c.GetConstraintOfTypeParameter(tp)
|
|
||||||
if cons != nil {
|
|
||||||
b.WriteString(" extends ")
|
|
||||||
b.WriteString(c.TypeToStringEx(cons, nil, typeFormatFlags))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b.WriteString(">")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeSignatures(b *strings.Builder, c *checker.Checker, signatures []*checker.Signature, container *ast.Node, prefix string, symbol *ast.Symbol) {
|
|
||||||
for i, sig := range signatures {
|
|
||||||
if i != 0 {
|
|
||||||
b.WriteString("\n")
|
|
||||||
}
|
|
||||||
if i == 3 && len(signatures) >= 5 {
|
|
||||||
b.WriteString(fmt.Sprintf("// +%v more overloads", len(signatures)-3))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
b.WriteString(prefix)
|
|
||||||
b.WriteString(c.SymbolToStringEx(symbol, container, ast.SymbolFlagsNone, symbolFormatFlags))
|
|
||||||
b.WriteString(c.SignatureToStringEx(sig, container, typeFormatFlags|checker.TypeFormatFlagsWriteCallStyleSignature|checker.TypeFormatFlagsWriteTypeArgumentsOfSignature))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func containsTypedefTag(jsdoc *ast.Node) bool {
|
|
||||||
if jsdoc.Kind == ast.KindJSDoc {
|
|
||||||
if tags := jsdoc.AsJSDoc().Tags; tags != nil {
|
|
||||||
for _, tag := range tags.Nodes {
|
|
||||||
if tag.Kind == ast.KindJSDocTypedefTag {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func commentHasPrefix(comments []*ast.Node, prefix string) bool {
|
|
||||||
return comments[0].Kind == ast.KindJSDocText && strings.HasPrefix(comments[0].Text(), prefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getJSDoc(node *ast.Node) *ast.Node {
|
|
||||||
return core.LastOrNil(node.JSDoc(nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func getJSDocOrTag(node *ast.Node) *ast.Node {
|
|
||||||
if jsdoc := getJSDoc(node); jsdoc != nil {
|
|
||||||
return jsdoc
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case ast.IsParameter(node):
|
|
||||||
return getMatchingJSDocTag(node.Parent, node.Name().Text(), isMatchingParameterTag)
|
|
||||||
case ast.IsTypeParameterDeclaration(node):
|
|
||||||
return getMatchingJSDocTag(node.Parent, node.Name().Text(), isMatchingTemplateTag)
|
|
||||||
case ast.IsVariableDeclaration(node) && ast.IsVariableDeclarationList(node.Parent) && core.FirstOrNil(node.Parent.AsVariableDeclarationList().Declarations.Nodes) == node:
|
|
||||||
return getJSDocOrTag(node.Parent.Parent)
|
|
||||||
case (ast.IsFunctionExpressionOrArrowFunction(node) || ast.IsClassExpression(node)) &&
|
|
||||||
(ast.IsVariableDeclaration(node.Parent) || ast.IsPropertyDeclaration(node.Parent) || ast.IsPropertyAssignment(node.Parent)) && node.Parent.Initializer() == node:
|
|
||||||
return getJSDocOrTag(node.Parent)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMatchingJSDocTag(node *ast.Node, name string, match func(*ast.Node, string) bool) *ast.Node {
|
|
||||||
if jsdoc := getJSDocOrTag(node); jsdoc != nil && jsdoc.Kind == ast.KindJSDoc {
|
|
||||||
if tags := jsdoc.AsJSDoc().Tags; tags != nil {
|
|
||||||
for _, tag := range tags.Nodes {
|
|
||||||
if match(tag, name) {
|
|
||||||
return tag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isMatchingParameterTag(tag *ast.Node, name string) bool {
|
|
||||||
return tag.Kind == ast.KindJSDocParameterTag && isNodeWithName(tag, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isMatchingTemplateTag(tag *ast.Node, name string) bool {
|
|
||||||
return tag.Kind == ast.KindJSDocTemplateTag && core.Some(tag.TypeParameters(), func(tp *ast.Node) bool { return isNodeWithName(tp, name) })
|
|
||||||
}
|
|
||||||
|
|
||||||
func isNodeWithName(node *ast.Node, name string) bool {
|
|
||||||
nodeName := node.Name()
|
|
||||||
return ast.IsIdentifier(nodeName) && nodeName.Text() == name
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeCode(b *strings.Builder, lang string, code string) {
|
|
||||||
if code == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ticks := 3
|
|
||||||
for strings.Contains(code, strings.Repeat("`", ticks)) {
|
|
||||||
ticks++
|
|
||||||
}
|
|
||||||
for range ticks {
|
|
||||||
b.WriteByte('`')
|
|
||||||
}
|
|
||||||
b.WriteString(lang)
|
|
||||||
b.WriteByte('\n')
|
|
||||||
b.WriteString(code)
|
|
||||||
b.WriteByte('\n')
|
|
||||||
for range ticks {
|
|
||||||
b.WriteByte('`')
|
|
||||||
}
|
|
||||||
b.WriteByte('\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeComments(b *strings.Builder, comments []*ast.Node) {
|
|
||||||
for _, comment := range comments {
|
|
||||||
switch comment.Kind {
|
|
||||||
case ast.KindJSDocText:
|
|
||||||
b.WriteString(comment.Text())
|
|
||||||
case ast.KindJSDocLink:
|
|
||||||
name := comment.Name()
|
|
||||||
text := comment.AsJSDocLink().Text()
|
|
||||||
if name != nil {
|
|
||||||
if text == "" {
|
|
||||||
writeEntityName(b, name)
|
|
||||||
} else {
|
|
||||||
writeEntityNameParts(b, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b.WriteString(text)
|
|
||||||
case ast.KindJSDocLinkCode:
|
|
||||||
// !!! TODO: This is a temporary placeholder implementation that needs to be updated later
|
|
||||||
name := comment.Name()
|
|
||||||
text := comment.AsJSDocLinkCode().Text()
|
|
||||||
if name != nil {
|
|
||||||
if text == "" {
|
|
||||||
writeEntityName(b, name)
|
|
||||||
} else {
|
|
||||||
writeEntityNameParts(b, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b.WriteString(text)
|
|
||||||
case ast.KindJSDocLinkPlain:
|
|
||||||
// !!! TODO: This is a temporary placeholder implementation that needs to be updated later
|
|
||||||
name := comment.Name()
|
|
||||||
text := comment.AsJSDocLinkPlain().Text()
|
|
||||||
if name != nil {
|
|
||||||
if text == "" {
|
|
||||||
writeEntityName(b, name)
|
|
||||||
} else {
|
|
||||||
writeEntityNameParts(b, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b.WriteString(text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeOptionalEntityName(b *strings.Builder, name *ast.Node) {
|
|
||||||
if name != nil {
|
|
||||||
b.WriteString(" ")
|
|
||||||
writeEntityName(b, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeEntityName(b *strings.Builder, name *ast.Node) {
|
|
||||||
b.WriteString("`")
|
|
||||||
writeEntityNameParts(b, name)
|
|
||||||
b.WriteString("`")
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeEntityNameParts(b *strings.Builder, node *ast.Node) {
|
|
||||||
switch node.Kind {
|
|
||||||
case ast.KindIdentifier:
|
|
||||||
b.WriteString(node.Text())
|
|
||||||
case ast.KindQualifiedName:
|
|
||||||
writeEntityNameParts(b, node.AsQualifiedName().Left)
|
|
||||||
b.WriteByte('.')
|
|
||||||
writeEntityNameParts(b, node.AsQualifiedName().Right)
|
|
||||||
case ast.KindPropertyAccessExpression:
|
|
||||||
writeEntityNameParts(b, node.Expression())
|
|
||||||
b.WriteByte('.')
|
|
||||||
writeEntityNameParts(b, node.Name())
|
|
||||||
case ast.KindParenthesizedExpression, ast.KindExpressionWithTypeArguments:
|
|
||||||
writeEntityNameParts(b, node.Expression())
|
|
||||||
case ast.KindJSDocNameReference:
|
|
||||||
writeEntityNameParts(b, node.Name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,728 +0,0 @@
|
|||||||
package ls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"slices"
|
|
||||||
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/checker"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/compiler"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/debug"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ImpExpKind int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
ImpExpKindUnknown ImpExpKind = iota
|
|
||||||
ImpExpKindImport
|
|
||||||
ImpExpKindExport
|
|
||||||
)
|
|
||||||
|
|
||||||
type ImportExportSymbol struct {
|
|
||||||
kind ImpExpKind
|
|
||||||
symbol *ast.Symbol
|
|
||||||
exportInfo *ExportInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
type ExportInfo struct {
|
|
||||||
exportingModuleSymbol *ast.Symbol
|
|
||||||
exportKind ExportKind
|
|
||||||
}
|
|
||||||
|
|
||||||
type LocationAndSymbol struct {
|
|
||||||
importLocation *ast.Node
|
|
||||||
importSymbol *ast.Symbol
|
|
||||||
}
|
|
||||||
|
|
||||||
type ImportsResult struct {
|
|
||||||
importSearches []LocationAndSymbol
|
|
||||||
singleReferences []*ast.Node
|
|
||||||
indirectUsers []*ast.SourceFile
|
|
||||||
}
|
|
||||||
|
|
||||||
type ImportTracker func(exportSymbol *ast.Symbol, exportInfo *ExportInfo, isForRename bool) *ImportsResult
|
|
||||||
|
|
||||||
type ModuleReferenceKind int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
ModuleReferenceKindImport ModuleReferenceKind = iota
|
|
||||||
ModuleReferenceKindReference
|
|
||||||
ModuleReferenceKindImplicit
|
|
||||||
)
|
|
||||||
|
|
||||||
// ModuleReference represents a reference to a module, either via import, <reference>, or implicit reference
|
|
||||||
type ModuleReference struct {
|
|
||||||
kind ModuleReferenceKind
|
|
||||||
literal *ast.Node // for import and implicit kinds (StringLiteralLike)
|
|
||||||
referencingFile *ast.SourceFile
|
|
||||||
ref *ast.FileReference // for reference kind
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates the imports map and returns an ImportTracker that uses it. Call this lazily to avoid calling `getDirectImportsMap` unnecessarily.
|
|
||||||
func createImportTracker(sourceFiles []*ast.SourceFile, sourceFilesSet *collections.Set[string], checker *checker.Checker) ImportTracker {
|
|
||||||
allDirectImports := getDirectImportsMap(sourceFiles, checker)
|
|
||||||
return func(exportSymbol *ast.Symbol, exportInfo *ExportInfo, isForRename bool) *ImportsResult {
|
|
||||||
directImports, indirectUsers := getImportersForExport(sourceFiles, sourceFilesSet, allDirectImports, exportInfo, checker)
|
|
||||||
importSearches, singleReferences := getSearchesFromDirectImports(directImports, exportSymbol, exportInfo.exportKind, checker, isForRename)
|
|
||||||
return &ImportsResult{importSearches, singleReferences, indirectUsers}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a map from a module symbol to all import statements that directly reference the module
|
|
||||||
func getDirectImportsMap(sourceFiles []*ast.SourceFile, checker *checker.Checker) map[*ast.Symbol][]*ast.Node {
|
|
||||||
result := make(map[*ast.Symbol][]*ast.Node)
|
|
||||||
for _, sourceFile := range sourceFiles {
|
|
||||||
// !!! cancellation
|
|
||||||
forEachImport(sourceFile, func(importDecl *ast.Node, moduleSpecifier *ast.Node) {
|
|
||||||
if moduleSymbol := checker.GetSymbolAtLocation(moduleSpecifier); moduleSymbol != nil {
|
|
||||||
result[moduleSymbol] = append(result[moduleSymbol], importDecl)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calls `action` for each import, re-export, or require() in a file
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
forEachPossibleImportOrExportStatement(sourceFile.AsNode(), func(node *ast.Node) bool {
|
|
||||||
switch node.Kind {
|
|
||||||
case ast.KindExportDeclaration, ast.KindImportDeclaration, ast.KindJSImportDeclaration:
|
|
||||||
if specifier := node.ModuleSpecifier(); specifier != nil && ast.IsStringLiteral(specifier) {
|
|
||||||
action(node, specifier)
|
|
||||||
}
|
|
||||||
case ast.KindImportEqualsDeclaration:
|
|
||||||
if isExternalModuleImportEquals(node) {
|
|
||||||
action(node, node.AsImportEqualsDeclaration().ModuleReference.Expression())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func forEachPossibleImportOrExportStatement(sourceFileLike *ast.Node, action func(statement *ast.Node) bool) bool {
|
|
||||||
for _, statement := range getStatementsOfSourceFileLike(sourceFileLike) {
|
|
||||||
if action(statement) || isAmbientModuleDeclaration(statement) && forEachPossibleImportOrExportStatement(statement, action) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSourceFileLikeForImportDeclaration(node *ast.Node) *ast.Node {
|
|
||||||
if ast.IsCallExpression(node) || ast.IsJSDocImportTag(node) {
|
|
||||||
return ast.GetSourceFileOfNode(node).AsNode()
|
|
||||||
}
|
|
||||||
parent := node.Parent
|
|
||||||
if ast.IsSourceFile(parent) {
|
|
||||||
return parent
|
|
||||||
}
|
|
||||||
debug.Assert(ast.IsModuleBlock(parent) && isAmbientModuleDeclaration(parent.Parent))
|
|
||||||
return parent.Parent
|
|
||||||
}
|
|
||||||
|
|
||||||
func isAmbientModuleDeclaration(node *ast.Node) bool {
|
|
||||||
return ast.IsModuleDeclaration(node) && ast.IsStringLiteral(node.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
func getStatementsOfSourceFileLike(node *ast.Node) []*ast.Node {
|
|
||||||
if ast.IsSourceFile(node) {
|
|
||||||
return node.Statements()
|
|
||||||
}
|
|
||||||
if body := node.Body(); body != nil {
|
|
||||||
return body.Statements()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getImportersForExport(
|
|
||||||
sourceFiles []*ast.SourceFile,
|
|
||||||
sourceFilesSet *collections.Set[string],
|
|
||||||
allDirectImports map[*ast.Symbol][]*ast.Node,
|
|
||||||
exportInfo *ExportInfo,
|
|
||||||
checker *checker.Checker,
|
|
||||||
) ([]*ast.Node, []*ast.SourceFile) {
|
|
||||||
var directImports []*ast.Node
|
|
||||||
var indirectUserDeclarations []*ast.Node
|
|
||||||
markSeenDirectImport := nodeSeenTracker()
|
|
||||||
markSeenIndirectUser := nodeSeenTracker()
|
|
||||||
isAvailableThroughGlobal := exportInfo.exportingModuleSymbol.GlobalExports != nil
|
|
||||||
|
|
||||||
getDirectImports := func(moduleSymbol *ast.Symbol) []*ast.Node {
|
|
||||||
return allDirectImports[moduleSymbol]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adds a module and all of its transitive dependencies as possible indirect users
|
|
||||||
var addIndirectUser func(*ast.Node, bool)
|
|
||||||
addIndirectUser = func(sourceFileLike *ast.Node, addTransitiveDependencies bool) {
|
|
||||||
debug.Assert(!isAvailableThroughGlobal)
|
|
||||||
if !markSeenIndirectUser(sourceFileLike) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
indirectUserDeclarations = append(indirectUserDeclarations, sourceFileLike)
|
|
||||||
if !addTransitiveDependencies {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
moduleSymbol := checker.GetMergedSymbol(sourceFileLike.Symbol())
|
|
||||||
if moduleSymbol == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
debug.Assert(moduleSymbol.Flags&ast.SymbolFlagsModule != 0)
|
|
||||||
for _, directImport := range getDirectImports(moduleSymbol) {
|
|
||||||
if !ast.IsImportTypeNode(directImport) {
|
|
||||||
addIndirectUser(getSourceFileLikeForImportDeclaration(directImport), true /*addTransitiveDependencies*/)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isExported := func(node *ast.Node, stopAtAmbientModule bool) bool {
|
|
||||||
for node != nil && !(stopAtAmbientModule && isAmbientModuleDeclaration(node)) {
|
|
||||||
if ast.HasSyntacticModifier(node, ast.ModifierFlagsExport) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
node = node.Parent
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
handleImportCall := func(importCall *ast.Node) {
|
|
||||||
top := ast.FindAncestor(importCall, isAmbientModuleDeclaration)
|
|
||||||
if top == nil {
|
|
||||||
top = ast.GetSourceFileOfNode(importCall).AsNode()
|
|
||||||
}
|
|
||||||
addIndirectUser(top, isExported(importCall, true /*stopAtAmbientModule*/))
|
|
||||||
}
|
|
||||||
|
|
||||||
handleNamespaceImport := func(importDeclaration *ast.Node, name *ast.Node, isReExport bool, alreadyAddedDirect bool) {
|
|
||||||
if exportInfo.exportKind == ExportKindExportEquals {
|
|
||||||
// This is a direct import, not import-as-namespace.
|
|
||||||
if !alreadyAddedDirect {
|
|
||||||
directImports = append(directImports, importDeclaration)
|
|
||||||
}
|
|
||||||
} else if !isAvailableThroughGlobal {
|
|
||||||
sourceFileLike := getSourceFileLikeForImportDeclaration(importDeclaration)
|
|
||||||
debug.Assert(ast.IsSourceFile(sourceFileLike) || ast.IsModuleDeclaration(sourceFileLike))
|
|
||||||
addIndirectUser(sourceFileLike, isReExport || findNamespaceReExports(sourceFileLike, name, checker))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var handleDirectImports func(*ast.Symbol)
|
|
||||||
handleDirectImports = func(exportingModuleSymbol *ast.Symbol) {
|
|
||||||
theseDirectImports := getDirectImports(exportingModuleSymbol)
|
|
||||||
for _, direct := range theseDirectImports {
|
|
||||||
if !markSeenDirectImport(direct) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// !!! cancellation
|
|
||||||
switch direct.Kind {
|
|
||||||
case ast.KindCallExpression:
|
|
||||||
if ast.IsImportCall(direct) {
|
|
||||||
handleImportCall(direct)
|
|
||||||
} else if !isAvailableThroughGlobal {
|
|
||||||
parent := direct.Parent
|
|
||||||
if exportInfo.exportKind == ExportKindExportEquals && ast.IsVariableDeclaration(parent) {
|
|
||||||
name := parent.Name()
|
|
||||||
if ast.IsIdentifier(name) {
|
|
||||||
directImports = append(directImports, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case ast.KindIdentifier:
|
|
||||||
// Nothing
|
|
||||||
case ast.KindImportEqualsDeclaration:
|
|
||||||
handleNamespaceImport(direct, direct.Name(), ast.HasSyntacticModifier(direct, ast.ModifierFlagsExport), false /*alreadyAddedDirect*/)
|
|
||||||
case ast.KindImportDeclaration, ast.KindJSImportDeclaration, ast.KindJSDocImportTag:
|
|
||||||
directImports = append(directImports, direct)
|
|
||||||
if importClause := direct.ImportClause(); importClause != nil {
|
|
||||||
if namedBindings := importClause.AsImportClause().NamedBindings; namedBindings != nil && ast.IsNamespaceImport(namedBindings) {
|
|
||||||
handleNamespaceImport(direct, namedBindings.Name(), false /*isReExport*/, true /*alreadyAddedDirect*/)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !isAvailableThroughGlobal && ast.IsDefaultImport(direct) {
|
|
||||||
addIndirectUser(getSourceFileLikeForImportDeclaration(direct), false)
|
|
||||||
// Add a check for indirect uses to handle synthetic default imports
|
|
||||||
}
|
|
||||||
case ast.KindExportDeclaration:
|
|
||||||
exportClause := direct.AsExportDeclaration().ExportClause
|
|
||||||
if exportClause == nil {
|
|
||||||
// This is `export * from "foo"`, so imports of this module may import the export too.
|
|
||||||
handleDirectImports(getContainingModuleSymbol(direct, checker))
|
|
||||||
} else if ast.IsNamespaceExport(exportClause) {
|
|
||||||
// `export * as foo from "foo"` add to indirect uses
|
|
||||||
addIndirectUser(getSourceFileLikeForImportDeclaration(direct), true /*addTransitiveDependencies*/)
|
|
||||||
} else {
|
|
||||||
// This is `export { foo } from "foo"` and creates an alias symbol, so recursive search will get handle re-exports.
|
|
||||||
directImports = append(directImports, direct)
|
|
||||||
}
|
|
||||||
case ast.KindImportType:
|
|
||||||
// Only check for typeof import('xyz')
|
|
||||||
if !isAvailableThroughGlobal && direct.AsImportTypeNode().IsTypeOf && direct.AsImportTypeNode().Qualifier == nil && isExported(direct, false) {
|
|
||||||
addIndirectUser(ast.GetSourceFileOfNode(direct).AsNode(), true /*addTransitiveDependencies*/)
|
|
||||||
}
|
|
||||||
directImports = append(directImports, direct)
|
|
||||||
default:
|
|
||||||
debug.FailBadSyntaxKind(direct, "Unexpected import kind.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getIndirectUsers := func() []*ast.SourceFile {
|
|
||||||
if isAvailableThroughGlobal {
|
|
||||||
// It has `export as namespace`, so anything could potentially use it.
|
|
||||||
return sourceFiles
|
|
||||||
}
|
|
||||||
// Module augmentations may use this module's exports without importing it.
|
|
||||||
for _, decl := range exportInfo.exportingModuleSymbol.Declarations {
|
|
||||||
if ast.IsExternalModuleAugmentation(decl) && sourceFilesSet.Has(ast.GetSourceFileOfNode(decl).FileName()) {
|
|
||||||
addIndirectUser(decl, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// This may return duplicates (if there are multiple module declarations in a single source file, all importing the same thing as a namespace), but `State.markSearchedSymbol` will handle that.
|
|
||||||
return core.Map(indirectUserDeclarations, ast.GetSourceFileOfNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDirectImports(exportInfo.exportingModuleSymbol)
|
|
||||||
return directImports, getIndirectUsers()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getContainingModuleSymbol(importer *ast.Node, checker *checker.Checker) *ast.Symbol {
|
|
||||||
return checker.GetMergedSymbol(getSourceFileLikeForImportDeclaration(importer).Symbol())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns 'true' is the namespace 'name' is re-exported from this module, and 'false' if it is only used locally
|
|
||||||
func findNamespaceReExports(sourceFileLike *ast.Node, name *ast.Node, checker *checker.Checker) bool {
|
|
||||||
namespaceImportSymbol := checker.GetSymbolAtLocation(name)
|
|
||||||
return forEachPossibleImportOrExportStatement(sourceFileLike, func(statement *ast.Node) bool {
|
|
||||||
if !ast.IsExportDeclaration(statement) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
exportClause := statement.AsExportDeclaration().ExportClause
|
|
||||||
moduleSpecifier := statement.AsExportDeclaration().ModuleSpecifier
|
|
||||||
return moduleSpecifier == nil && exportClause != nil && ast.IsNamedExports(exportClause) && core.Some(exportClause.Elements(), func(element *ast.Node) bool {
|
|
||||||
return checker.GetExportSpecifierLocalTargetSymbol(element) == namespaceImportSymbol
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSearchesFromDirectImports(
|
|
||||||
directImports []*ast.Node,
|
|
||||||
exportSymbol *ast.Symbol,
|
|
||||||
exportKind ExportKind,
|
|
||||||
checker *checker.Checker,
|
|
||||||
isForRename bool,
|
|
||||||
) ([]LocationAndSymbol, []*ast.Node) {
|
|
||||||
var importSearches []LocationAndSymbol
|
|
||||||
var singleReferences []*ast.Node
|
|
||||||
|
|
||||||
addSearch := func(location *ast.Node, symbol *ast.Symbol) {
|
|
||||||
importSearches = append(importSearches, LocationAndSymbol{location, symbol})
|
|
||||||
}
|
|
||||||
|
|
||||||
isNameMatch := func(name string) bool {
|
|
||||||
// Use name of "default" even in `export =` case because we may have allowSyntheticDefaultImports
|
|
||||||
return name == exportSymbol.Name || exportKind != ExportKindNamed && name == ast.InternalSymbolNameDefault
|
|
||||||
}
|
|
||||||
|
|
||||||
// `import x = require("./x")` or `import * as x from "./x"`.
|
|
||||||
// An `export =` may be imported by this syntax, so it may be a direct import.
|
|
||||||
// If it's not a direct import, it will be in `indirectUsers`, so we don't have to do anything here.
|
|
||||||
handleNamespaceImportLike := func(importName *ast.Node) {
|
|
||||||
// Don't rename an import that already has a different name than the export.
|
|
||||||
if exportKind == ExportKindExportEquals && (!isForRename || isNameMatch(importName.Text())) {
|
|
||||||
addSearch(importName, checker.GetSymbolAtLocation(importName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
searchForNamedImport := func(namedBindings *ast.Node) {
|
|
||||||
if namedBindings == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, element := range namedBindings.Elements() {
|
|
||||||
name := element.Name()
|
|
||||||
propertyName := element.PropertyName()
|
|
||||||
if !isNameMatch(core.OrElse(propertyName, name).Text()) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if propertyName != nil {
|
|
||||||
// This is `import { foo as bar } from "./a"` or `export { foo as bar } from "./a"`. `foo` isn't a local in the file, so just add it as a single reference.
|
|
||||||
singleReferences = append(singleReferences, propertyName)
|
|
||||||
// If renaming `{ foo as bar }`, don't touch `bar`, just `foo`.
|
|
||||||
// But do rename `foo` in ` { default as foo }` if that's the original export name.
|
|
||||||
if !isForRename || name.Text() == exportSymbol.Name {
|
|
||||||
// Search locally for `bar`.
|
|
||||||
addSearch(name, checker.GetSymbolAtLocation(name))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var localSymbol *ast.Symbol
|
|
||||||
if ast.IsExportSpecifier(element) && element.PropertyName() != nil {
|
|
||||||
localSymbol = checker.GetExportSpecifierLocalTargetSymbol(element)
|
|
||||||
} else {
|
|
||||||
localSymbol = checker.GetSymbolAtLocation(name)
|
|
||||||
}
|
|
||||||
addSearch(name, localSymbol)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleImport := func(decl *ast.Node) {
|
|
||||||
if ast.IsImportEqualsDeclaration(decl) {
|
|
||||||
if isExternalModuleImportEquals(decl) {
|
|
||||||
handleNamespaceImportLike(decl.Name())
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ast.IsIdentifier(decl) {
|
|
||||||
handleNamespaceImportLike(decl)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ast.IsImportTypeNode(decl) {
|
|
||||||
if qualifier := decl.AsImportTypeNode().Qualifier; qualifier != nil {
|
|
||||||
firstIdentifier := ast.GetFirstIdentifier(qualifier)
|
|
||||||
if firstIdentifier.Text() == ast.SymbolName(exportSymbol) {
|
|
||||||
singleReferences = append(singleReferences, firstIdentifier)
|
|
||||||
}
|
|
||||||
} else if exportKind == ExportKindExportEquals {
|
|
||||||
singleReferences = append(singleReferences, decl.AsImportTypeNode().Argument.AsLiteralTypeNode().Literal)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Ignore if there's a grammar error
|
|
||||||
if !ast.IsStringLiteral(decl.ModuleSpecifier()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ast.IsExportDeclaration(decl) {
|
|
||||||
if exportClause := decl.AsExportDeclaration().ExportClause; exportClause != nil && ast.IsNamedExports(exportClause) {
|
|
||||||
searchForNamedImport(exportClause)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if importClause := decl.ImportClause(); importClause != nil {
|
|
||||||
if namedBindings := importClause.AsImportClause().NamedBindings; namedBindings != nil {
|
|
||||||
switch namedBindings.Kind {
|
|
||||||
case ast.KindNamespaceImport:
|
|
||||||
handleNamespaceImportLike(namedBindings.Name())
|
|
||||||
case ast.KindNamedImports:
|
|
||||||
// 'default' might be accessed as a named import `{ default as foo }`.
|
|
||||||
if exportKind == ExportKindNamed || exportKind == ExportKindDefault {
|
|
||||||
searchForNamedImport(namedBindings)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// `export =` might be imported by a default import if `--allowSyntheticDefaultImports` is on, so this handles both ExportKind.Default and ExportKind.ExportEquals.
|
|
||||||
// If a default import has the same name as the default export, allow to rename it.
|
|
||||||
// Given `import f` and `export default function f`, we will rename both, but for `import g` we will rename just that.
|
|
||||||
if name := importClause.Name(); name != nil && (exportKind == ExportKindDefault || exportKind == ExportKindExportEquals) && (!isForRename || name.Text() == symbolNameNoDefault(exportSymbol)) {
|
|
||||||
defaultImportAlias := checker.GetSymbolAtLocation(name)
|
|
||||||
addSearch(name, defaultImportAlias)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, decl := range directImports {
|
|
||||||
handleImport(decl)
|
|
||||||
}
|
|
||||||
return importSearches, singleReferences
|
|
||||||
}
|
|
||||||
|
|
||||||
func getImportOrExportSymbol(node *ast.Node, symbol *ast.Symbol, checker *checker.Checker, comingFromExport bool) *ImportExportSymbol {
|
|
||||||
exportInfo := func(symbol *ast.Symbol, kind ExportKind) *ImportExportSymbol {
|
|
||||||
if exportInfo := getExportInfo(symbol, kind, checker); exportInfo != nil {
|
|
||||||
return &ImportExportSymbol{
|
|
||||||
kind: ImpExpKindExport,
|
|
||||||
symbol: symbol,
|
|
||||||
exportInfo: exportInfo,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
getExport := func() *ImportExportSymbol {
|
|
||||||
getExportAssignmentExport := func(ex *ast.Node) *ImportExportSymbol {
|
|
||||||
// Get the symbol for the `export =` node; its parent is the module it's the export of.
|
|
||||||
if ex.Symbol().Parent == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
exportKind := core.IfElse(ex.AsExportAssignment().IsExportEquals, ExportKindExportEquals, ExportKindDefault)
|
|
||||||
return &ImportExportSymbol{
|
|
||||||
kind: ImpExpKindExport,
|
|
||||||
symbol: symbol,
|
|
||||||
exportInfo: &ExportInfo{
|
|
||||||
exportingModuleSymbol: ex.Symbol().Parent,
|
|
||||||
exportKind: exportKind,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not meant for use with export specifiers or export assignment.
|
|
||||||
getExportKindForDeclaration := func(node *ast.Node) ExportKind {
|
|
||||||
if ast.HasSyntacticModifier(node, ast.ModifierFlagsDefault) {
|
|
||||||
return ExportKindDefault
|
|
||||||
}
|
|
||||||
return ExportKindNamed
|
|
||||||
}
|
|
||||||
|
|
||||||
getSpecialPropertyExport := func(node *ast.Node, useLhsSymbol bool) *ImportExportSymbol {
|
|
||||||
var kind ExportKind
|
|
||||||
switch ast.GetAssignmentDeclarationKind(node.AsBinaryExpression()) {
|
|
||||||
case ast.JSDeclarationKindExportsProperty:
|
|
||||||
kind = ExportKindNamed
|
|
||||||
case ast.JSDeclarationKindModuleExports:
|
|
||||||
kind = ExportKindExportEquals
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
sym := symbol
|
|
||||||
if useLhsSymbol {
|
|
||||||
sym = checker.GetSymbolAtLocation(ast.GetElementOrPropertyAccessName(node.AsBinaryExpression().Left))
|
|
||||||
}
|
|
||||||
if sym == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return exportInfo(sym, kind)
|
|
||||||
}
|
|
||||||
|
|
||||||
parent := node.Parent
|
|
||||||
grandparent := parent.Parent
|
|
||||||
if symbol.ExportSymbol != nil {
|
|
||||||
if ast.IsPropertyAccessExpression(parent) {
|
|
||||||
// When accessing an export of a JS module, there's no alias. The symbol will still be flagged as an export even though we're at the use.
|
|
||||||
// So check that we are at the declaration.
|
|
||||||
if ast.IsBinaryExpression(grandparent) && slices.Contains(symbol.Declarations, parent) {
|
|
||||||
return getSpecialPropertyExport(grandparent, false /*useLhsSymbol*/)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return exportInfo(symbol.ExportSymbol, getExportKindForDeclaration(parent))
|
|
||||||
} else {
|
|
||||||
exportNode := getExportNode(parent, node)
|
|
||||||
switch {
|
|
||||||
case exportNode != nil && ast.HasSyntacticModifier(exportNode, ast.ModifierFlagsExport):
|
|
||||||
if ast.IsImportEqualsDeclaration(exportNode) && exportNode.AsImportEqualsDeclaration().ModuleReference == node {
|
|
||||||
// We're at `Y` in `export import X = Y`. This is not the exported symbol, the left-hand-side is. So treat this as an import statement.
|
|
||||||
if comingFromExport {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
lhsSymbol := checker.GetSymbolAtLocation(exportNode.Name())
|
|
||||||
return &ImportExportSymbol{
|
|
||||||
kind: ImpExpKindImport,
|
|
||||||
symbol: lhsSymbol,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return exportInfo(symbol, getExportKindForDeclaration(exportNode))
|
|
||||||
case ast.IsNamespaceExport(parent):
|
|
||||||
return exportInfo(symbol, ExportKindNamed)
|
|
||||||
case ast.IsExportAssignment(parent):
|
|
||||||
return getExportAssignmentExport(parent)
|
|
||||||
case ast.IsExportAssignment(grandparent):
|
|
||||||
return getExportAssignmentExport(grandparent)
|
|
||||||
case ast.IsBinaryExpression(parent):
|
|
||||||
return getSpecialPropertyExport(parent, true /*useLhsSymbol*/)
|
|
||||||
case ast.IsBinaryExpression(grandparent):
|
|
||||||
return getSpecialPropertyExport(grandparent, true /*useLhsSymbol*/)
|
|
||||||
case ast.IsJSDocTypedefTag(parent) || ast.IsJSDocCallbackTag(parent):
|
|
||||||
return exportInfo(symbol, ExportKindNamed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
getImport := func() *ImportExportSymbol {
|
|
||||||
if !isNodeImport(node) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// A symbol being imported is always an alias. So get what that aliases to find the local symbol.
|
|
||||||
importedSymbol := checker.GetImmediateAliasedSymbol(symbol)
|
|
||||||
if importedSymbol == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Search on the local symbol in the exporting module, not the exported symbol.
|
|
||||||
importedSymbol = skipExportSpecifierSymbol(importedSymbol, checker)
|
|
||||||
// Similarly, skip past the symbol for 'export ='
|
|
||||||
if importedSymbol.Name == "export=" {
|
|
||||||
importedSymbol = getExportEqualsLocalSymbol(importedSymbol, checker)
|
|
||||||
if importedSymbol == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If the import has a different name than the export, do not continue searching.
|
|
||||||
// If `importedName` is undefined, do continue searching as the export is anonymous.
|
|
||||||
// (All imports returned from this function will be ignored anyway if we are in rename and this is a not a named export.)
|
|
||||||
importedName := symbolNameNoDefault(importedSymbol)
|
|
||||||
if importedName == "" || importedName == ast.InternalSymbolNameDefault || importedName == symbol.Name {
|
|
||||||
return &ImportExportSymbol{
|
|
||||||
kind: ImpExpKindImport,
|
|
||||||
symbol: importedSymbol,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
result := getExport()
|
|
||||||
if result == nil && !comingFromExport {
|
|
||||||
result = getImport()
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func getExportInfo(exportSymbol *ast.Symbol, exportKind ExportKind, c *checker.Checker) *ExportInfo {
|
|
||||||
// Parent can be nil if an `export` is not at the top-level (which is a compile error).
|
|
||||||
if exportSymbol.Parent != nil {
|
|
||||||
exportingModuleSymbol := c.GetMergedSymbol(exportSymbol.Parent)
|
|
||||||
// `export` may appear in a namespace. In that case, just rely on global search.
|
|
||||||
if checker.IsExternalModuleSymbol(exportingModuleSymbol) {
|
|
||||||
return &ExportInfo{
|
|
||||||
exportingModuleSymbol: exportingModuleSymbol,
|
|
||||||
exportKind: exportKind,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a reference is a class expression, the exported node would be its parent.
|
|
||||||
// If a reference is a variable declaration, the exported node would be the variable statement.
|
|
||||||
func getExportNode(parent *ast.Node, node *ast.Node) *ast.Node {
|
|
||||||
var declaration *ast.Node
|
|
||||||
switch {
|
|
||||||
case ast.IsVariableDeclaration(parent):
|
|
||||||
declaration = parent
|
|
||||||
case ast.IsBindingElement(parent):
|
|
||||||
declaration = ast.WalkUpBindingElementsAndPatterns(parent)
|
|
||||||
}
|
|
||||||
if declaration != nil {
|
|
||||||
if parent.Name() == node && !ast.IsCatchClause(declaration.Parent) && ast.IsVariableStatement(declaration.Parent.Parent) {
|
|
||||||
return declaration.Parent.Parent
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return parent
|
|
||||||
}
|
|
||||||
|
|
||||||
func isNodeImport(node *ast.Node) bool {
|
|
||||||
parent := node.Parent
|
|
||||||
switch parent.Kind {
|
|
||||||
case ast.KindImportEqualsDeclaration:
|
|
||||||
return parent.Name() == node && isExternalModuleImportEquals(parent)
|
|
||||||
case ast.KindImportSpecifier:
|
|
||||||
// For a rename import `{ foo as bar }`, don't search for the imported symbol. Just find local uses of `bar`.
|
|
||||||
return parent.PropertyName() == nil
|
|
||||||
case ast.KindImportClause, ast.KindNamespaceImport:
|
|
||||||
debug.Assert(parent.Name() == node)
|
|
||||||
return true
|
|
||||||
case ast.KindBindingElement:
|
|
||||||
return ast.IsInJSFile(node) && ast.IsVariableDeclarationInitializedToRequire(parent.Parent.Parent)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func isExternalModuleImportEquals(node *ast.Node) bool {
|
|
||||||
moduleReference := node.AsImportEqualsDeclaration().ModuleReference
|
|
||||||
return ast.IsExternalModuleReference(moduleReference) && moduleReference.Expression().Kind == ast.KindStringLiteral
|
|
||||||
}
|
|
||||||
|
|
||||||
// If at an export specifier, go to the symbol it refers to. */
|
|
||||||
func skipExportSpecifierSymbol(symbol *ast.Symbol, checker *checker.Checker) *ast.Symbol {
|
|
||||||
// For `export { foo } from './bar", there's nothing to skip, because it does not create a new alias. But `export { foo } does.
|
|
||||||
for _, declaration := range symbol.Declarations {
|
|
||||||
switch {
|
|
||||||
case ast.IsExportSpecifier(declaration) && declaration.PropertyName() == nil && declaration.Parent.Parent.ModuleSpecifier() == nil:
|
|
||||||
return core.OrElse(checker.GetExportSpecifierLocalTargetSymbol(declaration), symbol)
|
|
||||||
case ast.IsPropertyAccessExpression(declaration) && ast.IsModuleExportsAccessExpression(declaration.Expression()) && !ast.IsPrivateIdentifier(declaration.Name()):
|
|
||||||
// Export of form 'module.exports.propName = expr';
|
|
||||||
return checker.GetSymbolAtLocation(declaration)
|
|
||||||
case ast.IsShorthandPropertyAssignment(declaration) && ast.IsBinaryExpression(declaration.Parent.Parent) && ast.GetAssignmentDeclarationKind(declaration.Parent.Parent.AsBinaryExpression()) == ast.JSDeclarationKindModuleExports:
|
|
||||||
return checker.GetExportSpecifierLocalTargetSymbol(declaration.Name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return symbol
|
|
||||||
}
|
|
||||||
|
|
||||||
func getExportEqualsLocalSymbol(importedSymbol *ast.Symbol, checker *checker.Checker) *ast.Symbol {
|
|
||||||
if importedSymbol.Flags&ast.SymbolFlagsAlias != 0 {
|
|
||||||
return checker.GetImmediateAliasedSymbol(importedSymbol)
|
|
||||||
}
|
|
||||||
decl := debug.CheckDefined(importedSymbol.ValueDeclaration)
|
|
||||||
switch {
|
|
||||||
case ast.IsExportAssignment(decl):
|
|
||||||
return decl.Expression().Symbol()
|
|
||||||
case ast.IsBinaryExpression(decl):
|
|
||||||
return decl.AsBinaryExpression().Right.Symbol()
|
|
||||||
case ast.IsSourceFile(decl):
|
|
||||||
return decl.Symbol()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func symbolNameNoDefault(symbol *ast.Symbol) string {
|
|
||||||
if symbol.Name != ast.InternalSymbolNameDefault {
|
|
||||||
return symbol.Name
|
|
||||||
}
|
|
||||||
for _, decl := range symbol.Declarations {
|
|
||||||
name := ast.GetNameOfDeclaration(decl)
|
|
||||||
if name != nil && ast.IsIdentifier(name) {
|
|
||||||
return name.Text()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// findModuleReferences finds all references to a module symbol across the given source files.
|
|
||||||
// This includes import statements, <reference> directives, and implicit references (e.g., JSX runtime imports).
|
|
||||||
func findModuleReferences(program *compiler.Program, sourceFiles []*ast.SourceFile, searchModuleSymbol *ast.Symbol, checker *checker.Checker) []ModuleReference {
|
|
||||||
refs := []ModuleReference{}
|
|
||||||
|
|
||||||
for _, referencingFile := range sourceFiles {
|
|
||||||
searchSourceFile := searchModuleSymbol.ValueDeclaration
|
|
||||||
if searchSourceFile != nil && searchSourceFile.Kind == ast.KindSourceFile {
|
|
||||||
// Check <reference path> directives
|
|
||||||
for _, ref := range referencingFile.ReferencedFiles {
|
|
||||||
if program.GetSourceFileFromReference(referencingFile, ref) == searchSourceFile.AsSourceFile() {
|
|
||||||
refs = append(refs, ModuleReference{
|
|
||||||
kind: ModuleReferenceKindReference,
|
|
||||||
referencingFile: referencingFile,
|
|
||||||
ref: ref,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check <reference types> directives
|
|
||||||
for _, ref := range referencingFile.TypeReferenceDirectives {
|
|
||||||
referenced := program.GetResolvedTypeReferenceDirectiveFromTypeReferenceDirective(ref, referencingFile)
|
|
||||||
if referenced != nil && referenced.ResolvedFileName == searchSourceFile.AsSourceFile().FileName() {
|
|
||||||
refs = append(refs, ModuleReference{
|
|
||||||
kind: ModuleReferenceKindReference,
|
|
||||||
referencingFile: referencingFile,
|
|
||||||
ref: ref,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check all imports (including require() calls)
|
|
||||||
forEachImport(referencingFile, func(importDecl *ast.Node, moduleSpecifier *ast.Node) {
|
|
||||||
moduleSymbol := checker.GetSymbolAtLocation(moduleSpecifier)
|
|
||||||
if moduleSymbol == searchModuleSymbol {
|
|
||||||
if ast.NodeIsSynthesized(importDecl) {
|
|
||||||
refs = append(refs, ModuleReference{
|
|
||||||
kind: ModuleReferenceKindImplicit,
|
|
||||||
literal: moduleSpecifier,
|
|
||||||
referencingFile: referencingFile,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
refs = append(refs, ModuleReference{
|
|
||||||
kind: ModuleReferenceKindImport,
|
|
||||||
literal: moduleSpecifier,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return refs
|
|
||||||
}
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
package ls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/compiler"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/sourcemap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LanguageService struct {
|
|
||||||
host Host
|
|
||||||
program *compiler.Program
|
|
||||||
converters *Converters
|
|
||||||
documentPositionMappers map[string]*sourcemap.DocumentPositionMapper
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLanguageService(
|
|
||||||
program *compiler.Program,
|
|
||||||
host Host,
|
|
||||||
) *LanguageService {
|
|
||||||
return &LanguageService{
|
|
||||||
host: host,
|
|
||||||
program: program,
|
|
||||||
converters: host.Converters(),
|
|
||||||
documentPositionMappers: map[string]*sourcemap.DocumentPositionMapper{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) GetProgram() *compiler.Program {
|
|
||||||
return l.program
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) tryGetProgramAndFile(fileName string) (*compiler.Program, *ast.SourceFile) {
|
|
||||||
program := l.GetProgram()
|
|
||||||
file := program.GetSourceFile(fileName)
|
|
||||||
return program, file
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) getProgramAndFile(documentURI lsproto.DocumentUri) (*compiler.Program, *ast.SourceFile) {
|
|
||||||
fileName := documentURI.FileName()
|
|
||||||
program, file := l.tryGetProgramAndFile(fileName)
|
|
||||||
if file == nil {
|
|
||||||
panic("file not found: " + fileName)
|
|
||||||
}
|
|
||||||
return program, file
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) GetDocumentPositionMapper(fileName string) *sourcemap.DocumentPositionMapper {
|
|
||||||
d, ok := l.documentPositionMappers[fileName]
|
|
||||||
if !ok {
|
|
||||||
d = sourcemap.GetDocumentPositionMapper(l, fileName)
|
|
||||||
l.documentPositionMappers[fileName] = d
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) ReadFile(fileName string) (string, bool) {
|
|
||||||
return l.host.ReadFile(fileName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) UseCaseSensitiveFileNames() bool {
|
|
||||||
return l.host.UseCaseSensitiveFileNames()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) GetECMALineInfo(fileName string) *sourcemap.ECMALineInfo {
|
|
||||||
return l.host.GetECMALineInfo(fileName)
|
|
||||||
}
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
package ls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cmp"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LSPLineStarts []core.TextPos
|
|
||||||
|
|
||||||
type LSPLineMap struct {
|
|
||||||
LineStarts LSPLineStarts
|
|
||||||
AsciiOnly bool // TODO(jakebailey): collect ascii-only info per line
|
|
||||||
}
|
|
||||||
|
|
||||||
func ComputeLSPLineStarts(text string) *LSPLineMap {
|
|
||||||
// This is like core.ComputeLineStarts, but only considers "\n", "\r", and "\r\n" as line breaks,
|
|
||||||
// and reports when the text is ASCII-only.
|
|
||||||
lineStarts := make([]core.TextPos, 0, strings.Count(text, "\n")+1)
|
|
||||||
asciiOnly := true
|
|
||||||
|
|
||||||
textLen := core.TextPos(len(text))
|
|
||||||
var pos core.TextPos
|
|
||||||
var lineStart core.TextPos
|
|
||||||
for pos < textLen {
|
|
||||||
b := text[pos]
|
|
||||||
if b < utf8.RuneSelf {
|
|
||||||
pos++
|
|
||||||
switch b {
|
|
||||||
case '\r':
|
|
||||||
if pos < textLen && text[pos] == '\n' {
|
|
||||||
pos++
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
case '\n':
|
|
||||||
lineStarts = append(lineStarts, lineStart)
|
|
||||||
lineStart = pos
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, size := utf8.DecodeRuneInString(text[pos:])
|
|
||||||
pos += core.TextPos(size)
|
|
||||||
asciiOnly = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lineStarts = append(lineStarts, lineStart)
|
|
||||||
|
|
||||||
return &LSPLineMap{
|
|
||||||
LineStarts: lineStarts,
|
|
||||||
AsciiOnly: asciiOnly,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lm *LSPLineMap) ComputeIndexOfLineStart(targetPos core.TextPos) int {
|
|
||||||
// port of computeLineOfPosition(lineStarts: readonly number[], position: number, lowerBound?: number): number {
|
|
||||||
lineNumber, ok := slices.BinarySearchFunc(lm.LineStarts, targetPos, func(p, t core.TextPos) int {
|
|
||||||
return cmp.Compare(int(p), int(t))
|
|
||||||
})
|
|
||||||
if !ok && lineNumber > 0 {
|
|
||||||
// If the actual position was not found, the binary search returns where the target line start would be inserted
|
|
||||||
// if the target was in the slice.
|
|
||||||
// e.g. if the line starts at [5, 10, 23, 80] and the position requested was 20
|
|
||||||
// then the search will return (3, false).
|
|
||||||
//
|
|
||||||
// We want the index of the previous line start, so we subtract 1.
|
|
||||||
lineNumber = lineNumber - 1
|
|
||||||
}
|
|
||||||
return lineNumber
|
|
||||||
}
|
|
||||||
@ -1,128 +0,0 @@
|
|||||||
package ls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cmp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/compiler"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/modulespecifiers"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
|
|
||||||
)
|
|
||||||
|
|
||||||
// statement = anyImportOrRequireStatement
|
|
||||||
func getImportDeclarationInsertIndex(sortedImports []*ast.Statement, newImport *ast.Statement, comparer func(a, b *ast.Statement) int) int {
|
|
||||||
// !!!
|
|
||||||
return len(sortedImports)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 compareModuleSpecifiers(
|
|
||||||
a *ImportFix, // !!! ImportFixWithModuleSpecifier
|
|
||||||
b *ImportFix, // !!! ImportFixWithModuleSpecifier
|
|
||||||
importingFile *ast.SourceFile, // | FutureSourceFile,
|
|
||||||
program *compiler.Program,
|
|
||||||
preferences UserPreferences,
|
|
||||||
allowsImportingSpecifier func(specifier string) bool,
|
|
||||||
toPath func(fileName string) tspath.Path,
|
|
||||||
) int {
|
|
||||||
if a.kind == ImportFixKindUseNamespace || b.kind == ImportFixKindUseNamespace {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
if comparison := compareBooleans(
|
|
||||||
b.moduleSpecifierKind != modulespecifiers.ResultKindNodeModules || allowsImportingSpecifier(b.moduleSpecifier),
|
|
||||||
a.moduleSpecifierKind != modulespecifiers.ResultKindNodeModules || allowsImportingSpecifier(a.moduleSpecifier),
|
|
||||||
); comparison != 0 {
|
|
||||||
return comparison
|
|
||||||
}
|
|
||||||
if comparison := compareModuleSpecifierRelativity(a, b, preferences); comparison != 0 {
|
|
||||||
return comparison
|
|
||||||
}
|
|
||||||
if comparison := compareNodeCoreModuleSpecifiers(a.moduleSpecifier, b.moduleSpecifier, importingFile, program); comparison != 0 {
|
|
||||||
return comparison
|
|
||||||
}
|
|
||||||
if comparison := compareBooleans(isFixPossiblyReExportingImportingFile(a, importingFile.Path(), toPath), isFixPossiblyReExportingImportingFile(b, importingFile.Path(), toPath)); comparison != 0 {
|
|
||||||
return comparison
|
|
||||||
}
|
|
||||||
if comparison := compareNumberOfDirectorySeparators(a.moduleSpecifier, b.moduleSpecifier); comparison != 0 {
|
|
||||||
return comparison
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// True > False
|
|
||||||
func compareBooleans(a, b bool) int {
|
|
||||||
if a && !b {
|
|
||||||
return -1
|
|
||||||
} else if !a && b {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns `-1` if `a` is better than `b`
|
|
||||||
func compareModuleSpecifierRelativity(a *ImportFix, b *ImportFix, preferences UserPreferences) int {
|
|
||||||
switch preferences.ImportModuleSpecifierPreference {
|
|
||||||
case modulespecifiers.ImportModuleSpecifierPreferenceNonRelative, modulespecifiers.ImportModuleSpecifierPreferenceProjectRelative:
|
|
||||||
return compareBooleans(a.moduleSpecifierKind == modulespecifiers.ResultKindRelative, b.moduleSpecifierKind == modulespecifiers.ResultKindRelative)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func compareNodeCoreModuleSpecifiers(a, b string, importingFile *ast.SourceFile, program *compiler.Program) int {
|
|
||||||
if strings.HasPrefix(a, "node:") && !strings.HasPrefix(b, "node:") {
|
|
||||||
if shouldUseUriStyleNodeCoreModules(importingFile, program) {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(b, "node:") && !strings.HasPrefix(a, "node:") {
|
|
||||||
if shouldUseUriStyleNodeCoreModules(importingFile, program) {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func shouldUseUriStyleNodeCoreModules(file *ast.SourceFile, program *compiler.Program) bool {
|
|
||||||
for _, node := range file.Imports() {
|
|
||||||
if core.NodeCoreModules()[node.Text()] && !core.ExclusivelyPrefixedNodeCoreModules[node.Text()] {
|
|
||||||
if strings.HasPrefix(node.Text(), "node:") {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return program.UsesUriStyleNodeCoreModules()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 compareNumberOfDirectorySeparators(path1, path2 string) int {
|
|
||||||
return cmp.Compare(strings.Count(path1, "/"), strings.Count(path2, "/"))
|
|
||||||
}
|
|
||||||
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,81 +0,0 @@
|
|||||||
package ls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/debug"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/sourcemap"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (l *LanguageService) getMappedLocation(fileName string, fileRange core.TextRange) lsproto.Location {
|
|
||||||
startPos := l.tryGetSourcePosition(fileName, core.TextPos(fileRange.Pos()))
|
|
||||||
if startPos == nil {
|
|
||||||
lspRange := l.createLspRangeFromRange(fileRange, l.getScript(fileName))
|
|
||||||
return lsproto.Location{
|
|
||||||
Uri: FileNameToDocumentURI(fileName),
|
|
||||||
Range: *lspRange,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
endPos := l.tryGetSourcePosition(fileName, core.TextPos(fileRange.End()))
|
|
||||||
debug.Assert(endPos.FileName == startPos.FileName, "start and end should be in same file")
|
|
||||||
newRange := core.NewTextRange(startPos.Pos, endPos.Pos)
|
|
||||||
lspRange := l.createLspRangeFromRange(newRange, l.getScript(startPos.FileName))
|
|
||||||
return lsproto.Location{
|
|
||||||
Uri: FileNameToDocumentURI(startPos.FileName),
|
|
||||||
Range: *lspRange,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type script struct {
|
|
||||||
fileName string
|
|
||||||
text string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *script) FileName() string {
|
|
||||||
return s.fileName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *script) Text() string {
|
|
||||||
return s.text
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) getScript(fileName string) *script {
|
|
||||||
text, ok := l.host.ReadFile(fileName)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &script{fileName: fileName, text: text}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) tryGetSourcePosition(
|
|
||||||
fileName string,
|
|
||||||
position core.TextPos,
|
|
||||||
) *sourcemap.DocumentPosition {
|
|
||||||
newPos := l.tryGetSourcePositionWorker(fileName, position)
|
|
||||||
if newPos != nil {
|
|
||||||
if _, ok := l.ReadFile(newPos.FileName); !ok { // File doesn't exist
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newPos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) tryGetSourcePositionWorker(
|
|
||||||
fileName string,
|
|
||||||
position core.TextPos,
|
|
||||||
) *sourcemap.DocumentPosition {
|
|
||||||
if !tspath.IsDeclarationFileName(fileName) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
positionMapper := l.GetDocumentPositionMapper(fileName)
|
|
||||||
documentPos := positionMapper.GetSourcePosition(&sourcemap.DocumentPosition{FileName: fileName, Pos: int(position)})
|
|
||||||
if documentPos == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if newPos := l.tryGetSourcePositionWorker(documentPos.FileName, core.TextPos(documentPos.Pos)); newPos != nil {
|
|
||||||
return newPos
|
|
||||||
}
|
|
||||||
return documentPos
|
|
||||||
}
|
|
||||||
@ -1,731 +0,0 @@
|
|||||||
package ls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/checker"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/compiler"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/printer"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
|
|
||||||
)
|
|
||||||
|
|
||||||
type completionsFromTypes struct {
|
|
||||||
types []*checker.StringLiteralType
|
|
||||||
isNewIdentifier bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type completionsFromProperties struct {
|
|
||||||
symbols []*ast.Symbol
|
|
||||||
hasIndexSignature bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type pathCompletion struct {
|
|
||||||
name string
|
|
||||||
// ScriptElementKindScriptElement | ScriptElementKindDirectory | ScriptElementKindExternalModuleName
|
|
||||||
kind ScriptElementKind
|
|
||||||
extension string
|
|
||||||
textRange *core.TextRange
|
|
||||||
}
|
|
||||||
|
|
||||||
type stringLiteralCompletions struct {
|
|
||||||
fromTypes *completionsFromTypes
|
|
||||||
fromProperties *completionsFromProperties
|
|
||||||
fromPaths []*pathCompletion
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) getStringLiteralCompletions(
|
|
||||||
ctx context.Context,
|
|
||||||
file *ast.SourceFile,
|
|
||||||
position int,
|
|
||||||
contextToken *ast.Node,
|
|
||||||
compilerOptions *core.CompilerOptions,
|
|
||||||
preferences *UserPreferences,
|
|
||||||
clientOptions *lsproto.CompletionClientCapabilities,
|
|
||||||
) *lsproto.CompletionList {
|
|
||||||
// !!! reference comment
|
|
||||||
if IsInString(file, position, contextToken) {
|
|
||||||
if contextToken == nil || !ast.IsStringLiteralLike(contextToken) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
entries := l.getStringLiteralCompletionEntries(
|
|
||||||
ctx,
|
|
||||||
file,
|
|
||||||
contextToken,
|
|
||||||
position,
|
|
||||||
preferences)
|
|
||||||
return l.convertStringLiteralCompletions(
|
|
||||||
ctx,
|
|
||||||
entries,
|
|
||||||
contextToken,
|
|
||||||
file,
|
|
||||||
position,
|
|
||||||
compilerOptions,
|
|
||||||
preferences,
|
|
||||||
clientOptions,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) convertStringLiteralCompletions(
|
|
||||||
ctx context.Context,
|
|
||||||
completion *stringLiteralCompletions,
|
|
||||||
contextToken *ast.StringLiteralLike,
|
|
||||||
file *ast.SourceFile,
|
|
||||||
position int,
|
|
||||||
options *core.CompilerOptions,
|
|
||||||
preferences *UserPreferences,
|
|
||||||
clientOptions *lsproto.CompletionClientCapabilities,
|
|
||||||
) *lsproto.CompletionList {
|
|
||||||
if completion == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
optionalReplacementRange := l.createRangeFromStringLiteralLikeContent(file, contextToken, position)
|
|
||||||
switch {
|
|
||||||
case completion.fromPaths != nil:
|
|
||||||
completion := completion.fromPaths
|
|
||||||
return l.convertPathCompletions(completion, file, position, clientOptions)
|
|
||||||
case completion.fromProperties != nil:
|
|
||||||
completion := completion.fromProperties
|
|
||||||
data := &completionDataData{
|
|
||||||
symbols: completion.symbols,
|
|
||||||
completionKind: CompletionKindString,
|
|
||||||
isNewIdentifierLocation: completion.hasIndexSignature,
|
|
||||||
location: file.AsNode(),
|
|
||||||
contextToken: contextToken,
|
|
||||||
}
|
|
||||||
_, items := l.getCompletionEntriesFromSymbols(
|
|
||||||
ctx,
|
|
||||||
data,
|
|
||||||
contextToken, /*replacementToken*/
|
|
||||||
position,
|
|
||||||
file,
|
|
||||||
preferences,
|
|
||||||
options,
|
|
||||||
clientOptions,
|
|
||||||
)
|
|
||||||
defaultCommitCharacters := getDefaultCommitCharacters(completion.hasIndexSignature)
|
|
||||||
itemDefaults := l.setItemDefaults(
|
|
||||||
clientOptions,
|
|
||||||
position,
|
|
||||||
file,
|
|
||||||
items,
|
|
||||||
&defaultCommitCharacters,
|
|
||||||
optionalReplacementRange,
|
|
||||||
)
|
|
||||||
return &lsproto.CompletionList{
|
|
||||||
IsIncomplete: false,
|
|
||||||
ItemDefaults: itemDefaults,
|
|
||||||
Items: items,
|
|
||||||
}
|
|
||||||
case completion.fromTypes != nil:
|
|
||||||
completion := completion.fromTypes
|
|
||||||
var quoteChar printer.QuoteChar
|
|
||||||
if contextToken.Kind == ast.KindNoSubstitutionTemplateLiteral {
|
|
||||||
quoteChar = printer.QuoteCharBacktick
|
|
||||||
} else if strings.HasPrefix(contextToken.Text(), "'") {
|
|
||||||
quoteChar = printer.QuoteCharSingleQuote
|
|
||||||
} else {
|
|
||||||
quoteChar = printer.QuoteCharDoubleQuote
|
|
||||||
}
|
|
||||||
items := core.Map(completion.types, func(t *checker.StringLiteralType) *lsproto.CompletionItem {
|
|
||||||
name := printer.EscapeString(t.AsLiteralType().Value().(string), quoteChar)
|
|
||||||
return l.createLSPCompletionItem(
|
|
||||||
name,
|
|
||||||
"", /*insertText*/
|
|
||||||
"", /*filterText*/
|
|
||||||
SortTextLocationPriority,
|
|
||||||
ScriptElementKindString,
|
|
||||||
collections.Set[ScriptElementKindModifier]{},
|
|
||||||
l.getReplacementRangeForContextToken(file, contextToken, position),
|
|
||||||
nil, /*commitCharacters*/
|
|
||||||
nil, /*labelDetails*/
|
|
||||||
file,
|
|
||||||
position,
|
|
||||||
clientOptions,
|
|
||||||
false, /*isMemberCompletion*/
|
|
||||||
false, /*isSnippet*/
|
|
||||||
false, /*hasAction*/
|
|
||||||
false, /*preselect*/
|
|
||||||
"", /*source*/
|
|
||||||
nil, /*autoImportEntryData*/
|
|
||||||
)
|
|
||||||
})
|
|
||||||
defaultCommitCharacters := getDefaultCommitCharacters(completion.isNewIdentifier)
|
|
||||||
itemDefaults := l.setItemDefaults(
|
|
||||||
clientOptions,
|
|
||||||
position,
|
|
||||||
file,
|
|
||||||
items,
|
|
||||||
&defaultCommitCharacters,
|
|
||||||
nil, /*optionalReplacementSpan*/
|
|
||||||
)
|
|
||||||
return &lsproto.CompletionList{
|
|
||||||
IsIncomplete: false,
|
|
||||||
ItemDefaults: itemDefaults,
|
|
||||||
Items: items,
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) convertPathCompletions(
|
|
||||||
pathCompletions []*pathCompletion,
|
|
||||||
file *ast.SourceFile,
|
|
||||||
position int,
|
|
||||||
clientOptions *lsproto.CompletionClientCapabilities,
|
|
||||||
) *lsproto.CompletionList {
|
|
||||||
isNewIdentifierLocation := true // The user may type in a path that doesn't yet exist, creating a "new identifier" with respect to the collection of identifiers the server is aware of.
|
|
||||||
defaultCommitCharacters := getDefaultCommitCharacters(isNewIdentifierLocation)
|
|
||||||
items := core.Map(pathCompletions, func(pathCompletion *pathCompletion) *lsproto.CompletionItem {
|
|
||||||
replacementSpan := l.createLspRangeFromBounds(pathCompletion.textRange.Pos(), pathCompletion.textRange.End(), file)
|
|
||||||
return l.createLSPCompletionItem(
|
|
||||||
pathCompletion.name,
|
|
||||||
"", /*insertText*/
|
|
||||||
"", /*filterText*/
|
|
||||||
SortTextLocationPriority,
|
|
||||||
pathCompletion.kind,
|
|
||||||
*collections.NewSetFromItems(kindModifiersFromExtension(pathCompletion.extension)),
|
|
||||||
replacementSpan,
|
|
||||||
nil, /*commitCharacters*/
|
|
||||||
nil, /*labelDetails*/
|
|
||||||
file,
|
|
||||||
position,
|
|
||||||
clientOptions,
|
|
||||||
false, /*isMemberCompletion*/
|
|
||||||
false, /*isSnippet*/
|
|
||||||
false, /*hasAction*/
|
|
||||||
false, /*preselect*/
|
|
||||||
"", /*source*/
|
|
||||||
nil, /*autoImportEntryData*/
|
|
||||||
)
|
|
||||||
})
|
|
||||||
itemDefaults := l.setItemDefaults(
|
|
||||||
clientOptions,
|
|
||||||
position,
|
|
||||||
file,
|
|
||||||
items,
|
|
||||||
&defaultCommitCharacters,
|
|
||||||
nil, /*optionalReplacementSpan*/
|
|
||||||
)
|
|
||||||
return &lsproto.CompletionList{
|
|
||||||
IsIncomplete: false,
|
|
||||||
ItemDefaults: itemDefaults,
|
|
||||||
Items: items,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) getStringLiteralCompletionEntries(
|
|
||||||
ctx context.Context,
|
|
||||||
file *ast.SourceFile,
|
|
||||||
node *ast.StringLiteralLike,
|
|
||||||
position int,
|
|
||||||
preferences *UserPreferences,
|
|
||||||
) *stringLiteralCompletions {
|
|
||||||
typeChecker, done := l.GetProgram().GetTypeCheckerForFile(ctx, file)
|
|
||||||
defer done()
|
|
||||||
parent := walkUpParentheses(node.Parent)
|
|
||||||
switch parent.Kind {
|
|
||||||
case ast.KindLiteralType:
|
|
||||||
grandparent := walkUpParentheses(parent.Parent)
|
|
||||||
if grandparent.Kind == ast.KindImportType {
|
|
||||||
return getStringLiteralCompletionsFromModuleNames(
|
|
||||||
file,
|
|
||||||
node,
|
|
||||||
l.GetProgram(),
|
|
||||||
preferences,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return fromUnionableLiteralType(grandparent, parent, position, typeChecker)
|
|
||||||
case ast.KindPropertyAssignment:
|
|
||||||
if ast.IsObjectLiteralExpression(parent.Parent) && parent.Name() == node {
|
|
||||||
// Get quoted name of properties of the object literal expression
|
|
||||||
// i.e. interface ConfigFiles {
|
|
||||||
// 'jspm:dev': string
|
|
||||||
// }
|
|
||||||
// let files: ConfigFiles = {
|
|
||||||
// '/*completion position*/'
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// function foo(c: ConfigFiles) {}
|
|
||||||
// foo({
|
|
||||||
// '/*completion position*/'
|
|
||||||
// });
|
|
||||||
return &stringLiteralCompletions{
|
|
||||||
fromProperties: stringLiteralCompletionsForObjectLiteral(typeChecker, parent.Parent),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result := fromContextualType(checker.ContextFlagsCompletions, node, typeChecker)
|
|
||||||
if result != nil {
|
|
||||||
return &stringLiteralCompletions{
|
|
||||||
fromTypes: result,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &stringLiteralCompletions{
|
|
||||||
fromTypes: fromContextualType(checker.ContextFlagsNone, node, typeChecker),
|
|
||||||
}
|
|
||||||
case ast.KindElementAccessExpression:
|
|
||||||
expression := parent.Expression()
|
|
||||||
argumentExpression := parent.AsElementAccessExpression().ArgumentExpression
|
|
||||||
if node == ast.SkipParentheses(argumentExpression) {
|
|
||||||
// Get all names of properties on the expression
|
|
||||||
// i.e. interface A {
|
|
||||||
// 'prop1': string
|
|
||||||
// }
|
|
||||||
// let a: A;
|
|
||||||
// a['/*completion position*/']
|
|
||||||
t := typeChecker.GetTypeAtLocation(expression)
|
|
||||||
return &stringLiteralCompletions{
|
|
||||||
fromProperties: stringLiteralCompletionsFromProperties(t, typeChecker),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case ast.KindCallExpression, ast.KindNewExpression, ast.KindJsxAttribute:
|
|
||||||
if !isRequireCallArgument(node) && !ast.IsImportCall(parent) {
|
|
||||||
var argumentNode *ast.Node
|
|
||||||
if parent.Kind == ast.KindJsxAttribute {
|
|
||||||
argumentNode = parent.Parent
|
|
||||||
} else {
|
|
||||||
argumentNode = node
|
|
||||||
}
|
|
||||||
argumentInfo := getArgumentInfoForCompletions(argumentNode, position, file, typeChecker)
|
|
||||||
// Get string literal completions from specialized signatures of the target
|
|
||||||
// i.e. declare function f(a: 'A');
|
|
||||||
// f("/*completion position*/")
|
|
||||||
if argumentInfo == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
result := getStringLiteralCompletionsFromSignature(argumentInfo.invocation, node, argumentInfo, typeChecker)
|
|
||||||
if result != nil {
|
|
||||||
return &stringLiteralCompletions{
|
|
||||||
fromTypes: result,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &stringLiteralCompletions{
|
|
||||||
fromTypes: fromContextualType(checker.ContextFlagsNone, node, typeChecker),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fallthrough // is `require("")` or `require(""` or `import("")`
|
|
||||||
case ast.KindImportDeclaration, ast.KindExportDeclaration, ast.KindExternalModuleReference, ast.KindJSDocImportTag:
|
|
||||||
// Get all known external module names or complete a path to a module
|
|
||||||
// i.e. import * as ns from "/*completion position*/";
|
|
||||||
// var y = import("/*completion position*/");
|
|
||||||
// import x = require("/*completion position*/");
|
|
||||||
// var y = require("/*completion position*/");
|
|
||||||
// export * from "/*completion position*/";
|
|
||||||
return getStringLiteralCompletionsFromModuleNames(file, node, l.GetProgram(), preferences)
|
|
||||||
case ast.KindCaseClause:
|
|
||||||
tracker := newCaseClauseTracker(typeChecker, parent.Parent.AsCaseBlock().Clauses.Nodes)
|
|
||||||
contextualTypes := fromContextualType(checker.ContextFlagsCompletions, node, typeChecker)
|
|
||||||
if contextualTypes == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
literals := core.Filter(contextualTypes.types, func(t *checker.StringLiteralType) bool {
|
|
||||||
return !tracker.hasValue(t.AsLiteralType().Value())
|
|
||||||
})
|
|
||||||
return &stringLiteralCompletions{
|
|
||||||
fromTypes: &completionsFromTypes{
|
|
||||||
types: literals,
|
|
||||||
isNewIdentifier: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
case ast.KindImportSpecifier, ast.KindExportSpecifier:
|
|
||||||
// Complete string aliases in `import { "|" } from` and `export { "|" } from`
|
|
||||||
specifier := parent
|
|
||||||
if propertyName := specifier.PropertyName(); propertyName != nil && node != propertyName {
|
|
||||||
return nil // Don't complete in `export { "..." as "|" } from`
|
|
||||||
}
|
|
||||||
namedImportsOrExports := specifier.Parent
|
|
||||||
var moduleSpecifier *ast.Node
|
|
||||||
if namedImportsOrExports.Kind == ast.KindNamedImports {
|
|
||||||
moduleSpecifier = namedImportsOrExports.Parent.Parent
|
|
||||||
} else {
|
|
||||||
moduleSpecifier = namedImportsOrExports.Parent
|
|
||||||
}
|
|
||||||
if moduleSpecifier == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
moduleSpecifierSymbol := typeChecker.GetSymbolAtLocation(moduleSpecifier)
|
|
||||||
if moduleSpecifierSymbol == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
exports := typeChecker.GetExportsAndPropertiesOfModule(moduleSpecifierSymbol)
|
|
||||||
existing := collections.NewSetFromItems(core.Map(namedImportsOrExports.Elements(), func(n *ast.Node) string {
|
|
||||||
if n.PropertyName() != nil {
|
|
||||||
return n.PropertyName().Text()
|
|
||||||
}
|
|
||||||
return n.Name().Text()
|
|
||||||
})...)
|
|
||||||
uniques := core.Filter(exports, func(e *ast.Symbol) bool {
|
|
||||||
return e.Name != ast.InternalSymbolNameDefault && !existing.Has(e.Name)
|
|
||||||
})
|
|
||||||
return &stringLiteralCompletions{
|
|
||||||
fromProperties: &completionsFromProperties{
|
|
||||||
symbols: uniques,
|
|
||||||
hasIndexSignature: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
case ast.KindBinaryExpression:
|
|
||||||
if parent.AsBinaryExpression().OperatorToken.Kind == ast.KindInKeyword {
|
|
||||||
t := typeChecker.GetTypeAtLocation(parent.AsBinaryExpression().Right)
|
|
||||||
properties := getPropertiesForCompletion(t, typeChecker)
|
|
||||||
return &stringLiteralCompletions{
|
|
||||||
fromProperties: &completionsFromProperties{
|
|
||||||
symbols: core.Filter(properties, func(s *ast.Symbol) bool {
|
|
||||||
return s.ValueDeclaration == nil || !ast.IsPrivateIdentifierClassElementDeclaration(s.ValueDeclaration)
|
|
||||||
}),
|
|
||||||
hasIndexSignature: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &stringLiteralCompletions{
|
|
||||||
fromTypes: fromContextualType(checker.ContextFlagsNone, node, typeChecker),
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
result := fromContextualType(checker.ContextFlagsCompletions, node, typeChecker)
|
|
||||||
if result != nil {
|
|
||||||
return &stringLiteralCompletions{
|
|
||||||
fromTypes: result,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &stringLiteralCompletions{
|
|
||||||
fromTypes: fromContextualType(checker.ContextFlagsNone, node, typeChecker),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fromContextualType(contextFlags checker.ContextFlags, node *ast.Node, typeChecker *checker.Checker) *completionsFromTypes {
|
|
||||||
// Get completion for string literal from string literal type
|
|
||||||
// i.e. var x: "hi" | "hello" = "/*completion position*/"
|
|
||||||
types := getStringLiteralTypes(getContextualTypeFromParent(node, typeChecker, contextFlags), nil, typeChecker)
|
|
||||||
if len(types) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &completionsFromTypes{
|
|
||||||
types: types,
|
|
||||||
isNewIdentifier: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fromUnionableLiteralType(
|
|
||||||
grandparent *ast.Node,
|
|
||||||
parent *ast.Node,
|
|
||||||
position int,
|
|
||||||
typeChecker *checker.Checker,
|
|
||||||
) *stringLiteralCompletions {
|
|
||||||
switch grandparent.Kind {
|
|
||||||
case ast.KindExpressionWithTypeArguments, ast.KindTypeReference:
|
|
||||||
typeArgument := ast.FindAncestor(parent, func(n *ast.Node) bool { return n.Parent == grandparent })
|
|
||||||
if typeArgument != nil {
|
|
||||||
t := typeChecker.GetTypeArgumentConstraint(typeArgument)
|
|
||||||
return &stringLiteralCompletions{
|
|
||||||
fromTypes: &completionsFromTypes{
|
|
||||||
types: getStringLiteralTypes(t, nil, typeChecker),
|
|
||||||
isNewIdentifier: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case ast.KindIndexedAccessType:
|
|
||||||
// Get all apparent property names
|
|
||||||
// i.e. interface Foo {
|
|
||||||
// foo: string;
|
|
||||||
// bar: string;
|
|
||||||
// }
|
|
||||||
// let x: Foo["/*completion position*/"]
|
|
||||||
indexType := grandparent.AsIndexedAccessTypeNode().IndexType
|
|
||||||
objectType := grandparent.AsIndexedAccessTypeNode().ObjectType
|
|
||||||
if !indexType.Loc.ContainsInclusive(position) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
t := typeChecker.GetTypeFromTypeNode(objectType)
|
|
||||||
return &stringLiteralCompletions{
|
|
||||||
fromProperties: stringLiteralCompletionsFromProperties(t, typeChecker),
|
|
||||||
}
|
|
||||||
case ast.KindUnionType:
|
|
||||||
result := fromUnionableLiteralType(
|
|
||||||
walkUpParentheses(grandparent.Parent),
|
|
||||||
parent,
|
|
||||||
position,
|
|
||||||
typeChecker)
|
|
||||||
if result == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
alreadyUsedTypes := getAlreadyUsedTypesInStringLiteralUnion(grandparent, parent)
|
|
||||||
switch {
|
|
||||||
case result.fromProperties != nil:
|
|
||||||
result := result.fromProperties
|
|
||||||
return &stringLiteralCompletions{
|
|
||||||
fromProperties: &completionsFromProperties{
|
|
||||||
symbols: core.Filter(
|
|
||||||
result.symbols,
|
|
||||||
func(s *ast.Symbol) bool { return !slices.Contains(alreadyUsedTypes, s.Name) },
|
|
||||||
),
|
|
||||||
hasIndexSignature: result.hasIndexSignature,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
case result.fromTypes != nil:
|
|
||||||
result := result.fromTypes
|
|
||||||
return &stringLiteralCompletions{
|
|
||||||
fromTypes: &completionsFromTypes{
|
|
||||||
types: core.Filter(result.types, func(t *checker.StringLiteralType) bool {
|
|
||||||
return !slices.Contains(alreadyUsedTypes, t.AsLiteralType().Value().(string))
|
|
||||||
}),
|
|
||||||
isNewIdentifier: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringLiteralCompletionsForObjectLiteral(
|
|
||||||
typeChecker *checker.Checker,
|
|
||||||
objectLiteralExpression *ast.ObjectLiteralExpressionNode,
|
|
||||||
) *completionsFromProperties {
|
|
||||||
contextualType := typeChecker.GetContextualType(objectLiteralExpression, checker.ContextFlagsNone)
|
|
||||||
if contextualType == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
completionsType := typeChecker.GetContextualType(objectLiteralExpression, checker.ContextFlagsCompletions)
|
|
||||||
symbols := getPropertiesForObjectExpression(
|
|
||||||
contextualType,
|
|
||||||
completionsType,
|
|
||||||
objectLiteralExpression,
|
|
||||||
typeChecker)
|
|
||||||
|
|
||||||
return &completionsFromProperties{
|
|
||||||
symbols: symbols,
|
|
||||||
hasIndexSignature: hasIndexSignature(contextualType, typeChecker),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringLiteralCompletionsFromProperties(t *checker.Type, typeChecker *checker.Checker) *completionsFromProperties {
|
|
||||||
return &completionsFromProperties{
|
|
||||||
symbols: core.Filter(typeChecker.GetApparentProperties(t), func(s *ast.Symbol) bool {
|
|
||||||
return !(s.ValueDeclaration != nil && ast.IsPrivateIdentifierClassElementDeclaration(s.ValueDeclaration))
|
|
||||||
}),
|
|
||||||
hasIndexSignature: hasIndexSignature(t, typeChecker),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getStringLiteralCompletionsFromModuleNames(
|
|
||||||
file *ast.SourceFile,
|
|
||||||
node *ast.LiteralExpression,
|
|
||||||
program *compiler.Program,
|
|
||||||
preferences *UserPreferences,
|
|
||||||
) *stringLiteralCompletions {
|
|
||||||
// !!! needs `getModeForUsageLocationWorker`
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func walkUpParentheses(node *ast.Node) *ast.Node {
|
|
||||||
switch node.Kind {
|
|
||||||
case ast.KindParenthesizedType:
|
|
||||||
return ast.WalkUpParenthesizedTypes(node)
|
|
||||||
case ast.KindParenthesizedExpression:
|
|
||||||
return ast.WalkUpParenthesizedExpressions(node)
|
|
||||||
default:
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getStringLiteralTypes(t *checker.Type, uniques *collections.Set[string], typeChecker *checker.Checker) []*checker.StringLiteralType {
|
|
||||||
if t == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if uniques == nil {
|
|
||||||
uniques = &collections.Set[string]{}
|
|
||||||
}
|
|
||||||
t = skipConstraint(t, typeChecker)
|
|
||||||
if t.IsUnion() {
|
|
||||||
var types []*checker.StringLiteralType
|
|
||||||
for _, elementType := range t.Types() {
|
|
||||||
types = append(types, getStringLiteralTypes(elementType, uniques, typeChecker)...)
|
|
||||||
}
|
|
||||||
return types
|
|
||||||
}
|
|
||||||
if t.IsStringLiteral() && !t.IsEnumLiteral() && uniques.AddIfAbsent(t.AsLiteralType().Value().(string)) {
|
|
||||||
return []*checker.StringLiteralType{t}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAlreadyUsedTypesInStringLiteralUnion(union *ast.UnionType, current *ast.LiteralType) []string {
|
|
||||||
typesList := union.AsUnionTypeNode().Types
|
|
||||||
if typesList == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var values []string
|
|
||||||
for _, typeNode := range typesList.Nodes {
|
|
||||||
if typeNode != current && ast.IsLiteralTypeNode(typeNode) &&
|
|
||||||
ast.IsStringLiteral(typeNode.AsLiteralTypeNode().Literal) {
|
|
||||||
values = append(values, typeNode.AsLiteralTypeNode().Literal.Text())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasIndexSignature(t *checker.Type, typeChecker *checker.Checker) bool {
|
|
||||||
return typeChecker.GetStringIndexType(t) != nil || typeChecker.GetNumberIndexType(t) != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Matches
|
|
||||||
//
|
|
||||||
// require(""
|
|
||||||
// require("")
|
|
||||||
func isRequireCallArgument(node *ast.Node) bool {
|
|
||||||
return ast.IsCallExpression(node.Parent) && len(node.Parent.Arguments()) > 0 && node.Parent.Arguments()[0] == node &&
|
|
||||||
ast.IsIdentifier(node.Parent.Expression()) && node.Parent.Expression().Text() == "require"
|
|
||||||
}
|
|
||||||
|
|
||||||
func kindModifiersFromExtension(extension string) ScriptElementKindModifier {
|
|
||||||
switch extension {
|
|
||||||
case tspath.ExtensionDts:
|
|
||||||
return ScriptElementKindModifierDts
|
|
||||||
case tspath.ExtensionJs:
|
|
||||||
return ScriptElementKindModifierJs
|
|
||||||
case tspath.ExtensionJson:
|
|
||||||
return ScriptElementKindModifierJson
|
|
||||||
case tspath.ExtensionJsx:
|
|
||||||
return ScriptElementKindModifierJsx
|
|
||||||
case tspath.ExtensionTs:
|
|
||||||
return ScriptElementKindModifierTs
|
|
||||||
case tspath.ExtensionTsx:
|
|
||||||
return ScriptElementKindModifierTsx
|
|
||||||
case tspath.ExtensionDmts:
|
|
||||||
return ScriptElementKindModifierDmts
|
|
||||||
case tspath.ExtensionMjs:
|
|
||||||
return ScriptElementKindModifierMjs
|
|
||||||
case tspath.ExtensionMts:
|
|
||||||
return ScriptElementKindModifierMts
|
|
||||||
case tspath.ExtensionDcts:
|
|
||||||
return ScriptElementKindModifierDcts
|
|
||||||
case tspath.ExtensionCjs:
|
|
||||||
return ScriptElementKindModifierCjs
|
|
||||||
case tspath.ExtensionCts:
|
|
||||||
return ScriptElementKindModifierCts
|
|
||||||
case tspath.ExtensionTsBuildInfo:
|
|
||||||
panic(fmt.Sprintf("Extension %v is unsupported.", tspath.ExtensionTsBuildInfo))
|
|
||||||
case "":
|
|
||||||
return ScriptElementKindModifierNone
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("Unexpected extension: %v", extension))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getStringLiteralCompletionsFromSignature(
|
|
||||||
call *ast.CallLikeExpression,
|
|
||||||
arg *ast.StringLiteralLike,
|
|
||||||
argumentInfo *argumentInfoForCompletions,
|
|
||||||
typeChecker *checker.Checker,
|
|
||||||
) *completionsFromTypes {
|
|
||||||
isNewIdentifier := false
|
|
||||||
uniques := collections.Set[string]{}
|
|
||||||
var editingArgument *ast.Node
|
|
||||||
if ast.IsJsxOpeningLikeElement(call) {
|
|
||||||
editingArgument = ast.FindAncestor(arg.Parent, ast.IsJsxAttribute)
|
|
||||||
if editingArgument == nil {
|
|
||||||
panic("Expected jsx opening-like element to have a jsx attribute as ancestor.")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
editingArgument = arg
|
|
||||||
}
|
|
||||||
candidates := typeChecker.GetCandidateSignaturesForStringLiteralCompletions(call, editingArgument)
|
|
||||||
var types []*checker.StringLiteralType
|
|
||||||
for _, candidate := range candidates {
|
|
||||||
if !candidate.HasRestParameter() && argumentInfo.argumentCount > len(candidate.Parameters()) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
t := typeChecker.GetTypeParameterAtPosition(candidate, argumentInfo.argumentIndex)
|
|
||||||
if ast.IsJsxOpeningLikeElement(call) {
|
|
||||||
propType := typeChecker.GetTypeOfPropertyOfType(t, editingArgument.AsJsxAttribute().Name().Text())
|
|
||||||
if propType != nil {
|
|
||||||
t = propType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isNewIdentifier = isNewIdentifier || t.IsString()
|
|
||||||
types = append(types, getStringLiteralTypes(t, &uniques, typeChecker)...)
|
|
||||||
}
|
|
||||||
if len(types) > 0 {
|
|
||||||
return &completionsFromTypes{
|
|
||||||
types: types,
|
|
||||||
isNewIdentifier: isNewIdentifier,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) getStringLiteralCompletionDetails(
|
|
||||||
ctx context.Context,
|
|
||||||
checker *checker.Checker,
|
|
||||||
item *lsproto.CompletionItem,
|
|
||||||
name string,
|
|
||||||
file *ast.SourceFile,
|
|
||||||
position int,
|
|
||||||
contextToken *ast.Node,
|
|
||||||
preferences *UserPreferences,
|
|
||||||
) *lsproto.CompletionItem {
|
|
||||||
if contextToken == nil || !ast.IsStringLiteralLike(contextToken) {
|
|
||||||
return item
|
|
||||||
}
|
|
||||||
completions := l.getStringLiteralCompletionEntries(
|
|
||||||
ctx,
|
|
||||||
file,
|
|
||||||
contextToken,
|
|
||||||
position,
|
|
||||||
preferences,
|
|
||||||
)
|
|
||||||
if completions == nil {
|
|
||||||
return item
|
|
||||||
}
|
|
||||||
return stringLiteralCompletionDetails(item, name, contextToken, completions, file, checker)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringLiteralCompletionDetails(
|
|
||||||
item *lsproto.CompletionItem,
|
|
||||||
name string,
|
|
||||||
location *ast.Node,
|
|
||||||
completion *stringLiteralCompletions,
|
|
||||||
file *ast.SourceFile,
|
|
||||||
checker *checker.Checker,
|
|
||||||
) *lsproto.CompletionItem {
|
|
||||||
switch {
|
|
||||||
case completion.fromPaths != nil:
|
|
||||||
pathCompletions := completion.fromPaths
|
|
||||||
for _, pathCompletion := range pathCompletions {
|
|
||||||
if pathCompletion.name == name {
|
|
||||||
return createCompletionDetails(item, name, "" /*documentation*/)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case completion.fromProperties != nil:
|
|
||||||
properties := completion.fromProperties
|
|
||||||
for _, symbol := range properties.symbols {
|
|
||||||
if symbol.Name == name {
|
|
||||||
return createCompletionDetailsForSymbol(item, symbol, checker, location, nil /*actions*/)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case completion.fromTypes != nil:
|
|
||||||
types := completion.fromTypes
|
|
||||||
for _, t := range types.types {
|
|
||||||
if t.AsLiteralType().Value().(string) == name {
|
|
||||||
return createCompletionDetails(item, name, "" /*documentation*/)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return item
|
|
||||||
}
|
|
||||||
@ -1,389 +0,0 @@
|
|||||||
package ls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/checker"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ScriptElementKind string
|
|
||||||
|
|
||||||
const (
|
|
||||||
ScriptElementKindUnknown ScriptElementKind = ""
|
|
||||||
ScriptElementKindWarning ScriptElementKind = "warning"
|
|
||||||
// predefined type (void) or keyword (class)
|
|
||||||
ScriptElementKindKeyword ScriptElementKind = "keyword"
|
|
||||||
// top level script node
|
|
||||||
ScriptElementKindScriptElement ScriptElementKind = "script"
|
|
||||||
// module foo {}
|
|
||||||
ScriptElementKindModuleElement ScriptElementKind = "module"
|
|
||||||
// class X {}
|
|
||||||
ScriptElementKindClassElement ScriptElementKind = "class"
|
|
||||||
// var x = class X {}
|
|
||||||
ScriptElementKindLocalClassElement ScriptElementKind = "local class"
|
|
||||||
// interface Y {}
|
|
||||||
ScriptElementKindInterfaceElement ScriptElementKind = "interface"
|
|
||||||
// type T = ...
|
|
||||||
ScriptElementKindTypeElement ScriptElementKind = "type"
|
|
||||||
// enum E {}
|
|
||||||
ScriptElementKindEnumElement ScriptElementKind = "enum"
|
|
||||||
ScriptElementKindEnumMemberElement ScriptElementKind = "enum member"
|
|
||||||
// Inside module and script only.
|
|
||||||
// const v = ...
|
|
||||||
ScriptElementKindVariableElement ScriptElementKind = "var"
|
|
||||||
// Inside function.
|
|
||||||
ScriptElementKindLocalVariableElement ScriptElementKind = "local var"
|
|
||||||
// using foo = ...
|
|
||||||
ScriptElementKindVariableUsingElement ScriptElementKind = "using"
|
|
||||||
// await using foo = ...
|
|
||||||
ScriptElementKindVariableAwaitUsingElement ScriptElementKind = "await using"
|
|
||||||
// Inside module and script only.
|
|
||||||
// function f() {}
|
|
||||||
ScriptElementKindFunctionElement ScriptElementKind = "function"
|
|
||||||
// Inside function.
|
|
||||||
ScriptElementKindLocalFunctionElement ScriptElementKind = "local function"
|
|
||||||
// class X { [public|private]* foo() {} }
|
|
||||||
ScriptElementKindMemberFunctionElement ScriptElementKind = "method"
|
|
||||||
// class X { [public|private]* [get|set] foo:number; }
|
|
||||||
ScriptElementKindMemberGetAccessorElement ScriptElementKind = "getter"
|
|
||||||
ScriptElementKindMemberSetAccessorElement ScriptElementKind = "setter"
|
|
||||||
// class X { [public|private]* foo:number; }
|
|
||||||
// interface Y { foo:number; }
|
|
||||||
ScriptElementKindMemberVariableElement ScriptElementKind = "property"
|
|
||||||
// class X { [public|private]* accessor foo: number; }
|
|
||||||
ScriptElementKindMemberAccessorVariableElement ScriptElementKind = "accessor"
|
|
||||||
// class X { constructor() { } }
|
|
||||||
// class X { static { } }
|
|
||||||
ScriptElementKindConstructorImplementationElement ScriptElementKind = "constructor"
|
|
||||||
// interface Y { ():number; }
|
|
||||||
ScriptElementKindCallSignatureElement ScriptElementKind = "call"
|
|
||||||
// interface Y { []:number; }
|
|
||||||
ScriptElementKindIndexSignatureElement ScriptElementKind = "index"
|
|
||||||
// interface Y { new():Y; }
|
|
||||||
ScriptElementKindConstructSignatureElement ScriptElementKind = "construct"
|
|
||||||
// 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"
|
|
||||||
// String literal
|
|
||||||
ScriptElementKindString ScriptElementKind = "string"
|
|
||||||
// Jsdoc @link: in `{@link C link text}`, the before and after text "{@link " and "}"
|
|
||||||
ScriptElementKindLink ScriptElementKind = "link"
|
|
||||||
// Jsdoc @link: in `{@link C link text}`, the entity name "C"
|
|
||||||
ScriptElementKindLinkName ScriptElementKind = "link name"
|
|
||||||
// Jsdoc @link: in `{@link C link text}`, the link text "link text"
|
|
||||||
ScriptElementKindLinkText ScriptElementKind = "link text"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ScriptElementKindModifier string
|
|
||||||
|
|
||||||
const (
|
|
||||||
ScriptElementKindModifierNone ScriptElementKindModifier = ""
|
|
||||||
ScriptElementKindModifierPublic ScriptElementKindModifier = "public"
|
|
||||||
ScriptElementKindModifierPrivate ScriptElementKindModifier = "private"
|
|
||||||
ScriptElementKindModifierProtected ScriptElementKindModifier = "protected"
|
|
||||||
ScriptElementKindModifierExported ScriptElementKindModifier = "export"
|
|
||||||
ScriptElementKindModifierAmbient ScriptElementKindModifier = "declare"
|
|
||||||
ScriptElementKindModifierStatic ScriptElementKindModifier = "static"
|
|
||||||
ScriptElementKindModifierAbstract ScriptElementKindModifier = "abstract"
|
|
||||||
ScriptElementKindModifierOptional ScriptElementKindModifier = "optional"
|
|
||||||
ScriptElementKindModifierDeprecated ScriptElementKindModifier = "deprecated"
|
|
||||||
ScriptElementKindModifierDts ScriptElementKindModifier = ".d.ts"
|
|
||||||
ScriptElementKindModifierTs ScriptElementKindModifier = ".ts"
|
|
||||||
ScriptElementKindModifierTsx ScriptElementKindModifier = ".tsx"
|
|
||||||
ScriptElementKindModifierJs ScriptElementKindModifier = ".js"
|
|
||||||
ScriptElementKindModifierJsx ScriptElementKindModifier = ".jsx"
|
|
||||||
ScriptElementKindModifierJson ScriptElementKindModifier = ".json"
|
|
||||||
ScriptElementKindModifierDmts ScriptElementKindModifier = ".d.mts"
|
|
||||||
ScriptElementKindModifierMts ScriptElementKindModifier = ".mts"
|
|
||||||
ScriptElementKindModifierMjs ScriptElementKindModifier = ".mjs"
|
|
||||||
ScriptElementKindModifierDcts ScriptElementKindModifier = ".d.cts"
|
|
||||||
ScriptElementKindModifierCts ScriptElementKindModifier = ".cts"
|
|
||||||
ScriptElementKindModifierCjs ScriptElementKindModifier = ".cjs"
|
|
||||||
)
|
|
||||||
|
|
||||||
var fileExtensionKindModifiers = []ScriptElementKindModifier{
|
|
||||||
ScriptElementKindModifierDts,
|
|
||||||
ScriptElementKindModifierTs,
|
|
||||||
ScriptElementKindModifierTsx,
|
|
||||||
ScriptElementKindModifierJs,
|
|
||||||
ScriptElementKindModifierJsx,
|
|
||||||
ScriptElementKindModifierJson,
|
|
||||||
ScriptElementKindModifierDmts,
|
|
||||||
ScriptElementKindModifierMts,
|
|
||||||
ScriptElementKindModifierMjs,
|
|
||||||
ScriptElementKindModifierDcts,
|
|
||||||
ScriptElementKindModifierCts,
|
|
||||||
ScriptElementKindModifierCjs,
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
if flags&ast.SymbolFlagsClass != 0 {
|
|
||||||
decl := ast.GetDeclarationOfKind(symbol, ast.KindClassExpression)
|
|
||||||
if decl != nil {
|
|
||||||
return ScriptElementKindLocalClassElement
|
|
||||||
}
|
|
||||||
return ScriptElementKindClassElement
|
|
||||||
}
|
|
||||||
if flags&ast.SymbolFlagsEnum != 0 {
|
|
||||||
return ScriptElementKindEnumElement
|
|
||||||
}
|
|
||||||
if flags&ast.SymbolFlagsTypeAlias != 0 {
|
|
||||||
return ScriptElementKindTypeElement
|
|
||||||
}
|
|
||||||
if flags&ast.SymbolFlagsInterface != 0 {
|
|
||||||
return ScriptElementKindInterfaceElement
|
|
||||||
}
|
|
||||||
if flags&ast.SymbolFlagsTypeParameter != 0 {
|
|
||||||
return ScriptElementKindTypeParameterElement
|
|
||||||
}
|
|
||||||
if flags&ast.SymbolFlagsEnumMember != 0 {
|
|
||||||
return ScriptElementKindEnumMemberElement
|
|
||||||
}
|
|
||||||
if flags&ast.SymbolFlagsAlias != 0 {
|
|
||||||
return ScriptElementKindAlias
|
|
||||||
}
|
|
||||||
if flags&ast.SymbolFlagsModule != 0 {
|
|
||||||
return ScriptElementKindModuleElement
|
|
||||||
}
|
|
||||||
|
|
||||||
return ScriptElementKindUnknown
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker *checker.Checker, symbol *ast.Symbol, location *ast.Node) ScriptElementKind {
|
|
||||||
roots := typeChecker.GetRootSymbols(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 {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := checker.GetCombinedLocalAndExportSymbolFlags(symbol)
|
|
||||||
if flags&ast.SymbolFlagsVariable != 0 {
|
|
||||||
if isFirstDeclarationOfSymbolParameter(symbol) {
|
|
||||||
return ScriptElementKindParameterElement
|
|
||||||
} else if symbol.ValueDeclaration != nil && ast.IsVarConst(symbol.ValueDeclaration) {
|
|
||||||
return ScriptElementKindConstElement
|
|
||||||
} else if symbol.ValueDeclaration != nil && ast.IsVarUsing(symbol.ValueDeclaration) {
|
|
||||||
return ScriptElementKindVariableUsingElement
|
|
||||||
} else if symbol.ValueDeclaration != nil && ast.IsVarAwaitUsing(symbol.ValueDeclaration) {
|
|
||||||
return ScriptElementKindVariableAwaitUsingElement
|
|
||||||
} else if core.Some(symbol.Declarations, ast.IsLet) {
|
|
||||||
return ScriptElementKindLetElement
|
|
||||||
}
|
|
||||||
if isLocalVariableOrFunction(symbol) {
|
|
||||||
return ScriptElementKindLocalVariableElement
|
|
||||||
}
|
|
||||||
return ScriptElementKindVariableElement
|
|
||||||
}
|
|
||||||
if flags&ast.SymbolFlagsFunction != 0 {
|
|
||||||
if isLocalVariableOrFunction(symbol) {
|
|
||||||
return ScriptElementKindLocalFunctionElement
|
|
||||||
}
|
|
||||||
return ScriptElementKindFunctionElement
|
|
||||||
}
|
|
||||||
// FIXME: getter and setter use the same symbol. And it is rare to use only setter without getter, so in most cases the symbol always has getter flag.
|
|
||||||
// So, even when the location is just on the declaration of setter, this function returns getter.
|
|
||||||
if flags&ast.SymbolFlagsGetAccessor != 0 {
|
|
||||||
return ScriptElementKindMemberGetAccessorElement
|
|
||||||
}
|
|
||||||
if flags&ast.SymbolFlagsSetAccessor != 0 {
|
|
||||||
return ScriptElementKindMemberSetAccessorElement
|
|
||||||
}
|
|
||||||
if flags&ast.SymbolFlagsMethod != 0 {
|
|
||||||
return ScriptElementKindMemberFunctionElement
|
|
||||||
}
|
|
||||||
if flags&ast.SymbolFlagsConstructor != 0 {
|
|
||||||
return ScriptElementKindConstructorImplementationElement
|
|
||||||
}
|
|
||||||
if flags&ast.SymbolFlagsSignature != 0 {
|
|
||||||
return ScriptElementKindIndexSignatureElement
|
|
||||||
}
|
|
||||||
|
|
||||||
if flags&ast.SymbolFlagsProperty != 0 {
|
|
||||||
if 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 {
|
|
||||||
if rootSymbol.Flags&(ast.SymbolFlagsPropertyOrAccessor|ast.SymbolFlagsVariable) != 0 {
|
|
||||||
unionPropertyKind = ScriptElementKindMemberVariableElement
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if unionPropertyKind == ScriptElementKindUnknown {
|
|
||||||
// If this was union of all methods,
|
|
||||||
// make sure it has call signatures before we can label it as method.
|
|
||||||
typeOfUnionProperty := typeChecker.GetTypeOfSymbolAtLocation(symbol, location)
|
|
||||||
if len(typeChecker.GetCallSignatures(typeOfUnionProperty)) > 0 {
|
|
||||||
return ScriptElementKindMemberFunctionElement
|
|
||||||
}
|
|
||||||
return ScriptElementKindMemberVariableElement
|
|
||||||
}
|
|
||||||
return unionPropertyKind
|
|
||||||
}
|
|
||||||
|
|
||||||
return ScriptElementKindMemberVariableElement
|
|
||||||
}
|
|
||||||
|
|
||||||
return ScriptElementKindUnknown
|
|
||||||
}
|
|
||||||
|
|
||||||
func isFirstDeclarationOfSymbolParameter(symbol *ast.Symbol) bool {
|
|
||||||
var declaration *ast.Node
|
|
||||||
if len(symbol.Declarations) > 0 {
|
|
||||||
declaration = symbol.Declarations[0]
|
|
||||||
}
|
|
||||||
result := ast.FindAncestorOrQuit(declaration, func(n *ast.Node) ast.FindAncestorResult {
|
|
||||||
if ast.IsParameter(n) {
|
|
||||||
return ast.FindAncestorTrue
|
|
||||||
}
|
|
||||||
if ast.IsBindingElement(n) || ast.IsObjectBindingPattern(n) || ast.IsArrayBindingPattern(n) {
|
|
||||||
return ast.FindAncestorFalse
|
|
||||||
}
|
|
||||||
return ast.FindAncestorQuit
|
|
||||||
})
|
|
||||||
|
|
||||||
return result != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isLocalVariableOrFunction(symbol *ast.Symbol) bool {
|
|
||||||
if symbol.Parent != nil {
|
|
||||||
return false // This is exported symbol
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, decl := range symbol.Declarations {
|
|
||||||
// Function expressions are local
|
|
||||||
if decl.Kind == ast.KindFunctionExpression {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if decl.Kind != ast.KindVariableDeclaration && decl.Kind != ast.KindFunctionDeclaration {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the parent is not source file or module block, it is a local variable.
|
|
||||||
parent := decl.Parent
|
|
||||||
for ; !ast.IsFunctionBlock(parent); parent = parent.Parent {
|
|
||||||
// Reached source file or module block
|
|
||||||
if parent.Kind == ast.KindSourceFile || parent.Kind == ast.KindModuleBlock {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ast.IsFunctionBlock(parent) {
|
|
||||||
// Parent is in function block.
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
resolvedSymbol := typeChecker.GetAliasedSymbol(symbol)
|
|
||||||
if resolvedSymbol != symbol {
|
|
||||||
aliasModifiers := getNormalizedSymbolModifiers(typeChecker, resolvedSymbol)
|
|
||||||
for modifier := range aliasModifiers.Keys() {
|
|
||||||
modifiers.Add(modifier)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if symbol.Flags&ast.SymbolFlagsOptional != 0 {
|
|
||||||
modifiers.Add(ScriptElementKindModifierOptional)
|
|
||||||
}
|
|
||||||
|
|
||||||
return modifiers
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNormalizedSymbolModifiers(typeChecker *checker.Checker, symbol *ast.Symbol) collections.Set[ScriptElementKindModifier] {
|
|
||||||
var modifierSet collections.Set[ScriptElementKindModifier]
|
|
||||||
if len(symbol.Declarations) > 0 {
|
|
||||||
declaration := symbol.Declarations[0]
|
|
||||||
declarations := symbol.Declarations[1:]
|
|
||||||
// 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) }) {
|
|
||||||
excludeFlags = ast.ModifierFlagsDeprecated
|
|
||||||
} else {
|
|
||||||
excludeFlags = ast.ModifierFlagsNone
|
|
||||||
}
|
|
||||||
modifierSet = getNodeModifiers(declaration, excludeFlags)
|
|
||||||
}
|
|
||||||
|
|
||||||
return modifierSet
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNodeModifiers(node *ast.Node, excludeFlags ast.ModifierFlags) collections.Set[ScriptElementKindModifier] {
|
|
||||||
var result collections.Set[ScriptElementKindModifier]
|
|
||||||
var flags ast.ModifierFlags
|
|
||||||
if ast.IsDeclaration(node) {
|
|
||||||
flags = ast.GetCombinedModifierFlags(node) & ^excludeFlags // !!! include jsdoc node flags
|
|
||||||
}
|
|
||||||
|
|
||||||
if flags&ast.ModifierFlagsPrivate != 0 {
|
|
||||||
result.Add(ScriptElementKindModifierPrivate)
|
|
||||||
}
|
|
||||||
if flags&ast.ModifierFlagsProtected != 0 {
|
|
||||||
result.Add(ScriptElementKindModifierProtected)
|
|
||||||
}
|
|
||||||
if flags&ast.ModifierFlagsPublic != 0 {
|
|
||||||
result.Add(ScriptElementKindModifierPublic)
|
|
||||||
}
|
|
||||||
if flags&ast.ModifierFlagsStatic != 0 {
|
|
||||||
result.Add(ScriptElementKindModifierStatic)
|
|
||||||
}
|
|
||||||
if flags&ast.ModifierFlagsAbstract != 0 {
|
|
||||||
result.Add(ScriptElementKindModifierAbstract)
|
|
||||||
}
|
|
||||||
if flags&ast.ModifierFlagsExport != 0 {
|
|
||||||
result.Add(ScriptElementKindModifierExported)
|
|
||||||
}
|
|
||||||
if flags&ast.ModifierFlagsDeprecated != 0 {
|
|
||||||
result.Add(ScriptElementKindModifierDeprecated)
|
|
||||||
}
|
|
||||||
if flags&ast.ModifierFlagsAmbient != 0 {
|
|
||||||
result.Add(ScriptElementKindModifierAmbient)
|
|
||||||
}
|
|
||||||
if node.Flags&ast.NodeFlagsAmbient != 0 {
|
|
||||||
result.Add(ScriptElementKindModifierAmbient)
|
|
||||||
}
|
|
||||||
if node.Kind == ast.KindExportAssignment {
|
|
||||||
result.Add(ScriptElementKindModifierExported)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
@ -1,309 +0,0 @@
|
|||||||
package ls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/compiler"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/printer"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/scanner"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/stringutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (l *LanguageService) ProvideDocumentSymbols(ctx context.Context, documentURI lsproto.DocumentUri) (lsproto.DocumentSymbolResponse, error) {
|
|
||||||
_, file := l.getProgramAndFile(documentURI)
|
|
||||||
symbols := l.getDocumentSymbolsForChildren(ctx, file.AsNode())
|
|
||||||
return lsproto.SymbolInformationsOrDocumentSymbolsOrNull{DocumentSymbols: &symbols}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) getDocumentSymbolsForChildren(ctx context.Context, node *ast.Node) []*lsproto.DocumentSymbol {
|
|
||||||
var symbols []*lsproto.DocumentSymbol
|
|
||||||
addSymbolForNode := func(node *ast.Node, children []*lsproto.DocumentSymbol) {
|
|
||||||
if node.Flags&ast.NodeFlagsReparsed == 0 {
|
|
||||||
symbol := l.newDocumentSymbol(node, children)
|
|
||||||
if symbol != nil {
|
|
||||||
symbols = append(symbols, symbol)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var visit func(*ast.Node) bool
|
|
||||||
getSymbolsForChildren := func(node *ast.Node) []*lsproto.DocumentSymbol {
|
|
||||||
var result []*lsproto.DocumentSymbol
|
|
||||||
if node != nil {
|
|
||||||
saveSymbols := symbols
|
|
||||||
symbols = nil
|
|
||||||
node.ForEachChild(visit)
|
|
||||||
result = symbols
|
|
||||||
symbols = saveSymbols
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
visit = func(node *ast.Node) bool {
|
|
||||||
if ctx.Err() != nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
switch node.Kind {
|
|
||||||
case ast.KindClassDeclaration, ast.KindClassExpression, ast.KindInterfaceDeclaration, ast.KindEnumDeclaration:
|
|
||||||
addSymbolForNode(node, getSymbolsForChildren(node))
|
|
||||||
case ast.KindModuleDeclaration:
|
|
||||||
addSymbolForNode(node, getSymbolsForChildren(getInteriorModule(node)))
|
|
||||||
case ast.KindFunctionDeclaration, ast.KindFunctionExpression, ast.KindArrowFunction, ast.KindMethodDeclaration, ast.KindGetAccessor,
|
|
||||||
ast.KindSetAccessor, ast.KindConstructor:
|
|
||||||
addSymbolForNode(node, getSymbolsForChildren(node.Body()))
|
|
||||||
case ast.KindVariableDeclaration, ast.KindBindingElement, ast.KindPropertyAssignment, ast.KindPropertyDeclaration:
|
|
||||||
name := node.Name()
|
|
||||||
if name != nil {
|
|
||||||
if ast.IsBindingPattern(name) {
|
|
||||||
visit(name)
|
|
||||||
} else {
|
|
||||||
addSymbolForNode(node, getSymbolsForChildren(node.Initializer()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case ast.KindMethodSignature, ast.KindPropertySignature, ast.KindCallSignature, ast.KindConstructSignature, ast.KindIndexSignature,
|
|
||||||
ast.KindEnumMember, ast.KindShorthandPropertyAssignment, ast.KindTypeAliasDeclaration:
|
|
||||||
addSymbolForNode(node, nil)
|
|
||||||
default:
|
|
||||||
node.ForEachChild(visit)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
node.ForEachChild(visit)
|
|
||||||
return symbols
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) newDocumentSymbol(node *ast.Node, children []*lsproto.DocumentSymbol) *lsproto.DocumentSymbol {
|
|
||||||
result := new(lsproto.DocumentSymbol)
|
|
||||||
file := ast.GetSourceFileOfNode(node)
|
|
||||||
nodeStartPos := scanner.SkipTrivia(file.Text(), node.Pos())
|
|
||||||
name := ast.GetNameOfDeclaration(node)
|
|
||||||
var text string
|
|
||||||
var nameStartPos, nameEndPos int
|
|
||||||
if ast.IsModuleDeclaration(node) && !ast.IsAmbientModule(node) {
|
|
||||||
text = getModuleName(node)
|
|
||||||
nameStartPos = scanner.SkipTrivia(file.Text(), name.Pos())
|
|
||||||
nameEndPos = getInteriorModule(node).Name().End()
|
|
||||||
} else if name != nil {
|
|
||||||
text = getTextOfName(name)
|
|
||||||
nameStartPos = max(scanner.SkipTrivia(file.Text(), name.Pos()), nodeStartPos)
|
|
||||||
nameEndPos = max(name.End(), nodeStartPos)
|
|
||||||
} else {
|
|
||||||
text = getUnnamedNodeLabel(node)
|
|
||||||
nameStartPos = nodeStartPos
|
|
||||||
nameEndPos = nodeStartPos
|
|
||||||
}
|
|
||||||
if text == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
result.Name = text
|
|
||||||
result.Kind = getSymbolKindFromNode(node)
|
|
||||||
result.Range = lsproto.Range{
|
|
||||||
Start: l.converters.PositionToLineAndCharacter(file, core.TextPos(nodeStartPos)),
|
|
||||||
End: l.converters.PositionToLineAndCharacter(file, core.TextPos(node.End())),
|
|
||||||
}
|
|
||||||
result.SelectionRange = lsproto.Range{
|
|
||||||
Start: l.converters.PositionToLineAndCharacter(file, core.TextPos(nameStartPos)),
|
|
||||||
End: l.converters.PositionToLineAndCharacter(file, core.TextPos(nameEndPos)),
|
|
||||||
}
|
|
||||||
if children == nil {
|
|
||||||
children = []*lsproto.DocumentSymbol{}
|
|
||||||
}
|
|
||||||
result.Children = &children
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTextOfName(node *ast.Node) string {
|
|
||||||
switch node.Kind {
|
|
||||||
case ast.KindIdentifier, ast.KindPrivateIdentifier, ast.KindNumericLiteral:
|
|
||||||
return node.Text()
|
|
||||||
case ast.KindStringLiteral:
|
|
||||||
return "\"" + printer.EscapeString(node.Text(), '"') + "\""
|
|
||||||
case ast.KindNoSubstitutionTemplateLiteral:
|
|
||||||
return "`" + printer.EscapeString(node.Text(), '`') + "`"
|
|
||||||
case ast.KindComputedPropertyName:
|
|
||||||
if ast.IsStringOrNumericLiteralLike(node.Expression()) {
|
|
||||||
return getTextOfName(node.Expression())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return scanner.GetTextOfNode(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getUnnamedNodeLabel(node *ast.Node) string {
|
|
||||||
switch node.Kind {
|
|
||||||
case ast.KindFunctionExpression, ast.KindArrowFunction:
|
|
||||||
if ast.IsCallExpression(node.Parent) {
|
|
||||||
name := getCallExpressionName(node.Parent.Expression())
|
|
||||||
if name != "" {
|
|
||||||
return name + "() callback"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "<function>"
|
|
||||||
case ast.KindClassExpression:
|
|
||||||
return "<class>"
|
|
||||||
case ast.KindConstructor:
|
|
||||||
return "constructor"
|
|
||||||
case ast.KindCallSignature:
|
|
||||||
return "()"
|
|
||||||
case ast.KindConstructSignature:
|
|
||||||
return "new()"
|
|
||||||
case ast.KindIndexSignature:
|
|
||||||
return "[]"
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCallExpressionName(node *ast.Node) string {
|
|
||||||
switch node.Kind {
|
|
||||||
case ast.KindIdentifier, ast.KindPrivateIdentifier:
|
|
||||||
return node.Text()
|
|
||||||
case ast.KindPropertyAccessExpression:
|
|
||||||
left := getCallExpressionName(node.Expression())
|
|
||||||
right := getCallExpressionName(node.Name())
|
|
||||||
if left != "" {
|
|
||||||
return left + "." + right
|
|
||||||
}
|
|
||||||
return right
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func getInteriorModule(node *ast.Node) *ast.Node {
|
|
||||||
for node.Body() != nil && ast.IsModuleDeclaration(node.Body()) {
|
|
||||||
node = node.Body()
|
|
||||||
}
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
func getModuleName(node *ast.Node) string {
|
|
||||||
result := node.Name().Text()
|
|
||||||
for node.Body() != nil && ast.IsModuleDeclaration(node.Body()) {
|
|
||||||
node = node.Body()
|
|
||||||
result = result + "." + node.Name().Text()
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
type DeclarationInfo struct {
|
|
||||||
name string
|
|
||||||
declaration *ast.Node
|
|
||||||
matchScore int
|
|
||||||
}
|
|
||||||
|
|
||||||
func ProvideWorkspaceSymbols(ctx context.Context, programs []*compiler.Program, converters *Converters, query string) (lsproto.WorkspaceSymbolResponse, error) {
|
|
||||||
// Obtain set of non-declaration source files from all active programs.
|
|
||||||
var sourceFiles collections.Set[*ast.SourceFile]
|
|
||||||
for _, program := range programs {
|
|
||||||
for _, sourceFile := range program.SourceFiles() {
|
|
||||||
if !sourceFile.IsDeclarationFile {
|
|
||||||
sourceFiles.Add(sourceFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Create DeclarationInfos for all declarations in the source files.
|
|
||||||
var infos []DeclarationInfo
|
|
||||||
for sourceFile := range sourceFiles.Keys() {
|
|
||||||
if ctx.Err() != nil {
|
|
||||||
return lsproto.SymbolInformationsOrWorkspaceSymbolsOrNull{}, nil
|
|
||||||
}
|
|
||||||
declarationMap := sourceFile.GetDeclarationMap()
|
|
||||||
for name, declarations := range declarationMap {
|
|
||||||
score := getMatchScore(name, query)
|
|
||||||
if score >= 0 {
|
|
||||||
for _, declaration := range declarations {
|
|
||||||
infos = append(infos, DeclarationInfo{name, declaration, score})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Sort the DeclarationInfos and return the top 256 matches.
|
|
||||||
slices.SortFunc(infos, compareDeclarationInfos)
|
|
||||||
count := min(len(infos), 256)
|
|
||||||
symbols := make([]*lsproto.SymbolInformation, count)
|
|
||||||
for i, info := range infos[0:count] {
|
|
||||||
node := core.OrElse(ast.GetNameOfDeclaration(info.declaration), info.declaration)
|
|
||||||
sourceFile := ast.GetSourceFileOfNode(node)
|
|
||||||
pos := scanner.SkipTrivia(sourceFile.Text(), node.Pos())
|
|
||||||
var symbol lsproto.SymbolInformation
|
|
||||||
symbol.Name = info.name
|
|
||||||
symbol.Kind = getSymbolKindFromNode(info.declaration)
|
|
||||||
symbol.Location = converters.ToLSPLocation(sourceFile, core.NewTextRange(pos, node.End()))
|
|
||||||
symbols[i] = &symbol
|
|
||||||
}
|
|
||||||
return lsproto.SymbolInformationsOrWorkspaceSymbolsOrNull{SymbolInformations: &symbols}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a score for matching `s` against `pattern`. In order to match, `s` must contain each of the characters in
|
|
||||||
// `pattern` in the same order. Upper case characters in `pattern` must match exactly, whereas lower case characters
|
|
||||||
// in `pattern` match either case in `s`. If `s` doesn't match, -1 is returned. Otherwise, the returned score is the
|
|
||||||
// number of characters in `s` that weren't matched. Thus, zero represents an exact match, and higher values represent
|
|
||||||
// increasingly less specific partial matches.
|
|
||||||
func getMatchScore(s string, pattern string) int {
|
|
||||||
score := 0
|
|
||||||
for _, p := range pattern {
|
|
||||||
exact := unicode.IsUpper(p)
|
|
||||||
for {
|
|
||||||
c, size := utf8.DecodeRuneInString(s)
|
|
||||||
if size == 0 {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
s = s[size:]
|
|
||||||
if exact && c == p || !exact && unicode.ToLower(c) == unicode.ToLower(p) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
score++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return score
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort DeclarationInfos by ascending match score, then ascending case insensitive name, then
|
|
||||||
// ascending case sensitive name, and finally by source file name and position.
|
|
||||||
func compareDeclarationInfos(d1, d2 DeclarationInfo) int {
|
|
||||||
if d1.matchScore != d2.matchScore {
|
|
||||||
return d1.matchScore - d2.matchScore
|
|
||||||
}
|
|
||||||
if c := stringutil.CompareStringsCaseInsensitive(d1.name, d2.name); c != 0 {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
if c := strings.Compare(d1.name, d2.name); c != 0 {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
s1 := ast.GetSourceFileOfNode(d1.declaration)
|
|
||||||
s2 := ast.GetSourceFileOfNode(d2.declaration)
|
|
||||||
if s1 != s2 {
|
|
||||||
return strings.Compare(string(s1.Path()), string(s2.Path()))
|
|
||||||
}
|
|
||||||
return d1.declaration.Pos() - d2.declaration.Pos()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSymbolKindFromNode(node *ast.Node) lsproto.SymbolKind {
|
|
||||||
switch node.Kind {
|
|
||||||
case ast.KindModuleDeclaration:
|
|
||||||
return lsproto.SymbolKindNamespace
|
|
||||||
case ast.KindClassDeclaration, ast.KindClassExpression, ast.KindTypeAliasDeclaration:
|
|
||||||
return lsproto.SymbolKindClass
|
|
||||||
case ast.KindMethodDeclaration, ast.KindMethodSignature:
|
|
||||||
return lsproto.SymbolKindMethod
|
|
||||||
case ast.KindPropertyDeclaration, ast.KindPropertySignature, ast.KindGetAccessor, ast.KindSetAccessor:
|
|
||||||
return lsproto.SymbolKindProperty
|
|
||||||
case ast.KindConstructor, ast.KindConstructSignature:
|
|
||||||
return lsproto.SymbolKindConstructor
|
|
||||||
case ast.KindEnumDeclaration:
|
|
||||||
return lsproto.SymbolKindEnum
|
|
||||||
case ast.KindInterfaceDeclaration:
|
|
||||||
return lsproto.SymbolKindInterface
|
|
||||||
case ast.KindFunctionDeclaration, ast.KindFunctionExpression:
|
|
||||||
return lsproto.SymbolKindFunction
|
|
||||||
case ast.KindEnumMember:
|
|
||||||
return lsproto.SymbolKindEnumMember
|
|
||||||
case ast.KindTypeParameter:
|
|
||||||
return lsproto.SymbolKindTypeParameter
|
|
||||||
}
|
|
||||||
return lsproto.SymbolKindVariable
|
|
||||||
}
|
|
||||||
@ -1,211 +0,0 @@
|
|||||||
package ls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/modulespecifiers"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UserPreferences struct {
|
|
||||||
QuotePreference QuotePreference
|
|
||||||
LazyConfiguredProjectsFromExternalProject bool // !!!
|
|
||||||
|
|
||||||
// A positive integer indicating the maximum length of a hover text before it is truncated.
|
|
||||||
//
|
|
||||||
// Default: `500`
|
|
||||||
MaximumHoverLength int // !!!
|
|
||||||
|
|
||||||
// ------- Completions -------
|
|
||||||
|
|
||||||
// If enabled, TypeScript will search through all external modules' exports and add them to the completions list.
|
|
||||||
// This affects lone identifier completions but not completions on the right hand side of `obj.`.
|
|
||||||
IncludeCompletionsForModuleExports core.Tristate
|
|
||||||
// Enables auto-import-style completions on partially-typed import statements. E.g., allows
|
|
||||||
// `import write|` to be completed to `import { writeFile } from "fs"`.
|
|
||||||
IncludeCompletionsForImportStatements core.Tristate
|
|
||||||
// Unless this option is `false`, member completion lists triggered with `.` will include entries
|
|
||||||
// on potentially-null and potentially-undefined values, with insertion text to replace
|
|
||||||
// preceding `.` tokens with `?.`.
|
|
||||||
IncludeAutomaticOptionalChainCompletions core.Tristate
|
|
||||||
// Allows completions to be formatted with snippet text, indicated by `CompletionItem["isSnippet"]`.
|
|
||||||
IncludeCompletionsWithSnippetText core.Tristate // !!!
|
|
||||||
// If enabled, completions for class members (e.g. methods and properties) will include
|
|
||||||
// a whole declaration for the member.
|
|
||||||
// E.g., `class A { f| }` could be completed to `class A { foo(): number {} }`, instead of
|
|
||||||
// `class A { foo }`.
|
|
||||||
IncludeCompletionsWithClassMemberSnippets core.Tristate // !!!
|
|
||||||
// If enabled, object literal methods will have a method declaration completion entry in addition
|
|
||||||
// to the regular completion entry containing just the method name.
|
|
||||||
// E.g., `const objectLiteral: T = { f| }` could be completed to `const objectLiteral: T = { foo(): void {} }`,
|
|
||||||
// in addition to `const objectLiteral: T = { foo }`.
|
|
||||||
IncludeCompletionsWithObjectLiteralMethodSnippets core.Tristate // !!!
|
|
||||||
JsxAttributeCompletionStyle JsxAttributeCompletionStyle
|
|
||||||
|
|
||||||
// ------- AutoImports --------
|
|
||||||
|
|
||||||
ImportModuleSpecifierPreference modulespecifiers.ImportModuleSpecifierPreference // !!!
|
|
||||||
// Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js"
|
|
||||||
ImportModuleSpecifierEnding modulespecifiers.ImportModuleSpecifierEndingPreference // !!!
|
|
||||||
IncludePackageJsonAutoImports IncludePackageJsonAutoImports // !!!
|
|
||||||
AutoImportSpecifierExcludeRegexes []string // !!!
|
|
||||||
AutoImportFileExcludePatterns []string // !!!
|
|
||||||
PreferTypeOnlyAutoImports bool // !!!
|
|
||||||
|
|
||||||
// ------- OrganizeImports -------
|
|
||||||
|
|
||||||
// Indicates whether imports should be organized in a case-insensitive manner.
|
|
||||||
//
|
|
||||||
// Default: TSUnknown ("auto" in strada), will perform detection
|
|
||||||
OrganizeImportsIgnoreCase core.Tristate // !!!
|
|
||||||
// Indicates whether imports should be organized via an "ordinal" (binary) comparison using the numeric value of their
|
|
||||||
// code points, or via "unicode" collation (via the Unicode Collation Algorithm (https://unicode.org/reports/tr10/#Scope))
|
|
||||||
//
|
|
||||||
// using rules associated with the locale specified in organizeImportsCollationLocale.
|
|
||||||
//
|
|
||||||
// Default: Ordinal
|
|
||||||
OrganizeImportsCollation OrganizeImportsCollation // !!!
|
|
||||||
// Indicates the locale to use for "unicode" collation. If not specified, the locale `"en"` is used as an invariant
|
|
||||||
// for the sake of consistent sorting. Use `"auto"` to use the detected UI locale.
|
|
||||||
//
|
|
||||||
// This preference is ignored if organizeImportsCollation is not `unicode`.
|
|
||||||
//
|
|
||||||
// Default: `"en"`
|
|
||||||
OrganizeImportsLocale string // !!!
|
|
||||||
// Indicates whether numeric collation should be used for digit sequences in strings. When `true`, will collate
|
|
||||||
// strings such that `a1z < a2z < a100z`. When `false`, will collate strings such that `a1z < a100z < a2z`.
|
|
||||||
//
|
|
||||||
// This preference is ignored if organizeImportsCollation is not `unicode`.
|
|
||||||
//
|
|
||||||
// Default: `false`
|
|
||||||
OrganizeImportsNumericCollation bool // !!!
|
|
||||||
// Indicates whether accents and other diacritic marks are considered unequal for the purpose of collation. When
|
|
||||||
// `true`, characters with accents and other diacritics will be collated in the order defined by the locale specified
|
|
||||||
// in organizeImportsCollationLocale.
|
|
||||||
//
|
|
||||||
// This preference is ignored if organizeImportsCollation is not `unicode`.
|
|
||||||
//
|
|
||||||
// Default: `true`
|
|
||||||
OrganizeImportsAccentCollation OrganizeImportsAccentCollation // !!!
|
|
||||||
// Indicates whether upper case or lower case should sort first. When `false`, the default order for the locale
|
|
||||||
// specified in organizeImportsCollationLocale is used.
|
|
||||||
//
|
|
||||||
// This preference is ignored if:
|
|
||||||
// - organizeImportsCollation is not `unicode`
|
|
||||||
// - organizeImportsIgnoreCase is `true`
|
|
||||||
// - organizeImportsIgnoreCase is `auto` and the auto-detected case sensitivity is case-insensitive.
|
|
||||||
//
|
|
||||||
// Default: `false`
|
|
||||||
OrganizeImportsCaseFirst OrganizeImportsCaseFirst // !!!
|
|
||||||
// Indicates where named type-only imports should sort. "inline" sorts named imports without regard to if the import is type-only.
|
|
||||||
//
|
|
||||||
// Default: `last`
|
|
||||||
OrganizeImportsTypeOrder OrganizeImportsTypeOrder // !!!
|
|
||||||
|
|
||||||
// ------- MoveToFile -------
|
|
||||||
|
|
||||||
AllowTextChangesInNewFiles bool // !!!
|
|
||||||
|
|
||||||
// ------- Rename -------
|
|
||||||
|
|
||||||
// renamed from `providePrefixAndSuffixTextForRename`
|
|
||||||
UseAliasesForRename core.Tristate
|
|
||||||
AllowRenameOfImportPath bool // !!!
|
|
||||||
|
|
||||||
// ------- CodeFixes/Refactors -------
|
|
||||||
|
|
||||||
ProvideRefactorNotApplicableReason bool // !!!
|
|
||||||
|
|
||||||
// ------- InlayHints -------
|
|
||||||
|
|
||||||
IncludeInlayParameterNameHints IncludeInlayParameterNameHints
|
|
||||||
IncludeInlayParameterNameHintsWhenArgumentMatchesName bool
|
|
||||||
IncludeInlayFunctionParameterTypeHints bool
|
|
||||||
IncludeInlayVariableTypeHints bool
|
|
||||||
IncludeInlayVariableTypeHintsWhenTypeMatchesName bool
|
|
||||||
IncludeInlayPropertyDeclarationTypeHints bool
|
|
||||||
IncludeInlayFunctionLikeReturnTypeHints bool
|
|
||||||
IncludeInlayEnumMemberValueHints bool
|
|
||||||
InteractiveInlayHints bool
|
|
||||||
|
|
||||||
// ------- Misc -------
|
|
||||||
|
|
||||||
ExcludeLibrarySymbolsInNavTo bool // !!!
|
|
||||||
DisableSuggestions bool // !!!
|
|
||||||
DisableLineTextInReferences bool // !!!
|
|
||||||
DisplayPartsForJSDoc bool // !!!
|
|
||||||
}
|
|
||||||
|
|
||||||
type JsxAttributeCompletionStyle string
|
|
||||||
|
|
||||||
const (
|
|
||||||
JsxAttributeCompletionStyleUnknown JsxAttributeCompletionStyle = ""
|
|
||||||
JsxAttributeCompletionStyleAuto JsxAttributeCompletionStyle = "auto"
|
|
||||||
JsxAttributeCompletionStyleBraces JsxAttributeCompletionStyle = "braces"
|
|
||||||
JsxAttributeCompletionStyleNone JsxAttributeCompletionStyle = "none"
|
|
||||||
)
|
|
||||||
|
|
||||||
type IncludeInlayParameterNameHints string
|
|
||||||
|
|
||||||
const (
|
|
||||||
IncludeInlayParameterNameHintsNone IncludeInlayParameterNameHints = ""
|
|
||||||
IncludeInlayParameterNameHintsAll IncludeInlayParameterNameHints = "all"
|
|
||||||
IncludeInlayParameterNameHintsLiterals IncludeInlayParameterNameHints = "literals"
|
|
||||||
)
|
|
||||||
|
|
||||||
type IncludePackageJsonAutoImports string
|
|
||||||
|
|
||||||
const (
|
|
||||||
IncludePackageJsonAutoImportsUnknown IncludePackageJsonAutoImports = ""
|
|
||||||
IncludePackageJsonAutoImportsAuto IncludePackageJsonAutoImports = "auto"
|
|
||||||
IncludePackageJsonAutoImportsOn IncludePackageJsonAutoImports = "on"
|
|
||||||
IncludePackageJsonAutoImportsOff IncludePackageJsonAutoImports = "off"
|
|
||||||
)
|
|
||||||
|
|
||||||
type OrganizeImportsCollation bool
|
|
||||||
|
|
||||||
const (
|
|
||||||
OrganizeImportsCollationOrdinal OrganizeImportsCollation = false
|
|
||||||
OrganizeImportsCollationUnicode OrganizeImportsCollation = true
|
|
||||||
)
|
|
||||||
|
|
||||||
type OrganizeImportsAccentCollation int
|
|
||||||
|
|
||||||
const (
|
|
||||||
OrganizeImportsAccentCollationTrue OrganizeImportsAccentCollation = 0
|
|
||||||
OrganizeImportsAccentCollationFalse OrganizeImportsAccentCollation = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
type OrganizeImportsCaseFirst int
|
|
||||||
|
|
||||||
const (
|
|
||||||
OrganizeImportsCaseFirstFalse OrganizeImportsCaseFirst = 0
|
|
||||||
OrganizeImportsCaseFirstLower OrganizeImportsCaseFirst = 1
|
|
||||||
OrganizeImportsCaseFirstUpper OrganizeImportsCaseFirst = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
type OrganizeImportsTypeOrder int
|
|
||||||
|
|
||||||
const (
|
|
||||||
OrganizeImportsTypeOrderLast OrganizeImportsTypeOrder = 0
|
|
||||||
OrganizeImportsTypeOrderInline OrganizeImportsTypeOrder = 1
|
|
||||||
OrganizeImportsTypeOrderFirst OrganizeImportsTypeOrder = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
type QuotePreference string
|
|
||||||
|
|
||||||
const (
|
|
||||||
QuotePreferenceUnknown QuotePreference = ""
|
|
||||||
QuotePreferenceAuto QuotePreference = "auto"
|
|
||||||
QuotePreferenceDouble QuotePreference = "double"
|
|
||||||
QuotePreferenceSingle QuotePreference = "single"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (p *UserPreferences) Parse(config map[string]interface{}) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *UserPreferences) ModuleSpecifierPreferences() modulespecifiers.UserPreferences {
|
|
||||||
return modulespecifiers.UserPreferences{
|
|
||||||
ImportModuleSpecifierPreference: p.ImportModuleSpecifierPreference,
|
|
||||||
ImportModuleSpecifierEnding: p.ImportModuleSpecifierEnding,
|
|
||||||
AutoImportSpecifierExcludeRegexes: p.AutoImportSpecifierExcludeRegexes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,104 +0,0 @@
|
|||||||
package lsutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/astnav"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/scanner"
|
|
||||||
)
|
|
||||||
|
|
||||||
func PositionIsASICandidate(pos int, context *ast.Node, file *ast.SourceFile) bool {
|
|
||||||
contextAncestor := ast.FindAncestorOrQuit(context, func(ancestor *ast.Node) ast.FindAncestorResult {
|
|
||||||
if ancestor.End() != pos {
|
|
||||||
return ast.FindAncestorQuit
|
|
||||||
}
|
|
||||||
|
|
||||||
return ast.ToFindAncestorResult(SyntaxMayBeASICandidate(ancestor.Kind))
|
|
||||||
})
|
|
||||||
|
|
||||||
return contextAncestor != nil && NodeIsASICandidate(contextAncestor, file)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SyntaxMayBeASICandidate(kind ast.Kind) bool {
|
|
||||||
return SyntaxRequiresTrailingCommaOrSemicolonOrASI(kind) ||
|
|
||||||
SyntaxRequiresTrailingFunctionBlockOrSemicolonOrASI(kind) ||
|
|
||||||
SyntaxRequiresTrailingModuleBlockOrSemicolonOrASI(kind) ||
|
|
||||||
SyntaxRequiresTrailingSemicolonOrASI(kind)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SyntaxRequiresTrailingCommaOrSemicolonOrASI(kind ast.Kind) bool {
|
|
||||||
return kind == ast.KindCallSignature ||
|
|
||||||
kind == ast.KindConstructSignature ||
|
|
||||||
kind == ast.KindIndexSignature ||
|
|
||||||
kind == ast.KindPropertySignature ||
|
|
||||||
kind == ast.KindMethodSignature
|
|
||||||
}
|
|
||||||
|
|
||||||
func SyntaxRequiresTrailingFunctionBlockOrSemicolonOrASI(kind ast.Kind) bool {
|
|
||||||
return kind == ast.KindFunctionDeclaration ||
|
|
||||||
kind == ast.KindConstructor ||
|
|
||||||
kind == ast.KindMethodDeclaration ||
|
|
||||||
kind == ast.KindGetAccessor ||
|
|
||||||
kind == ast.KindSetAccessor
|
|
||||||
}
|
|
||||||
|
|
||||||
func SyntaxRequiresTrailingModuleBlockOrSemicolonOrASI(kind ast.Kind) bool {
|
|
||||||
return kind == ast.KindModuleDeclaration
|
|
||||||
}
|
|
||||||
|
|
||||||
func SyntaxRequiresTrailingSemicolonOrASI(kind ast.Kind) bool {
|
|
||||||
return kind == ast.KindVariableStatement ||
|
|
||||||
kind == ast.KindExpressionStatement ||
|
|
||||||
kind == ast.KindDoStatement ||
|
|
||||||
kind == ast.KindContinueStatement ||
|
|
||||||
kind == ast.KindBreakStatement ||
|
|
||||||
kind == ast.KindReturnStatement ||
|
|
||||||
kind == ast.KindThrowStatement ||
|
|
||||||
kind == ast.KindDebuggerStatement ||
|
|
||||||
kind == ast.KindPropertyDeclaration ||
|
|
||||||
kind == ast.KindTypeAliasDeclaration ||
|
|
||||||
kind == ast.KindImportDeclaration ||
|
|
||||||
kind == ast.KindImportEqualsDeclaration ||
|
|
||||||
kind == ast.KindExportDeclaration ||
|
|
||||||
kind == ast.KindNamespaceExportDeclaration ||
|
|
||||||
kind == ast.KindExportAssignment
|
|
||||||
}
|
|
||||||
|
|
||||||
func NodeIsASICandidate(node *ast.Node, file *ast.SourceFile) bool {
|
|
||||||
lastToken := GetLastToken(node, file)
|
|
||||||
if lastToken != nil && lastToken.Kind == ast.KindSemicolonToken {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if SyntaxRequiresTrailingCommaOrSemicolonOrASI(node.Kind) {
|
|
||||||
if lastToken != nil && lastToken.Kind == ast.KindCommaToken {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else if SyntaxRequiresTrailingModuleBlockOrSemicolonOrASI(node.Kind) {
|
|
||||||
lastChild := GetLastChild(node, file)
|
|
||||||
if lastChild != nil && ast.IsModuleBlock(lastChild) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else if SyntaxRequiresTrailingFunctionBlockOrSemicolonOrASI(node.Kind) {
|
|
||||||
lastChild := GetLastChild(node, file)
|
|
||||||
if lastChild != nil && ast.IsFunctionBlock(lastChild) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else if !SyntaxRequiresTrailingSemicolonOrASI(node.Kind) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// See comment in parser's `parseDoStatement`
|
|
||||||
if node.Kind == ast.KindDoStatement {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
topNode := ast.FindAncestor(node, func(ancestor *ast.Node) bool { return ancestor.Parent == nil })
|
|
||||||
nextToken := astnav.FindNextToken(node, topNode, file)
|
|
||||||
if nextToken == nil || nextToken.Kind == ast.KindCloseBraceToken {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
startLine, _ := scanner.GetECMALineAndCharacterOfPosition(file, node.End())
|
|
||||||
endLine, _ := scanner.GetECMALineAndCharacterOfPosition(file, astnav.GetStartOfNode(nextToken, file, false /*includeJSDoc*/))
|
|
||||||
return startLine != endLine
|
|
||||||
}
|
|
||||||
@ -1,130 +0,0 @@
|
|||||||
package lsutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/astnav"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
||||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/scanner"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Replaces last(node.getChildren(sourceFile))
|
|
||||||
func GetLastChild(node *ast.Node, sourceFile *ast.SourceFile) *ast.Node {
|
|
||||||
lastChildNode := GetLastVisitedChild(node, sourceFile)
|
|
||||||
if ast.IsJSDocSingleCommentNode(node) && lastChildNode == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var tokenStartPos int
|
|
||||||
if lastChildNode != nil {
|
|
||||||
tokenStartPos = lastChildNode.End()
|
|
||||||
} else {
|
|
||||||
tokenStartPos = node.Pos()
|
|
||||||
}
|
|
||||||
var lastToken *ast.Node
|
|
||||||
scanner := scanner.GetScannerForSourceFile(sourceFile, tokenStartPos)
|
|
||||||
for startPos := tokenStartPos; startPos < node.End(); {
|
|
||||||
tokenKind := scanner.Token()
|
|
||||||
tokenFullStart := scanner.TokenFullStart()
|
|
||||||
tokenEnd := scanner.TokenEnd()
|
|
||||||
lastToken = sourceFile.GetOrCreateToken(tokenKind, tokenFullStart, tokenEnd, node)
|
|
||||||
startPos = tokenEnd
|
|
||||||
scanner.Scan()
|
|
||||||
}
|
|
||||||
return core.IfElse(lastToken != nil, lastToken, lastChildNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetLastToken(node *ast.Node, sourceFile *ast.SourceFile) *ast.Node {
|
|
||||||
if node == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if ast.IsTokenKind(node.Kind) || ast.IsIdentifier(node) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
AssertHasRealPosition(node)
|
|
||||||
|
|
||||||
lastChild := GetLastChild(node, sourceFile)
|
|
||||||
if lastChild == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if lastChild.Kind < ast.KindFirstNode {
|
|
||||||
return lastChild
|
|
||||||
} else {
|
|
||||||
return GetLastToken(lastChild, sourceFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets the last visited child of the given node.
|
|
||||||
// NOTE: This doesn't include unvisited tokens; for this, use `getLastChild` or `getLastToken`.
|
|
||||||
func GetLastVisitedChild(node *ast.Node, sourceFile *ast.SourceFile) *ast.Node {
|
|
||||||
var lastChild *ast.Node
|
|
||||||
|
|
||||||
visitNode := func(n *ast.Node, _ *ast.NodeVisitor) *ast.Node {
|
|
||||||
if !(n == nil || node.Flags&ast.NodeFlagsReparsed != 0) {
|
|
||||||
lastChild = n
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
visitNodeList := func(nodeList *ast.NodeList, _ *ast.NodeVisitor) *ast.NodeList {
|
|
||||||
if nodeList != nil && len(nodeList.Nodes) > 0 {
|
|
||||||
for i := len(nodeList.Nodes) - 1; i >= 0; i-- {
|
|
||||||
if nodeList.Nodes[i].Flags&ast.NodeFlagsReparsed == 0 {
|
|
||||||
lastChild = nodeList.Nodes[i]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nodeList
|
|
||||||
}
|
|
||||||
|
|
||||||
astnav.VisitEachChildAndJSDoc(node, sourceFile, visitNode, visitNodeList)
|
|
||||||
return lastChild
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetFirstToken(node *ast.Node, sourceFile *ast.SourceFile) *ast.Node {
|
|
||||||
if ast.IsIdentifier(node) || ast.IsTokenKind(node.Kind) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
AssertHasRealPosition(node)
|
|
||||||
var firstChild *ast.Node
|
|
||||||
node.ForEachChild(func(n *ast.Node) bool {
|
|
||||||
if n == nil || node.Flags&ast.NodeFlagsReparsed != 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
firstChild = n
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
var tokenEndPosition int
|
|
||||||
if firstChild != nil {
|
|
||||||
tokenEndPosition = firstChild.Pos()
|
|
||||||
} else {
|
|
||||||
tokenEndPosition = node.End()
|
|
||||||
}
|
|
||||||
scanner := scanner.GetScannerForSourceFile(sourceFile, node.Pos())
|
|
||||||
var firstToken *ast.Node
|
|
||||||
if node.Pos() < tokenEndPosition {
|
|
||||||
tokenKind := scanner.Token()
|
|
||||||
tokenFullStart := scanner.TokenFullStart()
|
|
||||||
tokenEnd := scanner.TokenEnd()
|
|
||||||
firstToken = sourceFile.GetOrCreateToken(tokenKind, tokenFullStart, tokenEnd, node)
|
|
||||||
}
|
|
||||||
|
|
||||||
if firstToken != nil {
|
|
||||||
return firstToken
|
|
||||||
}
|
|
||||||
if firstChild == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if firstChild.Kind < ast.KindFirstNode {
|
|
||||||
return firstChild
|
|
||||||
}
|
|
||||||
return GetFirstToken(firstChild, sourceFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
func AssertHasRealPosition(node *ast.Node) {
|
|
||||||
if ast.PositionIsSynthesized(node.Pos()) || ast.PositionIsSynthesized(node.End()) {
|
|
||||||
panic("Node must have a real position for this operation.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user