Files
kjol/tools/tsgo/internal/ls/lsutil/symbol_display.go
2026-07-09 16:50:43 -04:00

439 lines
14 KiB
Go

package lsutil
import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/checker"
"github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/core"
)
type ScriptElementKind int
const (
ScriptElementKindUnknown ScriptElementKind = iota
ScriptElementKindWarning
// predefined type (void) or keyword (class)
ScriptElementKindKeyword
// top level script node
ScriptElementKindScriptElement
// module foo {}
ScriptElementKindModuleElement
// class X {}
ScriptElementKindClassElement
// var x = class X {}
ScriptElementKindLocalClassElement
// interface Y {}
ScriptElementKindInterfaceElement
// type T = ...
ScriptElementKindTypeElement
// enum E {}
ScriptElementKindEnumElement
ScriptElementKindEnumMemberElement
// Inside module and script only.
// const v = ...
ScriptElementKindVariableElement
// Inside function.
ScriptElementKindLocalVariableElement
// using foo = ...
ScriptElementKindVariableUsingElement
// await using foo = ...
ScriptElementKindVariableAwaitUsingElement
// Inside module and script only.
// function f() {}
ScriptElementKindFunctionElement
// Inside function.
ScriptElementKindLocalFunctionElement
// class X { [public|private]* foo() {} }
ScriptElementKindMemberFunctionElement
// class X { [public|private]* [get|set] foo:number; }
ScriptElementKindMemberGetAccessorElement
ScriptElementKindMemberSetAccessorElement
// class X { [public|private]* foo:number; }
// interface Y { foo:number; }
ScriptElementKindMemberVariableElement
// class X { [public|private]* accessor foo: number; }
ScriptElementKindMemberAccessorVariableElement
// class X { constructor() { } }
// class X { static { } }
ScriptElementKindConstructorImplementationElement
// interface Y { ():number; }
ScriptElementKindCallSignatureElement
// interface Y { []:number; }
ScriptElementKindIndexSignatureElement
// interface Y { new():Y; }
ScriptElementKindConstructSignatureElement
// function foo(*Y*: string)
ScriptElementKindParameterElement
ScriptElementKindTypeParameterElement
ScriptElementKindPrimitiveType
ScriptElementKindLabel
ScriptElementKindAlias
ScriptElementKindConstElement
ScriptElementKindLetElement
ScriptElementKindDirectory
ScriptElementKindExternalModuleName
// String literal
ScriptElementKindString
// Jsdoc @link: in `{@link C link text}`, the before and after text "{@link " and "}"
ScriptElementKindLink
// Jsdoc @link: in `{@link C link text}`, the entity name "C"
ScriptElementKindLinkName
// Jsdoc @link: in `{@link C link text}`, the link text "link text"
ScriptElementKindLinkText
)
type ScriptElementKindModifier uint32
const (
ScriptElementKindModifierNone ScriptElementKindModifier = 0
ScriptElementKindModifierPublic ScriptElementKindModifier = 1 << iota
ScriptElementKindModifierPrivate
ScriptElementKindModifierProtected
ScriptElementKindModifierExported
ScriptElementKindModifierAmbient
ScriptElementKindModifierStatic
ScriptElementKindModifierAbstract
ScriptElementKindModifierOptional
ScriptElementKindModifierDeprecated
ScriptElementKindModifierDts
ScriptElementKindModifierTs
ScriptElementKindModifierTsx
ScriptElementKindModifierJs
ScriptElementKindModifierJsx
ScriptElementKindModifierJson
ScriptElementKindModifierDmts
ScriptElementKindModifierMts
ScriptElementKindModifierMjs
ScriptElementKindModifierDcts
ScriptElementKindModifierCts
ScriptElementKindModifierCjs
)
var scriptElementKindModifierNames = []struct {
flag ScriptElementKindModifier
name string
}{
{ScriptElementKindModifierPublic, "public"},
{ScriptElementKindModifierPrivate, "private"},
{ScriptElementKindModifierProtected, "protected"},
{ScriptElementKindModifierExported, "export"},
{ScriptElementKindModifierAmbient, "declare"},
{ScriptElementKindModifierStatic, "static"},
{ScriptElementKindModifierAbstract, "abstract"},
{ScriptElementKindModifierOptional, "optional"},
{ScriptElementKindModifierDeprecated, "deprecated"},
{ScriptElementKindModifierDts, ".d.ts"},
{ScriptElementKindModifierTs, ".ts"},
{ScriptElementKindModifierTsx, ".tsx"},
{ScriptElementKindModifierJs, ".js"},
{ScriptElementKindModifierJsx, ".jsx"},
{ScriptElementKindModifierJson, ".json"},
{ScriptElementKindModifierDmts, ".d.mts"},
{ScriptElementKindModifierMts, ".mts"},
{ScriptElementKindModifierMjs, ".mjs"},
{ScriptElementKindModifierDcts, ".d.cts"},
{ScriptElementKindModifierCts, ".cts"},
{ScriptElementKindModifierCjs, ".cjs"},
}
func (m ScriptElementKindModifier) Strings() collections.Set[string] {
result := collections.Set[string]{}
for _, entry := range scriptElementKindModifierNames {
if m&entry.flag != 0 {
result.Add(entry.name)
}
}
return result
}
var FileExtensionKindModifiers = 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 := symbol.CombinedLocalAndExportSymbolFlags()
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 {
var roots []*ast.Symbol
if typeChecker != nil {
roots = typeChecker.GetRootSymbols(symbol)
} else {
roots = []*ast.Symbol{symbol}
}
// If this is a method from a mapped type, leave as a method so long as it still has a call signature, as opposed to e.g.
// `{ [K in keyof I]: number }`.
if len(roots) == 1 &&
roots[0].Flags&ast.SymbolFlagsMethod != 0 &&
(typeChecker == nil || len(typeChecker.GetCallSignatures(typeChecker.GetNonNullableType(typeChecker.GetTypeOfSymbolAtLocation(symbol, location)))) > 0) {
return ScriptElementKindMemberFunctionElement
}
if typeChecker != nil {
if typeChecker.IsUndefinedSymbol(symbol) {
return ScriptElementKindVariableElement
}
if typeChecker.IsArgumentsSymbol(symbol) {
return ScriptElementKindLocalVariableElement
}
if location.Kind == ast.KindThisKeyword && ast.IsExpression(location) ||
ast.IsThisInTypeQuery(location) {
return ScriptElementKindParameterElement
}
}
flags := symbol.CombinedLocalAndExportSymbolFlags()
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 typeChecker != nil && flags&ast.SymbolFlagsTransient != 0 && symbol.CheckFlags&ast.CheckFlagsSynthetic != 0 {
// If union property is result of union of non method (property/accessors/variables), it is labeled as property
var unionPropertyKind ScriptElementKind
for _, rootSymbol := range roots {
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.IsParameterDeclaration(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) ScriptElementKindModifier {
if symbol == nil {
return ScriptElementKindModifierNone
}
modifiers := getNormalizedSymbolModifiers(typeChecker, symbol)
if symbol.Flags&ast.SymbolFlagsAlias != 0 && typeChecker != nil {
resolvedSymbol := typeChecker.GetAliasedSymbol(symbol)
if resolvedSymbol != symbol {
modifiers |= getNormalizedSymbolModifiers(typeChecker, resolvedSymbol)
}
}
if symbol.Flags&ast.SymbolFlagsOptional != 0 {
modifiers |= ScriptElementKindModifierOptional
}
return modifiers
}
func getNormalizedSymbolModifiers(typeChecker *checker.Checker, symbol *ast.Symbol) ScriptElementKindModifier {
var modifierSet 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 &&
isDeprecatedDeclaration(typeChecker, declaration) && // !!! include jsdoc node flags
core.Some(declarations, func(d *ast.Node) bool { return !isDeprecatedDeclaration(typeChecker, d) }) {
excludeFlags = ast.ModifierFlagsDeprecated
} else {
excludeFlags = ast.ModifierFlagsNone
}
modifierSet = getNodeModifiers(typeChecker, declaration, excludeFlags)
}
return modifierSet
}
func isDeprecatedDeclaration(typeChecker *checker.Checker, declaration *ast.Node) bool {
if typeChecker != nil {
return typeChecker.IsDeprecatedDeclaration(declaration)
}
return ast.IsDeprecatedDeclaration(declaration)
}
func getNodeModifiers(typeChecker *checker.Checker, node *ast.Node, excludeFlags ast.ModifierFlags) ScriptElementKindModifier {
var result ScriptElementKindModifier
var flags ast.ModifierFlags
if ast.IsDeclaration(node) {
flags = ast.GetCombinedModifierFlags(node)
if isDeprecatedDeclaration(typeChecker, node) {
flags |= ast.ModifierFlagsDeprecated
}
flags &^= excludeFlags
}
if flags&ast.ModifierFlagsPrivate != 0 {
result |= ScriptElementKindModifierPrivate
}
if flags&ast.ModifierFlagsProtected != 0 {
result |= ScriptElementKindModifierProtected
}
if flags&ast.ModifierFlagsPublic != 0 {
result |= ScriptElementKindModifierPublic
}
if flags&ast.ModifierFlagsStatic != 0 {
result |= ScriptElementKindModifierStatic
}
if flags&ast.ModifierFlagsAbstract != 0 {
result |= ScriptElementKindModifierAbstract
}
if flags&ast.ModifierFlagsExport != 0 {
result |= ScriptElementKindModifierExported
}
if flags&ast.ModifierFlagsDeprecated != 0 {
result |= ScriptElementKindModifierDeprecated
}
if flags&ast.ModifierFlagsAmbient != 0 {
result |= ScriptElementKindModifierAmbient
}
if node.Flags&ast.NodeFlagsAmbient != 0 {
result |= ScriptElementKindModifierAmbient
}
if node.Kind == ast.KindExportAssignment {
result |= ScriptElementKindModifierExported
}
return result
}