390 lines
15 KiB
Go
390 lines
15 KiB
Go
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
|
|
}
|