1405 lines
50 KiB
Go
1405 lines
50 KiB
Go
package ls
|
|
|
|
import (
|
|
"fmt"
|
|
"iter"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/microsoft/typescript-go/internal/ast"
|
|
"github.com/microsoft/typescript-go/internal/astnav"
|
|
"github.com/microsoft/typescript-go/internal/checker"
|
|
"github.com/microsoft/typescript-go/internal/collections"
|
|
"github.com/microsoft/typescript-go/internal/compiler"
|
|
"github.com/microsoft/typescript-go/internal/core"
|
|
"github.com/microsoft/typescript-go/internal/debug"
|
|
"github.com/microsoft/typescript-go/internal/jsnum"
|
|
"github.com/microsoft/typescript-go/internal/ls/lsconv"
|
|
"github.com/microsoft/typescript-go/internal/ls/lsutil"
|
|
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
|
|
"github.com/microsoft/typescript-go/internal/scanner"
|
|
"github.com/microsoft/typescript-go/internal/stringutil"
|
|
"github.com/microsoft/typescript-go/internal/tspath"
|
|
)
|
|
|
|
var quoteReplacer = strings.NewReplacer("'", `\'`, `\"`, `"`)
|
|
|
|
func IsInString(sourceFile *ast.SourceFile, position int, previousToken *ast.Node) bool {
|
|
if previousToken != nil && ast.IsStringTextContainingNode(previousToken) {
|
|
start := astnav.GetStartOfNode(previousToken, sourceFile, false /*includeJSDoc*/)
|
|
end := previousToken.End()
|
|
|
|
// To be "in" one of these literals, the position has to be:
|
|
// 1. entirely within the token text.
|
|
// 2. at the end position of an unterminated token.
|
|
// 3. at the end of a regular expression (due to trailing flags like '/foo/g').
|
|
if start < position && position < end {
|
|
return true
|
|
}
|
|
|
|
if position == end {
|
|
return ast.IsUnterminatedLiteral(previousToken)
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isModuleSpecifierLike(node *ast.Node) bool {
|
|
if !ast.IsStringLiteralLike(node) {
|
|
return false
|
|
}
|
|
|
|
if ast.IsRequireCall(node.Parent, false /*requireStringLiteralLikeArgument*/) || ast.IsImportCall(node.Parent) {
|
|
return node.Parent.Arguments()[0] == node
|
|
}
|
|
|
|
return node.Parent.Kind == ast.KindExternalModuleReference ||
|
|
node.Parent.Kind == ast.KindImportDeclaration ||
|
|
node.Parent.Kind == ast.KindJSImportDeclaration
|
|
}
|
|
|
|
func getNonModuleSymbolOfMergedModuleSymbol(symbol *ast.Symbol) *ast.Symbol {
|
|
if len(symbol.Declarations) == 0 || (symbol.Flags&(ast.SymbolFlagsModule|ast.SymbolFlagsTransient)) == 0 {
|
|
return nil
|
|
}
|
|
|
|
if decl := core.Find(symbol.Declarations, func(d *ast.Node) bool { return !ast.IsSourceFile(d) && !ast.IsModuleDeclaration(d) }); decl != nil {
|
|
return decl.Symbol()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getLocalSymbolForExportSpecifier(referenceLocation *ast.Node, referenceSymbol *ast.Symbol, exportSpecifier *ast.ExportSpecifier, ch *checker.Checker) *ast.Symbol {
|
|
if isExportSpecifierAlias(referenceLocation, exportSpecifier) {
|
|
if symbol := ch.GetExportSpecifierLocalTargetSymbol(exportSpecifier.AsNode()); symbol != nil {
|
|
return symbol
|
|
}
|
|
}
|
|
return referenceSymbol
|
|
}
|
|
|
|
func isExportSpecifierAlias(referenceLocation *ast.Node, exportSpecifier *ast.ExportSpecifier) bool {
|
|
debug.Assert(exportSpecifier.PropertyName == referenceLocation || exportSpecifier.Name() == referenceLocation, "referenceLocation is not export specifier name or property name")
|
|
propertyName := exportSpecifier.PropertyName
|
|
if propertyName != nil {
|
|
// Given `export { foo as bar } [from "someModule"]`: It's an alias at `foo`, but at `bar` it's a new symbol.
|
|
return propertyName == referenceLocation
|
|
} else {
|
|
// `export { foo } from "foo"` is a re-export.
|
|
// `export { foo };` is not a re-export, it creates an alias for the local variable `foo`.
|
|
return exportSpecifier.Parent.Parent.ModuleSpecifier() == nil
|
|
}
|
|
}
|
|
|
|
func isInComment(file *ast.SourceFile, position int, tokenAtPosition *ast.Node) *ast.CommentRange {
|
|
return getRangeOfEnclosingComment(file, position, astnav.FindPrecedingToken(file, position), tokenAtPosition)
|
|
}
|
|
|
|
func positionBelongsToNode(candidate *ast.Node, position int, file *ast.SourceFile) bool {
|
|
return lsutil.PositionBelongsToNode(candidate, position, file)
|
|
}
|
|
|
|
type PossibleTypeArgumentInfo struct {
|
|
called *ast.IdentifierNode
|
|
nTypeArguments int
|
|
}
|
|
|
|
// Get info for an expression like `f <` that may be the start of type arguments.
|
|
func getPossibleTypeArgumentsInfo(tokenIn *ast.Node, sourceFile *ast.SourceFile) *PossibleTypeArgumentInfo {
|
|
// This is a rare case, but one that saves on a _lot_ of work if true - if the source file has _no_ `<` character,
|
|
// then there obviously can't be any type arguments - no expensive brace-matching backwards scanning required
|
|
if strings.LastIndexByte(sourceFile.Text(), '<') == -1 {
|
|
return nil
|
|
}
|
|
|
|
token := tokenIn
|
|
// This function determines if the node could be a type argument position
|
|
// When editing, it is common to have an incomplete type argument list (e.g. missing ">"),
|
|
// so the tree can have any shape depending on the tokens before the current node.
|
|
// Instead, scanning for an identifier followed by a "<" before current node
|
|
// will typically give us better results than inspecting the tree.
|
|
// Note that we also balance out the already provided type arguments, arrays, object literals while doing so.
|
|
remainingLessThanTokens := 0
|
|
nTypeArguments := 0
|
|
for token != nil {
|
|
switch token.Kind {
|
|
case ast.KindLessThanToken:
|
|
// Found the beginning of the generic argument expression
|
|
token = astnav.FindPrecedingToken(sourceFile, token.Pos())
|
|
if token != nil && token.Kind == ast.KindQuestionDotToken {
|
|
token = astnav.FindPrecedingToken(sourceFile, token.Pos())
|
|
}
|
|
if token == nil || !ast.IsIdentifier(token) {
|
|
return nil
|
|
}
|
|
if remainingLessThanTokens == 0 {
|
|
if ast.IsDeclarationName(token) {
|
|
return nil
|
|
}
|
|
return &PossibleTypeArgumentInfo{
|
|
called: token,
|
|
nTypeArguments: nTypeArguments,
|
|
}
|
|
}
|
|
remainingLessThanTokens--
|
|
case ast.KindGreaterThanGreaterThanGreaterThanToken:
|
|
remainingLessThanTokens += 3
|
|
case ast.KindGreaterThanGreaterThanToken:
|
|
remainingLessThanTokens += 2
|
|
case ast.KindGreaterThanToken:
|
|
remainingLessThanTokens++
|
|
case ast.KindCloseBraceToken:
|
|
// This can be object type, skip until we find the matching open brace token
|
|
// Skip until the matching open brace token
|
|
token = findPrecedingMatchingToken(token, ast.KindOpenBraceToken, sourceFile)
|
|
if token == nil {
|
|
return nil
|
|
}
|
|
case ast.KindCloseParenToken:
|
|
// This can be object type, skip until we find the matching open brace token
|
|
// Skip until the matching open brace token
|
|
token = findPrecedingMatchingToken(token, ast.KindOpenParenToken, sourceFile)
|
|
if token == nil {
|
|
return nil
|
|
}
|
|
case ast.KindCloseBracketToken:
|
|
// This can be object type, skip until we find the matching open brace token
|
|
// Skip until the matching open brace token
|
|
token = findPrecedingMatchingToken(token, ast.KindOpenBracketToken, sourceFile)
|
|
if token == nil {
|
|
return nil
|
|
}
|
|
case ast.KindCommaToken:
|
|
// Valid tokens in a type name. Skip.
|
|
nTypeArguments++
|
|
case ast.KindEqualsGreaterThanToken, ast.KindIdentifier, ast.KindStringLiteral, ast.KindNumericLiteral,
|
|
ast.KindBigIntLiteral, ast.KindTrueKeyword, ast.KindFalseKeyword, ast.KindTypeOfKeyword, ast.KindExtendsKeyword,
|
|
ast.KindKeyOfKeyword, ast.KindDotToken, ast.KindBarToken, ast.KindQuestionToken, ast.KindColonToken:
|
|
// do nothing
|
|
default:
|
|
if !ast.IsTypeNode(token) {
|
|
// Invalid token in type
|
|
return nil
|
|
}
|
|
}
|
|
token = astnav.FindPrecedingToken(sourceFile, token.Pos())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func isNameOfModuleDeclaration(node *ast.Node) bool {
|
|
if node.Parent.Kind != ast.KindModuleDeclaration {
|
|
return false
|
|
}
|
|
return node.Parent.Name() == node
|
|
}
|
|
|
|
func isExpressionOfExternalModuleImportEqualsDeclaration(node *ast.Node) bool {
|
|
return ast.IsExternalModuleImportEqualsDeclaration(node.Parent.Parent) && ast.GetExternalModuleImportEqualsDeclarationExpression(node.Parent.Parent) == node
|
|
}
|
|
|
|
func isNamespaceReference(node *ast.Node) bool {
|
|
return isQualifiedNameNamespaceReference(node) || isPropertyAccessNamespaceReference(node)
|
|
}
|
|
|
|
func isQualifiedNameNamespaceReference(node *ast.Node) bool {
|
|
root := node
|
|
isLastClause := true
|
|
if root.Parent.Kind == ast.KindQualifiedName {
|
|
for root.Parent != nil && root.Parent.Kind == ast.KindQualifiedName {
|
|
root = root.Parent
|
|
}
|
|
|
|
isLastClause = root.AsQualifiedName().Right == node
|
|
}
|
|
|
|
return root.Parent.Kind == ast.KindTypeReference && !isLastClause
|
|
}
|
|
|
|
func isPropertyAccessNamespaceReference(node *ast.Node) bool {
|
|
root := node
|
|
isLastClause := true
|
|
if root.Parent.Kind == ast.KindPropertyAccessExpression {
|
|
for root.Parent != nil && root.Parent.Kind == ast.KindPropertyAccessExpression {
|
|
root = root.Parent
|
|
}
|
|
|
|
isLastClause = root.Name() == node
|
|
}
|
|
|
|
if !isLastClause && root.Parent.Kind == ast.KindExpressionWithTypeArguments && root.Parent.Parent.Kind == ast.KindHeritageClause {
|
|
decl := root.Parent.Parent.Parent
|
|
return (decl.Kind == ast.KindClassDeclaration && root.Parent.Parent.AsHeritageClause().Token == ast.KindImplementsKeyword) ||
|
|
(decl.Kind == ast.KindInterfaceDeclaration && root.Parent.Parent.AsHeritageClause().Token == ast.KindExtendsKeyword)
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func isThis(node *ast.Node) bool {
|
|
switch node.Kind {
|
|
case ast.KindThisKeyword:
|
|
// case ast.KindThisType: TODO: GH#9267
|
|
return true
|
|
case ast.KindIdentifier:
|
|
// 'this' as a parameter
|
|
return node.Text() == "this" && node.Parent.Kind == ast.KindParameter
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func isTypeReference(node *ast.Node) bool {
|
|
if ast.IsRightSideOfQualifiedNameOrPropertyAccess(node) {
|
|
node = node.Parent
|
|
}
|
|
|
|
switch node.Kind {
|
|
case ast.KindThisKeyword:
|
|
return !ast.IsExpressionNode(node)
|
|
case ast.KindThisType:
|
|
return true
|
|
}
|
|
|
|
switch node.Parent.Kind {
|
|
case ast.KindTypeReference:
|
|
return true
|
|
case ast.KindImportType:
|
|
return !node.Parent.AsImportTypeNode().IsTypeOf
|
|
case ast.KindExpressionWithTypeArguments:
|
|
return ast.IsPartOfTypeNode(node.Parent)
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func isInRightSideOfInternalImportEqualsDeclaration(node *ast.Node) bool {
|
|
if node.Parent == nil {
|
|
return false
|
|
}
|
|
for node.Parent.Kind == ast.KindQualifiedName {
|
|
node = node.Parent
|
|
}
|
|
|
|
return ast.IsInternalModuleImportEqualsDeclaration(node.Parent) && node.Parent.AsImportEqualsDeclaration().ModuleReference == node
|
|
}
|
|
|
|
func (l *LanguageService) createLspRangeFromNode(node *ast.Node, file *ast.SourceFile) lsproto.Range {
|
|
return l.createLspRangeFromBounds(scanner.GetTokenPosOfNode(node, file, false /*includeJSDoc*/), node.End(), file)
|
|
}
|
|
|
|
func createRangeFromNode(node *ast.Node, file *ast.SourceFile) core.TextRange {
|
|
return core.NewTextRange(scanner.GetTokenPosOfNode(node, file, false /*includeJSDoc*/), node.End())
|
|
}
|
|
|
|
func (l *LanguageService) createLspRangeFromBounds(start, end int, file *ast.SourceFile) lsproto.Range {
|
|
return l.converters.ToLSPRange(file, core.NewTextRange(start, end))
|
|
}
|
|
|
|
func (l *LanguageService) createLspRangeFromRange(textRange core.TextRange, script lsconv.Script) lsproto.Range {
|
|
return l.converters.ToLSPRange(script, textRange)
|
|
}
|
|
|
|
func (l *LanguageService) createLspPosition(position int, file *ast.SourceFile) lsproto.Position {
|
|
return l.converters.PositionToLineAndCharacter(file, core.TextPos(position))
|
|
}
|
|
|
|
func quote(file *ast.SourceFile, preferences lsutil.UserPreferences, text string) string {
|
|
// Editors can pass in undefined or empty string - we want to infer the preference in those cases.
|
|
quotePreference := lsutil.GetQuotePreference(file, preferences)
|
|
quoted, _ := core.StringifyJson(text, "" /*prefix*/, "" /*indent*/)
|
|
if quotePreference == lsutil.QuotePreferenceSingle {
|
|
quoted = quoteReplacer.Replace(stringutil.StripQuotes(quoted))
|
|
}
|
|
return quoted
|
|
}
|
|
|
|
var typeKeywords *collections.Set[ast.Kind] = collections.NewSetFromItems(
|
|
ast.KindAnyKeyword,
|
|
ast.KindAssertsKeyword,
|
|
ast.KindBigIntKeyword,
|
|
ast.KindBooleanKeyword,
|
|
ast.KindFalseKeyword,
|
|
ast.KindInferKeyword,
|
|
ast.KindKeyOfKeyword,
|
|
ast.KindNeverKeyword,
|
|
ast.KindNullKeyword,
|
|
ast.KindNumberKeyword,
|
|
ast.KindObjectKeyword,
|
|
ast.KindReadonlyKeyword,
|
|
ast.KindStringKeyword,
|
|
ast.KindSymbolKeyword,
|
|
ast.KindTypeOfKeyword,
|
|
ast.KindTrueKeyword,
|
|
ast.KindVoidKeyword,
|
|
ast.KindUndefinedKeyword,
|
|
ast.KindUniqueKeyword,
|
|
ast.KindUnknownKeyword,
|
|
)
|
|
|
|
func isTypeKeyword(kind ast.Kind) bool {
|
|
return typeKeywords.Has(kind)
|
|
}
|
|
|
|
func isSeparator(node *ast.Node, candidate *ast.Node) bool {
|
|
return candidate != nil && node.Parent != nil && (candidate.Kind == ast.KindCommaToken || (candidate.Kind == ast.KindSemicolonToken && node.Parent.Kind == ast.KindObjectLiteralExpression))
|
|
}
|
|
|
|
func isLiteralNameOfPropertyDeclarationOrIndexAccess(node *ast.Node) bool {
|
|
// utilities
|
|
switch node.Parent.Kind {
|
|
case ast.KindPropertyDeclaration,
|
|
ast.KindPropertySignature,
|
|
ast.KindPropertyAssignment,
|
|
ast.KindEnumMember,
|
|
ast.KindMethodDeclaration,
|
|
ast.KindMethodSignature,
|
|
ast.KindGetAccessor,
|
|
ast.KindSetAccessor,
|
|
ast.KindModuleDeclaration:
|
|
return ast.GetNameOfDeclaration(node.Parent) == node
|
|
case ast.KindElementAccessExpression:
|
|
return node.Parent.AsElementAccessExpression().ArgumentExpression == node
|
|
case ast.KindComputedPropertyName:
|
|
return true
|
|
case ast.KindLiteralType:
|
|
return node.Parent.Parent.Kind == ast.KindIndexedAccessType
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func isObjectBindingElementWithoutPropertyName(bindingElement *ast.Node) bool {
|
|
return bindingElement.Kind == ast.KindBindingElement &&
|
|
bindingElement.Parent.Kind == ast.KindObjectBindingPattern &&
|
|
bindingElement.Name().Kind == ast.KindIdentifier &&
|
|
bindingElement.PropertyName() == nil
|
|
}
|
|
|
|
func isRightSideOfPropertyAccess(node *ast.Node) bool {
|
|
return node.Parent != nil && node.Parent.Kind == ast.KindPropertyAccessExpression && node.Parent.Name() == node
|
|
}
|
|
|
|
func isStaticSymbol(symbol *ast.Symbol) bool {
|
|
if symbol.ValueDeclaration == nil {
|
|
return false
|
|
}
|
|
modifierFlags := symbol.ValueDeclaration.ModifierFlags()
|
|
return modifierFlags&ast.ModifierFlagsStatic != 0
|
|
}
|
|
|
|
func isImplementation(node *ast.Node) bool {
|
|
if node.Flags&ast.NodeFlagsAmbient != 0 {
|
|
return !(node.Kind == ast.KindInterfaceDeclaration || node.Kind == ast.KindTypeAliasDeclaration)
|
|
}
|
|
if ast.IsVariableLike(node) {
|
|
return ast.HasInitializer(node)
|
|
}
|
|
if ast.IsFunctionLikeDeclaration(node) {
|
|
return node.Body() != nil
|
|
}
|
|
return ast.IsClassLike(node) || ast.IsModuleOrEnumDeclaration(node)
|
|
}
|
|
|
|
func isImplementationExpression(node *ast.Node) bool {
|
|
switch node.Kind {
|
|
case ast.KindParenthesizedExpression:
|
|
return isImplementationExpression(node.Expression())
|
|
case ast.KindArrowFunction, ast.KindFunctionExpression, ast.KindObjectLiteralExpression, ast.KindClassExpression, ast.KindArrayLiteralExpression:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func isReadonlyTypeOperator(node *ast.Node) bool {
|
|
return node.Kind == ast.KindReadonlyKeyword && node.Parent.Kind == ast.KindTypeOperator && node.Parent.AsTypeOperatorNode().Operator == ast.KindReadonlyKeyword
|
|
}
|
|
|
|
func isJumpStatementTarget(node *ast.Node) bool {
|
|
return node.Kind == ast.KindIdentifier && ast.IsBreakOrContinueStatement(node.Parent) && node.Parent.Label() == node
|
|
}
|
|
|
|
func isLabelOfLabeledStatement(node *ast.Node) bool {
|
|
return node.Kind == ast.KindIdentifier && node.Parent.Kind == ast.KindLabeledStatement && node.Parent.Label() == node
|
|
}
|
|
|
|
func findReferenceInPosition(refs []*ast.FileReference, pos int) *ast.FileReference {
|
|
return core.Find(refs, func(ref *ast.FileReference) bool { return ref.TextRange.ContainsInclusive(pos) })
|
|
}
|
|
|
|
func getContainingNodeIfInHeritageClause(node *ast.Node) *ast.Node {
|
|
if node.Kind == ast.KindIdentifier || node.Kind == ast.KindPropertyAccessExpression {
|
|
return getContainingNodeIfInHeritageClause(node.Parent)
|
|
}
|
|
if node.Kind == ast.KindExpressionWithTypeArguments && (ast.IsClassLike(node.Parent.Parent) || node.Parent.Parent.Kind == ast.KindInterfaceDeclaration) {
|
|
return node.Parent.Parent
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getContainerNode(node *ast.Node) *ast.Node {
|
|
for parent := node.Parent; parent != nil; parent = parent.Parent {
|
|
switch parent.Kind {
|
|
case ast.KindSourceFile, ast.KindMethodDeclaration, ast.KindMethodSignature, ast.KindFunctionDeclaration, ast.KindFunctionExpression,
|
|
ast.KindGetAccessor, ast.KindSetAccessor, ast.KindClassDeclaration, ast.KindInterfaceDeclaration, ast.KindEnumDeclaration, ast.KindModuleDeclaration:
|
|
return parent
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getAdjustedLocation(node *ast.Node, forRename bool, sourceFile *ast.SourceFile) *ast.Node {
|
|
// todo: check if this function needs to be changed for jsdoc updates
|
|
|
|
parent := node.Parent
|
|
// /**/<modifier> [|name|] ...
|
|
// /**/<modifier> <class|interface|type|enum|module|namespace|function|get|set> [|name|] ...
|
|
// /**/<class|interface|type|enum|module|namespace|function|get|set> [|name|] ...
|
|
// /**/import [|name|] = ...
|
|
//
|
|
// NOTE: If the node is a modifier, we don't adjust its location if it is the `default` modifier as that is handled
|
|
// specially by `getSymbolAtLocation`.
|
|
isModifier := func(node *ast.Node) bool {
|
|
if ast.IsModifier(node) && (forRename || node.Kind != ast.KindDefaultKeyword) {
|
|
return ast.CanHaveModifiers(parent) && slices.Contains(parent.ModifierNodes(), node)
|
|
}
|
|
switch node.Kind {
|
|
case ast.KindClassKeyword:
|
|
return ast.IsClassDeclaration(parent) || ast.IsClassExpression(node)
|
|
case ast.KindFunctionKeyword:
|
|
return ast.IsFunctionDeclaration(parent) || ast.IsFunctionExpression(node)
|
|
case ast.KindInterfaceKeyword:
|
|
return ast.IsInterfaceDeclaration(parent)
|
|
case ast.KindEnumKeyword:
|
|
return ast.IsEnumDeclaration(parent)
|
|
case ast.KindTypeKeyword:
|
|
return ast.IsTypeAliasDeclaration(parent)
|
|
case ast.KindNamespaceKeyword, ast.KindModuleKeyword:
|
|
return ast.IsModuleDeclaration(parent)
|
|
case ast.KindImportKeyword:
|
|
return ast.IsImportEqualsDeclaration(parent)
|
|
case ast.KindGetKeyword:
|
|
return ast.IsGetAccessorDeclaration(parent)
|
|
case ast.KindSetKeyword:
|
|
return ast.IsSetAccessorDeclaration(parent)
|
|
}
|
|
return false
|
|
}
|
|
if isModifier(node) {
|
|
if sourceFile == nil {
|
|
sourceFile = ast.GetSourceFileOfNode(node)
|
|
}
|
|
if location := getAdjustedLocationForDeclaration(parent, forRename, sourceFile); location != nil {
|
|
return location
|
|
}
|
|
}
|
|
|
|
// /**/<var|let|const> [|name|] ...
|
|
if (node.Kind == ast.KindVarKeyword || node.Kind == ast.KindConstKeyword || node.Kind == ast.KindLetKeyword) &&
|
|
ast.IsVariableDeclarationList(parent) && len(parent.AsVariableDeclarationList().Declarations.Nodes) == 1 {
|
|
declaration := parent.AsVariableDeclarationList().Declarations.Nodes[0].AsVariableDeclaration()
|
|
if ast.IsIdentifier(declaration.Name()) {
|
|
return declaration.Name()
|
|
}
|
|
}
|
|
|
|
if node.Kind == ast.KindTypeKeyword {
|
|
// import /**/type [|name|] from ...;
|
|
// import /**/type { [|name|] } from ...;
|
|
// import /**/type { propertyName as [|name|] } from ...;
|
|
// import /**/type ... from "[|module|]";
|
|
if ast.IsImportClause(parent) && parent.IsTypeOnly() {
|
|
if location := getAdjustedLocationForImportDeclaration(parent.Parent.AsImportDeclaration(), forRename); location != nil {
|
|
return location
|
|
}
|
|
}
|
|
// export /**/type { [|name|] } from ...;
|
|
// export /**/type { propertyName as [|name|] } from ...;
|
|
// export /**/type * from "[|module|]";
|
|
// export /**/type * as ... from "[|module|]";
|
|
if ast.IsExportDeclaration(parent) && parent.IsTypeOnly() {
|
|
if location := getAdjustedLocationForExportDeclaration(parent.AsExportDeclaration(), forRename); location != nil {
|
|
return location
|
|
}
|
|
}
|
|
}
|
|
|
|
// import { propertyName /**/as [|name|] } ...
|
|
// import * /**/as [|name|] ...
|
|
// export { propertyName /**/as [|name|] } ...
|
|
// export * /**/as [|name|] ...
|
|
if node.Kind == ast.KindAsKeyword {
|
|
if parent.Kind == ast.KindImportSpecifier && parent.PropertyName() != nil ||
|
|
parent.Kind == ast.KindExportSpecifier && parent.PropertyName() != nil ||
|
|
parent.Kind == ast.KindNamespaceImport ||
|
|
parent.Kind == ast.KindNamespaceExport {
|
|
return parent.Name()
|
|
}
|
|
if parent.Kind == ast.KindExportDeclaration {
|
|
if exportClause := parent.AsExportDeclaration().ExportClause; exportClause != nil && exportClause.Kind == ast.KindNamespaceExport {
|
|
return exportClause.Name()
|
|
}
|
|
}
|
|
}
|
|
|
|
// /**/import [|name|] from ...;
|
|
// /**/import { [|name|] } from ...;
|
|
// /**/import { propertyName as [|name|] } from ...;
|
|
// /**/import ... from "[|module|]";
|
|
// /**/import "[|module|]";
|
|
if node.Kind == ast.KindImportKeyword && parent.Kind == ast.KindImportDeclaration {
|
|
if location := getAdjustedLocationForImportDeclaration(parent.AsImportDeclaration(), forRename); location != nil {
|
|
return location
|
|
}
|
|
}
|
|
|
|
if node.Kind == ast.KindExportKeyword {
|
|
// /**/export { [|name|] } ...;
|
|
// /**/export { propertyName as [|name|] } ...;
|
|
// /**/export * from "[|module|]";
|
|
// /**/export * as ... from "[|module|]";
|
|
if parent.Kind == ast.KindExportDeclaration {
|
|
if location := getAdjustedLocationForExportDeclaration(parent.AsExportDeclaration(), forRename); location != nil {
|
|
return location
|
|
}
|
|
}
|
|
// NOTE: We don't adjust the location of the `default` keyword as that is handled specially by `getSymbolAtLocation`.
|
|
// /**/export default [|name|];
|
|
// /**/export = [|name|];
|
|
if parent.Kind == ast.KindExportAssignment {
|
|
return ast.SkipOuterExpressions(parent.Expression(), ast.OEKAll)
|
|
}
|
|
}
|
|
// import name = /**/require("[|module|]");
|
|
if node.Kind == ast.KindRequireKeyword && parent.Kind == ast.KindExternalModuleReference {
|
|
return parent.Expression()
|
|
}
|
|
// import ... /**/from "[|module|]";
|
|
// export ... /**/from "[|module|]";
|
|
if node.Kind == ast.KindFromKeyword {
|
|
if (parent.Kind == ast.KindImportDeclaration || parent.Kind == ast.KindExportDeclaration) && parent.ModuleSpecifier() != nil {
|
|
return parent.ModuleSpecifier()
|
|
}
|
|
}
|
|
// class ... /**/extends [|name|] ...
|
|
// class ... /**/implements [|name|] ...
|
|
// class ... /**/implements name1, name2 ...
|
|
// interface ... /**/extends [|name|] ...
|
|
// interface ... /**/extends name1, name2 ...
|
|
if (node.Kind == ast.KindExtendsKeyword || node.Kind == ast.KindImplementsKeyword) && parent.Kind == ast.KindHeritageClause && parent.AsHeritageClause().Token == node.Kind {
|
|
getAdjustedLocationForHeritageClause := func(node *ast.HeritageClause) *ast.Node {
|
|
// /**/extends [|name|]
|
|
// /**/implements [|name|]
|
|
if len(node.Types.Nodes) == 1 {
|
|
return node.Types.Nodes[0].Expression()
|
|
}
|
|
|
|
// fall through `getAdjustedLocation`
|
|
// /**/extends name1, name2 ...
|
|
// /**/implements name1, name2 ...
|
|
return nil
|
|
}
|
|
|
|
if location := getAdjustedLocationForHeritageClause(parent.AsHeritageClause()); location != nil {
|
|
return location
|
|
}
|
|
}
|
|
if node.Kind == ast.KindExtendsKeyword {
|
|
// ... <T /**/extends [|U|]> ...
|
|
if parent.Kind == ast.KindTypeParameter {
|
|
if constraint := parent.AsTypeParameterDeclaration().Constraint; constraint != nil && constraint.Kind == ast.KindTypeReference {
|
|
return constraint.AsTypeReferenceNode().TypeName
|
|
}
|
|
}
|
|
// ... T /**/extends [|U|] ? ...
|
|
if parent.Kind == ast.KindConditionalType {
|
|
if extendsType := parent.AsConditionalTypeNode().ExtendsType; extendsType != nil && extendsType.Kind == ast.KindTypeReference {
|
|
return extendsType.AsTypeReferenceNode().TypeName
|
|
}
|
|
}
|
|
}
|
|
// ... T extends /**/infer [|U|] ? ...
|
|
if node.Kind == ast.KindInferKeyword && parent.Kind == ast.KindInferType {
|
|
return parent.AsInferTypeNode().TypeParameter.Name()
|
|
}
|
|
// { [ [|K|] /**/in keyof T]: ... }
|
|
if node.Kind == ast.KindInKeyword && parent.Kind == ast.KindTypeParameter && parent.Parent.Kind == ast.KindMappedType {
|
|
return parent.Name()
|
|
}
|
|
// /**/keyof [|T|]
|
|
if node.Kind == ast.KindKeyOfKeyword && parent.Kind == ast.KindTypeOperator && parent.AsTypeOperatorNode().Operator == ast.KindKeyOfKeyword {
|
|
if parentType := parent.Type(); parentType != nil && parentType.Kind == ast.KindTypeReference {
|
|
return parentType.AsTypeReferenceNode().TypeName
|
|
}
|
|
}
|
|
// /**/readonly [|name|][]
|
|
if node.Kind == ast.KindReadonlyKeyword && parent.Kind == ast.KindTypeOperator && parent.AsTypeOperatorNode().Operator == ast.KindReadonlyKeyword {
|
|
if parentType := parent.Type(); parentType != nil && parentType.Kind == ast.KindArrayType && parentType.AsArrayTypeNode().ElementType.Kind == ast.KindTypeReference {
|
|
return parentType.AsArrayTypeNode().ElementType.AsTypeReferenceNode().TypeName
|
|
}
|
|
}
|
|
|
|
if !forRename {
|
|
// /**/new [|name|]
|
|
// /**/void [|name|]
|
|
// /**/void obj.[|name|]
|
|
// /**/typeof [|name|]
|
|
// /**/typeof obj.[|name|]
|
|
// /**/await [|name|]
|
|
// /**/await obj.[|name|]
|
|
// /**/yield [|name|]
|
|
// /**/yield obj.[|name|]
|
|
// /**/delete obj.[|name|]
|
|
if node.Kind == ast.KindNewKeyword && parent.Kind == ast.KindNewExpression ||
|
|
node.Kind == ast.KindVoidKeyword && parent.Kind == ast.KindVoidExpression ||
|
|
node.Kind == ast.KindTypeOfKeyword && parent.Kind == ast.KindTypeOfExpression ||
|
|
node.Kind == ast.KindAwaitKeyword && parent.Kind == ast.KindAwaitExpression ||
|
|
node.Kind == ast.KindYieldKeyword && parent.Kind == ast.KindYieldExpression ||
|
|
node.Kind == ast.KindDeleteKeyword && parent.Kind == ast.KindDeleteExpression {
|
|
if expr := parent.Expression(); expr != nil {
|
|
return ast.SkipOuterExpressions(expr, ast.OEKAll)
|
|
}
|
|
}
|
|
|
|
// left /**/in [|name|]
|
|
// left /**/instanceof [|name|]
|
|
if (node.Kind == ast.KindInKeyword || node.Kind == ast.KindInstanceOfKeyword) && parent.Kind == ast.KindBinaryExpression && parent.AsBinaryExpression().OperatorToken == node {
|
|
return ast.SkipOuterExpressions(parent.AsBinaryExpression().Right, ast.OEKAll)
|
|
}
|
|
|
|
// left /**/as [|name|]
|
|
if node.Kind == ast.KindAsKeyword && parent.Kind == ast.KindAsExpression {
|
|
if asExprType := parent.Type(); asExprType != nil && asExprType.Kind == ast.KindTypeReference {
|
|
return asExprType.AsTypeReferenceNode().TypeName
|
|
}
|
|
}
|
|
|
|
// for (... /**/in [|name|])
|
|
// for (... /**/of [|name|])
|
|
if node.Kind == ast.KindInKeyword && parent.Kind == ast.KindForInStatement ||
|
|
node.Kind == ast.KindOfKeyword && parent.Kind == ast.KindForOfStatement {
|
|
return ast.SkipOuterExpressions(parent.Expression(), ast.OEKAll)
|
|
}
|
|
}
|
|
|
|
return node
|
|
}
|
|
|
|
func getAdjustedLocationForDeclaration(node *ast.Node, forRename bool, sourceFile *ast.SourceFile) *ast.Node {
|
|
if node.Name() != nil {
|
|
return node.Name()
|
|
}
|
|
if forRename {
|
|
return nil
|
|
}
|
|
switch node.Kind {
|
|
case ast.KindClassDeclaration, ast.KindFunctionDeclaration:
|
|
// for class and function declarations, use the `default` modifier
|
|
// when the declaration is unnamed.
|
|
return core.Find(node.ModifierNodes(), func(*ast.Node) bool { return node.Kind == ast.KindDefaultKeyword })
|
|
case ast.KindClassExpression:
|
|
// for class expressions, use the `class` keyword when the class is unnamed
|
|
return astnav.FindChildOfKind(node, ast.KindClassKeyword, sourceFile)
|
|
case ast.KindFunctionExpression:
|
|
// for function expressions, use the `function` keyword when the function is unnamed
|
|
return astnav.FindChildOfKind(node, ast.KindFunctionKeyword, sourceFile)
|
|
case ast.KindConstructor:
|
|
return node
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getAdjustedLocationForImportDeclaration(node *ast.ImportDeclaration, forRename bool) *ast.Node {
|
|
if node.ImportClause != nil {
|
|
if name := node.ImportClause.Name(); name != nil {
|
|
if node.ImportClause.AsImportClause().NamedBindings != nil {
|
|
// do not adjust if we have both a name and named bindings
|
|
return nil
|
|
}
|
|
// /**/import [|name|] from ...;
|
|
// import /**/type [|name|] from ...;
|
|
return node.ImportClause.Name()
|
|
}
|
|
|
|
// /**/import { [|name|] } from ...;
|
|
// /**/import { propertyName as [|name|] } from ...;
|
|
// /**/import * as [|name|] from ...;
|
|
// import /**/type { [|name|] } from ...;
|
|
// import /**/type { propertyName as [|name|] } from ...;
|
|
// import /**/type * as [|name|] from ...;
|
|
if namedBindings := node.ImportClause.AsImportClause().NamedBindings; namedBindings != nil {
|
|
switch namedBindings.Kind {
|
|
case ast.KindNamedImports:
|
|
// do nothing if there is more than one binding
|
|
elements := namedBindings.Elements()
|
|
if len(elements) != 1 {
|
|
return nil
|
|
}
|
|
return elements[0].Name()
|
|
|
|
case ast.KindNamespaceImport:
|
|
return namedBindings.Name()
|
|
|
|
}
|
|
}
|
|
}
|
|
if !forRename {
|
|
// /**/import "[|module|]";
|
|
// /**/import ... from "[|module|]";
|
|
// import /**/type ... from "[|module|]";
|
|
return node.ModuleSpecifier
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getAdjustedLocationForExportDeclaration(node *ast.ExportDeclaration, forRename bool) *ast.Node {
|
|
if node.ExportClause != nil {
|
|
// /**/export { [|name|] } ...
|
|
// /**/export { propertyName as [|name|] } ...
|
|
// /**/export * as [|name|] ...
|
|
// export /**/type { [|name|] } from ...
|
|
// export /**/type { propertyName as [|name|] } from ...
|
|
// export /**/type * as [|name|] ...
|
|
switch node.ExportClause.Kind {
|
|
case ast.KindNamedExports:
|
|
// do nothing if there is more than one binding
|
|
elements := node.ExportClause.Elements()
|
|
if len(elements) != 1 {
|
|
return nil
|
|
}
|
|
return elements[0].Name()
|
|
case ast.KindNamespaceExport:
|
|
return node.ExportClause.Name()
|
|
}
|
|
}
|
|
if !forRename {
|
|
// /**/export * from "[|module|]";
|
|
// export /**/type * from "[|module|]";
|
|
return node.ModuleSpecifier
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func symbolFlagsHaveMeaning(flags ast.SymbolFlags, meaning ast.SemanticMeaning) bool {
|
|
if meaning == ast.SemanticMeaningAll {
|
|
return true
|
|
}
|
|
if meaning&ast.SemanticMeaningValue != 0 {
|
|
return flags&ast.SymbolFlagsValue != 0
|
|
}
|
|
if meaning&ast.SemanticMeaningType != 0 {
|
|
return flags&ast.SymbolFlagsType != 0
|
|
}
|
|
if meaning&ast.SemanticMeaningNamespace != 0 {
|
|
return flags&ast.SymbolFlagsNamespace != 0
|
|
}
|
|
return false
|
|
}
|
|
|
|
func getMeaningFromLocation(node *ast.Node) ast.SemanticMeaning {
|
|
// todo: check if this function needs to be changed for jsdoc updates
|
|
node = getAdjustedLocation(ast.GetReparsedNodeForNode(node), false /*forRename*/, nil)
|
|
parent := node.Parent
|
|
switch {
|
|
case ast.IsSourceFile(node):
|
|
return ast.SemanticMeaningValue
|
|
case ast.NodeKindIs(parent, ast.KindExportAssignment, ast.KindExportSpecifier, ast.KindExternalModuleReference, ast.KindImportSpecifier, ast.KindImportClause) || parent.Kind == ast.KindImportEqualsDeclaration && node == parent.Name():
|
|
return ast.SemanticMeaningAll
|
|
case isInRightSideOfInternalImportEqualsDeclaration(node):
|
|
// import a = |b|; // Namespace
|
|
// import a = |b.c|; // Value, type, namespace
|
|
// import a = |b.c|.d; // Namespace
|
|
name := node
|
|
if node.Kind != ast.KindQualifiedName {
|
|
name = core.IfElse(node.Parent.Kind == ast.KindQualifiedName && node.Parent.AsQualifiedName().Right == node, node.Parent, nil)
|
|
}
|
|
if name != nil && name.Parent.Kind == ast.KindImportEqualsDeclaration {
|
|
return ast.SemanticMeaningAll
|
|
}
|
|
return ast.SemanticMeaningNamespace
|
|
case ast.IsDeclarationName(node):
|
|
return getMeaningFromDeclaration(parent)
|
|
case ast.IsEntityName(node) && ast.IsJSDocNameReferenceContext(node):
|
|
return ast.SemanticMeaningAll
|
|
case isTypeReference(node):
|
|
return ast.SemanticMeaningType
|
|
case isNamespaceReference(node):
|
|
return ast.SemanticMeaningNamespace
|
|
case ast.IsTypeParameterDeclaration(parent):
|
|
return ast.SemanticMeaningType
|
|
case ast.IsLiteralTypeNode(parent):
|
|
// This might be T["name"], which is actually referencing a property and not a type. So allow both meanings.
|
|
return ast.SemanticMeaningType | ast.SemanticMeaningValue
|
|
default:
|
|
return ast.SemanticMeaningValue
|
|
}
|
|
}
|
|
|
|
func getMeaningFromDeclaration(node *ast.Node) ast.SemanticMeaning {
|
|
switch node.Kind {
|
|
case ast.KindVariableDeclaration, ast.KindParameter, ast.KindBindingElement,
|
|
ast.KindPropertyDeclaration, ast.KindPropertySignature, ast.KindPropertyAssignment, ast.KindShorthandPropertyAssignment,
|
|
ast.KindMethodDeclaration, ast.KindMethodSignature, ast.KindConstructor, ast.KindGetAccessor, ast.KindSetAccessor,
|
|
ast.KindFunctionDeclaration, ast.KindFunctionExpression, ast.KindArrowFunction, ast.KindCatchClause, ast.KindJsxAttribute:
|
|
return ast.SemanticMeaningValue
|
|
|
|
case ast.KindTypeParameter, ast.KindInterfaceDeclaration, ast.KindTypeAliasDeclaration, ast.KindJSTypeAliasDeclaration, ast.KindTypeLiteral:
|
|
return ast.SemanticMeaningType
|
|
|
|
case ast.KindEnumMember, ast.KindClassDeclaration:
|
|
return ast.SemanticMeaningValue | ast.SemanticMeaningType
|
|
|
|
case ast.KindModuleDeclaration:
|
|
if ast.IsAmbientModule(node) {
|
|
return ast.SemanticMeaningNamespace | ast.SemanticMeaningValue
|
|
} else if ast.GetModuleInstanceState(node) == ast.ModuleInstanceStateInstantiated {
|
|
return ast.SemanticMeaningNamespace | ast.SemanticMeaningValue
|
|
} else {
|
|
return ast.SemanticMeaningNamespace
|
|
}
|
|
|
|
case ast.KindEnumDeclaration, ast.KindNamedImports, ast.KindImportSpecifier, ast.KindImportEqualsDeclaration, ast.KindImportDeclaration,
|
|
ast.KindJSImportDeclaration, ast.KindExportAssignment, ast.KindExportDeclaration:
|
|
return ast.SemanticMeaningAll
|
|
|
|
// An external module can be a Value
|
|
case ast.KindSourceFile:
|
|
return ast.SemanticMeaningNamespace | ast.SemanticMeaningValue
|
|
}
|
|
|
|
return ast.SemanticMeaningAll
|
|
}
|
|
|
|
func getIntersectingMeaningFromDeclarations(node *ast.Node, symbol *ast.Symbol, defaultMeaning ast.SemanticMeaning) ast.SemanticMeaning {
|
|
if node == nil {
|
|
return defaultMeaning
|
|
}
|
|
|
|
meaning := getMeaningFromLocation(node)
|
|
declarations := symbol.Declarations
|
|
if len(declarations) == 0 {
|
|
return meaning
|
|
}
|
|
|
|
lastIterationMeaning := meaning
|
|
|
|
// !!! TODO check if the port is correct and the for loop is needed
|
|
iteration := func(m ast.SemanticMeaning) ast.SemanticMeaning {
|
|
for _, declaration := range declarations {
|
|
declarationMeaning := getMeaningFromDeclaration(declaration)
|
|
|
|
if declarationMeaning&m != 0 {
|
|
m |= declarationMeaning
|
|
}
|
|
}
|
|
return m
|
|
}
|
|
meaning = iteration(meaning)
|
|
|
|
for meaning != lastIterationMeaning {
|
|
// The result is order-sensitive, for instance if initialMeaning == Namespace, and declarations = [class, instantiated module]
|
|
// we need to consider both as the initialMeaning intersects with the module in the namespace space, and the module
|
|
// intersects with the class in the value space.
|
|
// To achieve that we will keep iterating until the result stabilizes.
|
|
|
|
// Remember the last meaning
|
|
lastIterationMeaning = meaning
|
|
meaning = iteration(meaning)
|
|
}
|
|
|
|
return meaning
|
|
}
|
|
|
|
// Returns the node in an `extends` or `implements` clause of a class or interface.
|
|
func getAllSuperTypeNodes(node *ast.Node) []*ast.TypeNode {
|
|
if ast.IsInterfaceDeclaration(node) {
|
|
return ast.GetHeritageElements(node, ast.KindExtendsKeyword)
|
|
}
|
|
if ast.IsClassLike(node) {
|
|
return append(
|
|
core.SingleElementSlice(ast.GetClassExtendsHeritageElement(node)),
|
|
ast.GetImplementsTypeNodes(node)...,
|
|
)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getParentSymbolsOfPropertyAccess(location *ast.Node, symbol *ast.Symbol, ch *checker.Checker) []*ast.Symbol {
|
|
if !isRightSideOfPropertyAccess(location) {
|
|
return nil
|
|
}
|
|
lhsType := ch.GetTypeAtLocation(location.Parent.Expression())
|
|
if lhsType == nil {
|
|
return nil
|
|
}
|
|
var possibleSymbols []*checker.Type
|
|
if lhsType.Flags()&checker.TypeFlagsUnionOrIntersection != 0 {
|
|
possibleSymbols = lhsType.Types()
|
|
} else if lhsType.Symbol() != symbol.Parent {
|
|
possibleSymbols = []*checker.Type{lhsType}
|
|
}
|
|
return core.MapNonNil(possibleSymbols, func(t *checker.Type) *ast.Symbol {
|
|
if t.Symbol() != nil && t.Symbol().Flags&(ast.SymbolFlagsClass|ast.SymbolFlagsInterface) != 0 {
|
|
return t.Symbol()
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// Find symbol of the given property-name and add the symbol to the given result array
|
|
// @param symbol a symbol to start searching for the given propertyName
|
|
// @param propertyName a name of property to search for
|
|
// @param cb a cache of symbol from previous iterations of calling this function to prevent infinite revisiting of the same symbol.
|
|
//
|
|
// The value of previousIterationSymbol is undefined when the function is first called.
|
|
func getPropertySymbolsFromBaseTypes(symbol *ast.Symbol, propertyName string, checker *checker.Checker, cb func(base *ast.Symbol) *ast.Symbol) *ast.Symbol {
|
|
var seen collections.Set[*ast.Symbol]
|
|
var recur func(*ast.Symbol) *ast.Symbol
|
|
recur = func(symbol *ast.Symbol) *ast.Symbol {
|
|
// Use `addToSeen` to ensure we don't infinitely recurse in this situation:
|
|
// interface C extends C {
|
|
// /*findRef*/propName: string;
|
|
// }
|
|
if symbol.Flags&(ast.SymbolFlagsClass|ast.SymbolFlagsInterface) == 0 || !seen.AddIfAbsent(symbol) {
|
|
return nil
|
|
}
|
|
for _, declaration := range symbol.Declarations {
|
|
for _, typeReference := range getAllSuperTypeNodes(declaration) {
|
|
if propertyType := checker.GetTypeAtLocation(typeReference); propertyType != nil && propertyType.Symbol() != nil {
|
|
// Visit the typeReference as well to see if it directly or indirectly uses that property
|
|
if propertySymbol := checker.GetPropertyOfType(propertyType, propertyName); propertySymbol != nil {
|
|
for _, rootSymbol := range checker.GetRootSymbols(propertySymbol) {
|
|
if result := cb(rootSymbol); result != nil {
|
|
return result
|
|
}
|
|
}
|
|
}
|
|
if result := recur(propertyType.Symbol()); result != nil {
|
|
return result
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
return recur(symbol)
|
|
}
|
|
|
|
func getPropertySymbolFromBindingElement(checker *checker.Checker, bindingElement *ast.Node) *ast.Symbol {
|
|
if typeOfPattern := checker.GetTypeAtLocation(bindingElement.Parent); typeOfPattern != nil {
|
|
return checker.GetPropertyOfType(typeOfPattern, bindingElement.Name().Text())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol *ast.Symbol, checker *checker.Checker) *ast.Symbol {
|
|
bindingElement := ast.GetDeclarationOfKind(symbol, ast.KindBindingElement)
|
|
if bindingElement != nil && isObjectBindingElementWithoutPropertyName(bindingElement) {
|
|
return getPropertySymbolFromBindingElement(checker, bindingElement)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getTargetLabel(referenceNode *ast.Node, labelName string) *ast.Node {
|
|
// todo: rewrite as `ast.FindAncestor`
|
|
for referenceNode != nil {
|
|
if referenceNode.Kind == ast.KindLabeledStatement && referenceNode.Label().Text() == labelName {
|
|
return referenceNode.Label()
|
|
}
|
|
referenceNode = referenceNode.Parent
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func skipConstraint(t *checker.Type, typeChecker *checker.Checker) *checker.Type {
|
|
if t.IsTypeParameter() {
|
|
c := typeChecker.GetBaseConstraintOfType(t)
|
|
if c != nil {
|
|
return c
|
|
}
|
|
}
|
|
return t
|
|
}
|
|
|
|
type caseClauseTrackerState struct {
|
|
existingStrings collections.Set[string]
|
|
existingNumbers collections.Set[jsnum.Number]
|
|
existingBigInts collections.Set[jsnum.PseudoBigInt]
|
|
}
|
|
|
|
// string | jsnum.Number
|
|
type trackerAddValue = any
|
|
|
|
// string | jsnum.Number | jsnum.PseudoBigInt
|
|
type trackerHasValue = any
|
|
|
|
type caseClauseTracker interface {
|
|
addValue(value trackerAddValue)
|
|
hasValue(value trackerHasValue) bool
|
|
}
|
|
|
|
func (c *caseClauseTrackerState) addValue(value trackerAddValue) {
|
|
switch v := value.(type) {
|
|
case string:
|
|
c.existingStrings.Add(v)
|
|
case jsnum.Number:
|
|
c.existingNumbers.Add(v)
|
|
default:
|
|
panic(fmt.Sprintf("Unsupported type: %T", v))
|
|
}
|
|
}
|
|
|
|
func (c *caseClauseTrackerState) hasValue(value trackerHasValue) bool {
|
|
switch v := value.(type) {
|
|
case string:
|
|
return c.existingStrings.Has(v)
|
|
case jsnum.Number:
|
|
return c.existingNumbers.Has(v)
|
|
case jsnum.PseudoBigInt:
|
|
return c.existingBigInts.Has(v)
|
|
default:
|
|
panic(fmt.Sprintf("Unsupported type: %T", v))
|
|
}
|
|
}
|
|
|
|
func newCaseClauseTracker(typeChecker *checker.Checker, clauses []*ast.CaseOrDefaultClauseNode) caseClauseTracker {
|
|
c := &caseClauseTrackerState{
|
|
existingStrings: collections.Set[string]{},
|
|
existingNumbers: collections.Set[jsnum.Number]{},
|
|
existingBigInts: collections.Set[jsnum.PseudoBigInt]{},
|
|
}
|
|
for _, clause := range clauses {
|
|
if !ast.IsDefaultClause(clause) {
|
|
expression := ast.SkipParentheses(clause.Expression())
|
|
if ast.IsLiteralExpression(expression) {
|
|
switch expression.Kind {
|
|
case ast.KindNoSubstitutionTemplateLiteral, ast.KindStringLiteral:
|
|
c.existingStrings.Add(expression.Text())
|
|
case ast.KindNumericLiteral:
|
|
c.existingNumbers.Add(jsnum.FromString(expression.Text()))
|
|
case ast.KindBigIntLiteral:
|
|
c.existingBigInts.Add(jsnum.ParseValidBigInt(expression.Text()))
|
|
}
|
|
} else {
|
|
symbol := typeChecker.GetSymbolAtLocation(clause.Expression())
|
|
if symbol != nil && symbol.ValueDeclaration != nil && ast.IsEnumMember(symbol.ValueDeclaration) {
|
|
enumValue := typeChecker.GetConstantValue(symbol.ValueDeclaration)
|
|
if enumValue != nil {
|
|
c.addValue(enumValue)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return c
|
|
}
|
|
|
|
func RangeContainsRange(r1 core.TextRange, r2 core.TextRange) bool {
|
|
return startEndContainsRange(r1.Pos(), r1.End(), r2)
|
|
}
|
|
|
|
func startEndContainsRange(start int, end int, textRange core.TextRange) bool {
|
|
return start <= textRange.Pos() && end >= textRange.End()
|
|
}
|
|
|
|
func getPossibleGenericSignatures(called *ast.Expression, typeArgumentCount int, c *checker.Checker) []*checker.Signature {
|
|
typeAtLocation := c.GetTypeAtLocation(called)
|
|
if ast.IsOptionalChain(called.Parent) {
|
|
typeAtLocation = removeOptionality(typeAtLocation, ast.IsOptionalChainRoot(called.Parent), true /*isOptionalChain*/, c)
|
|
}
|
|
var signatures []*checker.Signature
|
|
if ast.IsNewExpression(called.Parent) {
|
|
signatures = c.GetSignaturesOfType(typeAtLocation, checker.SignatureKindConstruct)
|
|
} else {
|
|
signatures = c.GetSignaturesOfType(typeAtLocation, checker.SignatureKindCall)
|
|
}
|
|
return core.Filter(signatures, func(s *checker.Signature) bool {
|
|
return s.TypeParameters() != nil && len(s.TypeParameters()) >= typeArgumentCount
|
|
})
|
|
}
|
|
|
|
func removeOptionality(t *checker.Type, isOptionalExpression bool, isOptionalChain bool, c *checker.Checker) *checker.Type {
|
|
if isOptionalExpression {
|
|
return c.GetNonNullableType(t)
|
|
} else if isOptionalChain {
|
|
return c.GetNonOptionalType(t)
|
|
}
|
|
return t
|
|
}
|
|
|
|
func isNoSubstitutionTemplateLiteral(node *ast.Node) bool {
|
|
return node.Kind == ast.KindNoSubstitutionTemplateLiteral
|
|
}
|
|
|
|
func isTaggedTemplateExpression(node *ast.Node) bool {
|
|
return node.Kind == ast.KindTaggedTemplateExpression
|
|
}
|
|
|
|
func isInsideTemplateLiteral(node *ast.Node, position int, sourceFile *ast.SourceFile) bool {
|
|
return ast.IsTemplateLiteralKind(node.Kind) && (scanner.GetTokenPosOfNode(node, sourceFile, false) < position && position < node.End() || (ast.IsUnterminatedLiteral(node) && position == node.End()))
|
|
}
|
|
|
|
// Pseudo-literals
|
|
func isTemplateHead(node *ast.Node) bool {
|
|
return node.Kind == ast.KindTemplateHead
|
|
}
|
|
|
|
func isTemplateTail(node *ast.Node) bool {
|
|
return node.Kind == ast.KindTemplateTail
|
|
}
|
|
|
|
func findPrecedingMatchingToken(token *ast.Node, matchingTokenKind ast.Kind, sourceFile *ast.SourceFile) *ast.Node {
|
|
closeTokenText := scanner.TokenToString(token.Kind)
|
|
matchingTokenText := scanner.TokenToString(matchingTokenKind)
|
|
// Text-scan based fast path - can be bamboozled by comments and other trivia, but often provides
|
|
// a good, fast approximation without too much extra work in the cases where it fails.
|
|
bestGuessIndex := strings.LastIndex(sourceFile.Text(), matchingTokenText)
|
|
if bestGuessIndex == -1 {
|
|
return nil // if the token text doesn't appear in the file, there can't be a match - super fast bail
|
|
}
|
|
// we can only use the textual result directly if we didn't have to count any close tokens within the range
|
|
if strings.LastIndex(sourceFile.Text(), closeTokenText) < bestGuessIndex {
|
|
nodeAtGuess := astnav.FindPrecedingToken(sourceFile, bestGuessIndex+1)
|
|
if nodeAtGuess != nil && nodeAtGuess.Kind == matchingTokenKind {
|
|
return nodeAtGuess
|
|
}
|
|
}
|
|
tokenKind := token.Kind
|
|
remainingMatchingTokens := 0
|
|
for {
|
|
preceding := astnav.FindPrecedingToken(sourceFile, token.Pos())
|
|
if preceding == nil {
|
|
return nil
|
|
}
|
|
token = preceding
|
|
switch token.Kind {
|
|
case matchingTokenKind:
|
|
if remainingMatchingTokens == 0 {
|
|
return token
|
|
}
|
|
remainingMatchingTokens--
|
|
case tokenKind:
|
|
remainingMatchingTokens++
|
|
}
|
|
}
|
|
}
|
|
|
|
func findContainingList(node *ast.Node, file *ast.SourceFile) *ast.NodeList {
|
|
// The node might be a list element (nonsynthetic) or a comma (synthetic). Either way, it will
|
|
// be parented by the container of the SyntaxList, not the SyntaxList itself.
|
|
var list *ast.NodeList
|
|
visitNode := func(n *ast.Node, visitor *ast.NodeVisitor) *ast.Node {
|
|
return n
|
|
}
|
|
visitNodes := func(nodes *ast.NodeList, visitor *ast.NodeVisitor) *ast.NodeList {
|
|
if nodes != nil && RangeContainsRange(nodes.Loc, node.Loc) {
|
|
list = nodes
|
|
}
|
|
return nodes
|
|
}
|
|
astnav.VisitEachChildAndJSDoc(node.Parent, file, visitNode, visitNodes)
|
|
return list
|
|
}
|
|
|
|
func getLeadingCommentRangesOfNode(node *ast.Node, file *ast.SourceFile) iter.Seq[ast.CommentRange] {
|
|
if node.Kind == ast.KindJsxText {
|
|
return nil
|
|
}
|
|
return scanner.GetLeadingCommentRanges(&ast.NodeFactory{}, file.Text(), node.Pos())
|
|
}
|
|
|
|
// Equivalent to Strada's `node.getChildren()` for non-JSDoc nodes.
|
|
func getChildrenFromNonJSDocNode(node *ast.Node, sourceFile *ast.SourceFile) []*ast.Node {
|
|
var childNodes []*ast.Node
|
|
node.ForEachChild(func(child *ast.Node) bool {
|
|
childNodes = append(childNodes, child)
|
|
return false
|
|
})
|
|
|
|
// If the node has no children, don't scan for tokens.
|
|
// This prevents creating tokens for leaf nodes' own text.
|
|
if len(childNodes) == 0 {
|
|
return nil
|
|
}
|
|
|
|
var children []*ast.Node
|
|
pos := node.Pos()
|
|
for _, child := range childNodes {
|
|
scanner := scanner.GetScannerForSourceFile(sourceFile, pos)
|
|
for pos < child.Pos() {
|
|
token := scanner.Token()
|
|
tokenFullStart := scanner.TokenFullStart()
|
|
tokenEnd := scanner.TokenEnd()
|
|
children = append(children, sourceFile.GetOrCreateToken(token, tokenFullStart, tokenEnd, node, scanner.TokenFlags()))
|
|
pos = tokenEnd
|
|
scanner.Scan()
|
|
}
|
|
children = append(children, child)
|
|
pos = child.End()
|
|
}
|
|
scanner := scanner.GetScannerForSourceFile(sourceFile, pos)
|
|
for pos < node.End() {
|
|
token := scanner.Token()
|
|
tokenFullStart := scanner.TokenFullStart()
|
|
tokenEnd := scanner.TokenEnd()
|
|
children = append(children, sourceFile.GetOrCreateToken(token, tokenFullStart, tokenEnd, node, scanner.TokenFlags()))
|
|
pos = tokenEnd
|
|
scanner.Scan()
|
|
}
|
|
return children
|
|
}
|
|
|
|
// Returns the containing object literal property declaration given a possible name node, e.g. "a" in x = { "a": 1 }
|
|
func getContainingObjectLiteralElement(node *ast.Node) *ast.Node {
|
|
element := getContainingObjectLiteralElementWorker(node)
|
|
if element != nil && (ast.IsObjectLiteralExpression(element.Parent) || ast.IsJsxAttributes(element.Parent)) {
|
|
return element
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getContainingObjectLiteralElementWorker(node *ast.Node) *ast.Node {
|
|
switch node.Kind {
|
|
case ast.KindStringLiteral, ast.KindNoSubstitutionTemplateLiteral, ast.KindNumericLiteral:
|
|
if node.Parent.Kind == ast.KindComputedPropertyName {
|
|
if isObjectLiteralOrJsxElement(node.Parent.Parent) {
|
|
return node.Parent.Parent
|
|
}
|
|
return nil
|
|
}
|
|
fallthrough
|
|
case ast.KindIdentifier, ast.KindJsxNamespacedName:
|
|
if isObjectLiteralOrJsxElement(node.Parent) && (node.Parent.Parent.Kind == ast.KindObjectLiteralExpression || node.Parent.Parent.Kind == ast.KindJsxAttributes) && node.Parent.Name() == node {
|
|
return node.Parent
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func isObjectLiteralOrJsxElement(node *ast.Node) bool {
|
|
return ast.IsObjectLiteralElement(node) || ast.IsJsxAttribute(node) || ast.IsJsxSpreadAttribute(node)
|
|
}
|
|
|
|
// Return a function that returns true if the given node has not been seen
|
|
func nodeSeenTracker() func(*ast.Node) bool {
|
|
var seen collections.Set[*ast.Node]
|
|
return func(node *ast.Node) bool {
|
|
return seen.AddIfAbsent(node)
|
|
}
|
|
}
|
|
|
|
// FindAllReferences.toContextSpan
|
|
func toContextRange(textRange *core.TextRange, contextFile *ast.SourceFile, context *ast.Node) *core.TextRange {
|
|
if context == nil {
|
|
return textRange
|
|
}
|
|
// !!! isContextWithStartAndEndNode
|
|
contextRange := getRangeOfNode(context, contextFile, nil /*endNode*/)
|
|
if contextRange.Pos() != textRange.Pos() || contextRange.End() != textRange.End() {
|
|
return &contextRange
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getReferenceAtPosition(sourceFile *ast.SourceFile, position int, program *compiler.Program) *refInfo {
|
|
if referencePath := findReferenceInPosition(sourceFile.ReferencedFiles, position); referencePath != nil {
|
|
if file := program.GetSourceFileFromReference(sourceFile, referencePath); file != nil {
|
|
return &refInfo{reference: referencePath, fileName: file.FileName(), file: file, unverified: false}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if typeReferenceDirective := findReferenceInPosition(sourceFile.TypeReferenceDirectives, position); typeReferenceDirective != nil {
|
|
if reference := program.GetResolvedTypeReferenceDirectiveFromTypeReferenceDirective(typeReferenceDirective, sourceFile); reference != nil {
|
|
if file := program.GetSourceFile(reference.ResolvedFileName); file != nil {
|
|
return &refInfo{reference: typeReferenceDirective, fileName: file.FileName(), file: file, unverified: false}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if libReferenceDirective := findReferenceInPosition(sourceFile.LibReferenceDirectives, position); libReferenceDirective != nil {
|
|
if file := program.GetLibFileFromReference(libReferenceDirective); file != nil {
|
|
return &refInfo{reference: libReferenceDirective, fileName: file.FileName(), file: file, unverified: false}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if len(sourceFile.Imports()) == 0 && len(sourceFile.ModuleAugmentations) == 0 {
|
|
return nil
|
|
}
|
|
|
|
node := astnav.GetTouchingToken(sourceFile, position)
|
|
if !isModuleSpecifierLike(node) || !tspath.IsExternalModuleNameRelative(node.Text()) {
|
|
return nil
|
|
}
|
|
|
|
if resolution := program.GetResolvedModuleFromModuleSpecifier(sourceFile, node); resolution != nil {
|
|
verifiedFileName := resolution.ResolvedFileName
|
|
fileName := resolution.ResolvedFileName
|
|
if fileName == "" {
|
|
fileName = tspath.ResolvePath(tspath.GetDirectoryPath(sourceFile.FileName()), node.Text())
|
|
}
|
|
return &refInfo{
|
|
file: program.GetSourceFile(fileName),
|
|
fileName: fileName,
|
|
reference: nil,
|
|
unverified: verifiedFileName != "",
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getContextualTypeFromParent(node *ast.Expression, typeChecker *checker.Checker, contextFlags checker.ContextFlags) *checker.Type {
|
|
parent := ast.WalkUpParenthesizedExpressions(node.Parent)
|
|
switch parent.Kind {
|
|
case ast.KindNewExpression:
|
|
return typeChecker.GetContextualType(parent, contextFlags)
|
|
case ast.KindBinaryExpression:
|
|
if isEqualityOperatorKind(parent.AsBinaryExpression().OperatorToken.Kind) {
|
|
return typeChecker.GetTypeAtLocation(
|
|
core.IfElse(node == parent.AsBinaryExpression().Right, parent.AsBinaryExpression().Left, parent.AsBinaryExpression().Right),
|
|
)
|
|
}
|
|
return typeChecker.GetContextualType(node, contextFlags)
|
|
case ast.KindCaseClause:
|
|
return getSwitchedType(parent, typeChecker)
|
|
default:
|
|
return typeChecker.GetContextualType(node, contextFlags)
|
|
}
|
|
}
|
|
|
|
func getContextualTypeFromParentOrAncestorTypeNode(node *ast.Expression, typeChecker *checker.Checker) *checker.Type {
|
|
if node.Flags&ast.NodeFlagsJSDoc != 0 && node.Flags&ast.NodeFlagsJavaScriptFile == 0 {
|
|
return nil
|
|
}
|
|
|
|
contextualType := getContextualTypeFromParent(node, typeChecker, checker.ContextFlagsNone)
|
|
if contextualType != nil {
|
|
return contextualType
|
|
}
|
|
|
|
if ancestorTypeNode := getAncestorTypeNode(node); ancestorTypeNode != nil {
|
|
return typeChecker.GetTypeAtLocation(ancestorTypeNode)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getAncestorTypeNode(node *ast.Node) *ast.Node {
|
|
var lastTypeNode *ast.TypeNode
|
|
ast.FindAncestor(node, func(n *ast.Node) bool {
|
|
if ast.IsTypeNode(n) {
|
|
lastTypeNode = n
|
|
}
|
|
return !ast.IsQualifiedName(n.Parent) && !ast.IsTypeNode(n.Parent) && !ast.IsTypeElement(n.Parent)
|
|
})
|
|
return lastTypeNode
|
|
}
|
|
|
|
func isSourceFileWithGlobalExports(node *ast.Node) bool {
|
|
return node != nil && ast.IsSourceFile(node) && node.AsSourceFile().GlobalExports != nil
|
|
}
|