1723 lines
60 KiB
Go
1723 lines
60 KiB
Go
package ls
|
|
|
|
import (
|
|
"cmp"
|
|
"fmt"
|
|
"iter"
|
|
"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/checker"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/debug"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/jsnum"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto"
|
|
"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/tspath"
|
|
)
|
|
|
|
// Implements a cmp.Compare like function for two lsproto.Position
|
|
// ComparePositions(pos, other) == cmp.Compare(pos, other)
|
|
func ComparePositions(pos, other lsproto.Position) int {
|
|
if lineComp := cmp.Compare(pos.Line, other.Line); lineComp != 0 {
|
|
return lineComp
|
|
}
|
|
return cmp.Compare(pos.Character, other.Character)
|
|
}
|
|
|
|
// Implements a cmp.Compare like function for two *lsproto.Range
|
|
// CompareRanges(lsRange, other) == cmp.Compare(lsrange, other)
|
|
//
|
|
// Range.Start is compared before Range.End
|
|
func CompareRanges(lsRange, other *lsproto.Range) int {
|
|
if startComp := ComparePositions(lsRange.Start, other.Start); startComp != 0 {
|
|
return startComp
|
|
}
|
|
return ComparePositions(lsRange.End, other.End)
|
|
}
|
|
|
|
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 importFromModuleSpecifier(node *ast.Node) *ast.Node {
|
|
if result := tryGetImportFromModuleSpecifier(node); result != nil {
|
|
return result
|
|
}
|
|
debug.FailBadSyntaxKind(node.Parent)
|
|
return nil
|
|
}
|
|
|
|
func tryGetImportFromModuleSpecifier(node *ast.StringLiteralLike) *ast.Node {
|
|
switch node.Parent.Kind {
|
|
case ast.KindImportDeclaration, ast.KindJSImportDeclaration, ast.KindExportDeclaration:
|
|
return node.Parent
|
|
case ast.KindExternalModuleReference:
|
|
return node.Parent.Parent
|
|
case ast.KindCallExpression:
|
|
if ast.IsImportCall(node.Parent) || ast.IsRequireCall(node.Parent, false /*requireStringLiteralLikeArgument*/) {
|
|
return node.Parent
|
|
}
|
|
return nil
|
|
case ast.KindLiteralType:
|
|
if !ast.IsStringLiteral(node) {
|
|
return nil
|
|
}
|
|
if ast.IsImportTypeNode(node.Parent.Parent) {
|
|
return node.Parent.Parent
|
|
}
|
|
return nil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
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.AsCallExpression().Arguments.Nodes[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 moduleSymbolToValidIdentifier(moduleSymbol *ast.Symbol, target core.ScriptTarget, forceCapitalize bool) string {
|
|
return moduleSpecifierToValidIdentifier(stringutil.StripQuotes(moduleSymbol.Name), target, forceCapitalize)
|
|
}
|
|
|
|
func moduleSpecifierToValidIdentifier(moduleSpecifier string, target core.ScriptTarget, forceCapitalize bool) string {
|
|
baseName := tspath.GetBaseFileName(strings.TrimSuffix(tspath.RemoveFileExtension(moduleSpecifier), "/index"))
|
|
res := []rune{}
|
|
lastCharWasValid := true
|
|
baseNameRunes := []rune(baseName)
|
|
if len(baseNameRunes) > 0 && scanner.IsIdentifierStart(baseNameRunes[0]) {
|
|
if forceCapitalize {
|
|
res = append(res, unicode.ToUpper(baseNameRunes[0]))
|
|
} else {
|
|
res = append(res, baseNameRunes[0])
|
|
}
|
|
} else {
|
|
lastCharWasValid = false
|
|
}
|
|
|
|
for i := 1; i < len(baseNameRunes); i++ {
|
|
isValid := scanner.IsIdentifierPart(baseNameRunes[i])
|
|
if isValid {
|
|
if !lastCharWasValid {
|
|
res = append(res, unicode.ToUpper(baseNameRunes[i]))
|
|
} else {
|
|
res = append(res, baseNameRunes[i])
|
|
}
|
|
}
|
|
lastCharWasValid = isValid
|
|
}
|
|
|
|
// Need `"_"` to ensure result isn't empty.
|
|
resString := string(res)
|
|
if resString != "" && !isNonContextualKeyword(scanner.StringToToken(resString)) {
|
|
return resString
|
|
}
|
|
return "_" + resString
|
|
}
|
|
|
|
func getLocalSymbolForExportSpecifier(referenceLocation *ast.Identifier, 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.Identifier, exportSpecifier *ast.ExportSpecifier) bool {
|
|
debug.Assert(exportSpecifier.PropertyName == referenceLocation.AsNode() || exportSpecifier.Name() == referenceLocation.AsNode(), "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.AsNode()
|
|
} 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.AsExportDeclaration().ModuleSpecifier == nil
|
|
}
|
|
}
|
|
|
|
func isInComment(file *ast.SourceFile, position int, tokenAtPosition *ast.Node) *ast.CommentRange {
|
|
return getRangeOfEnclosingComment(file, position, astnav.FindPrecedingToken(file, position), tokenAtPosition)
|
|
}
|
|
|
|
func hasChildOfKind(containingNode *ast.Node, kind ast.Kind, sourceFile *ast.SourceFile) bool {
|
|
return findChildOfKind(containingNode, kind, sourceFile) != nil
|
|
}
|
|
|
|
func findChildOfKind(containingNode *ast.Node, kind ast.Kind, sourceFile *ast.SourceFile) *ast.Node {
|
|
lastNodePos := containingNode.Pos()
|
|
scanner := scanner.GetScannerForSourceFile(sourceFile, lastNodePos)
|
|
|
|
var foundChild *ast.Node
|
|
visitNode := func(node *ast.Node) bool {
|
|
if node == nil || node.Flags&ast.NodeFlagsReparsed != 0 {
|
|
return false
|
|
}
|
|
// Look for child in preceding tokens.
|
|
startPos := lastNodePos
|
|
for startPos < node.Pos() {
|
|
tokenKind := scanner.Token()
|
|
tokenFullStart := scanner.TokenFullStart()
|
|
tokenEnd := scanner.TokenEnd()
|
|
token := sourceFile.GetOrCreateToken(tokenKind, tokenFullStart, tokenEnd, containingNode)
|
|
if tokenKind == kind {
|
|
foundChild = token
|
|
return true
|
|
}
|
|
startPos = tokenEnd
|
|
scanner.Scan()
|
|
}
|
|
if node.Kind == kind {
|
|
foundChild = node
|
|
return true
|
|
}
|
|
|
|
lastNodePos = node.End()
|
|
scanner.ResetPos(lastNodePos)
|
|
return false
|
|
}
|
|
|
|
ast.ForEachChildAndJSDoc(containingNode, sourceFile, visitNode)
|
|
|
|
if foundChild != nil {
|
|
return foundChild
|
|
}
|
|
|
|
// Look for child in trailing tokens.
|
|
startPos := lastNodePos
|
|
for startPos < containingNode.End() {
|
|
tokenKind := scanner.Token()
|
|
tokenFullStart := scanner.TokenFullStart()
|
|
tokenEnd := scanner.TokenEnd()
|
|
token := sourceFile.GetOrCreateToken(tokenKind, tokenFullStart, tokenEnd, containingNode)
|
|
if tokenKind == kind {
|
|
return token
|
|
}
|
|
startPos = tokenEnd
|
|
scanner.Scan()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
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.AsIdentifier().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 {
|
|
lspRange := l.converters.ToLSPRange(file, core.NewTextRange(start, end))
|
|
return &lspRange
|
|
}
|
|
|
|
func (l *LanguageService) createLspRangeFromRange(textRange core.TextRange, script Script) *lsproto.Range {
|
|
lspRange := l.converters.ToLSPRange(script, textRange)
|
|
return &lspRange
|
|
}
|
|
|
|
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 *UserPreferences, text string) string {
|
|
// Editors can pass in undefined or empty string - we want to infer the preference in those cases.
|
|
quotePreference := getQuotePreference(file, preferences)
|
|
quoted, _ := core.StringifyJson(text, "" /*prefix*/, "" /*indent*/)
|
|
if quotePreference == quotePreferenceSingle {
|
|
quoted = quoteReplacer.Replace(stringutil.StripQuotes(quoted))
|
|
}
|
|
return quoted
|
|
}
|
|
|
|
type quotePreference int
|
|
|
|
const (
|
|
quotePreferenceSingle quotePreference = iota
|
|
quotePreferenceDouble
|
|
)
|
|
|
|
// !!!
|
|
func getQuotePreference(file *ast.SourceFile, preferences *UserPreferences) quotePreference {
|
|
return quotePreferenceDouble
|
|
}
|
|
|
|
func isNonContextualKeyword(token ast.Kind) bool {
|
|
return ast.IsKeywordKind(token) && !ast.IsContextualKeyword(token)
|
|
}
|
|
|
|
func probablyUsesSemicolons(file *ast.SourceFile) bool {
|
|
withSemicolon := 0
|
|
withoutSemicolon := 0
|
|
nStatementsToObserve := 5
|
|
|
|
var visit func(node *ast.Node) bool
|
|
visit = func(node *ast.Node) bool {
|
|
if node.Flags&ast.NodeFlagsReparsed != 0 {
|
|
return false
|
|
}
|
|
if lsutil.SyntaxRequiresTrailingSemicolonOrASI(node.Kind) {
|
|
lastToken := lsutil.GetLastToken(node, file)
|
|
if lastToken != nil && lastToken.Kind == ast.KindSemicolonToken {
|
|
withSemicolon++
|
|
} else {
|
|
withoutSemicolon++
|
|
}
|
|
} else if lsutil.SyntaxRequiresTrailingCommaOrSemicolonOrASI(node.Kind) {
|
|
lastToken := lsutil.GetLastToken(node, file)
|
|
if lastToken != nil && lastToken.Kind == ast.KindSemicolonToken {
|
|
withSemicolon++
|
|
} else if lastToken != nil && lastToken.Kind != ast.KindCommaToken {
|
|
lastTokenLine, _ := scanner.GetECMALineAndCharacterOfPosition(
|
|
file,
|
|
astnav.GetStartOfNode(lastToken, file, false /*includeJSDoc*/))
|
|
nextTokenLine, _ := scanner.GetECMALineAndCharacterOfPosition(
|
|
file,
|
|
scanner.GetRangeOfTokenAtPosition(file, lastToken.End()).Pos())
|
|
// Avoid counting missing semicolon in single-line objects:
|
|
// `function f(p: { x: string /*no semicolon here is insignificant*/ }) {`
|
|
if lastTokenLine != nextTokenLine {
|
|
withoutSemicolon++
|
|
}
|
|
}
|
|
}
|
|
|
|
if withSemicolon+withoutSemicolon >= nStatementsToObserve {
|
|
return true
|
|
}
|
|
|
|
return node.ForEachChild(visit)
|
|
}
|
|
|
|
file.ForEachChild(visit)
|
|
|
|
// One statement missing a semicolon isn't sufficient evidence to say the user
|
|
// doesn't want semicolons, because they may not even be done writing that statement.
|
|
if withSemicolon == 0 && withoutSemicolon <= 1 {
|
|
return true
|
|
}
|
|
|
|
// If even 2/5 places have a semicolon, the user probably wants semicolons
|
|
if withoutSemicolon == 0 {
|
|
return true
|
|
}
|
|
return withSemicolon/withoutSemicolon > 1/nStatementsToObserve
|
|
}
|
|
|
|
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))
|
|
}
|
|
|
|
// Returns a map of all names in the file to their positions.
|
|
// !!! cache this
|
|
func getNameTable(file *ast.SourceFile) map[string]int {
|
|
nameTable := make(map[string]int)
|
|
var walk func(node *ast.Node) bool
|
|
|
|
walk = func(node *ast.Node) bool {
|
|
if ast.IsIdentifier(node) && !isTagName(node) && node.Text() != "" ||
|
|
ast.IsStringOrNumericLiteralLike(node) && literalIsName(node) ||
|
|
ast.IsPrivateIdentifier(node) {
|
|
text := node.Text()
|
|
if _, ok := nameTable[text]; ok {
|
|
nameTable[text] = -1
|
|
} else {
|
|
nameTable[text] = node.Pos()
|
|
}
|
|
}
|
|
|
|
node.ForEachChild(walk)
|
|
jsdocNodes := node.JSDoc(file)
|
|
for _, jsdoc := range jsdocNodes {
|
|
jsdoc.ForEachChild(walk)
|
|
}
|
|
return false
|
|
}
|
|
|
|
file.ForEachChild(walk)
|
|
return nameTable
|
|
}
|
|
|
|
// We want to store any numbers/strings if they were a name that could be
|
|
// related to a declaration. So, if we have 'import x = require("something")'
|
|
// then we want 'something' to be in the name table. Similarly, if we have
|
|
// "a['propname']" then we want to store "propname" in the name table.
|
|
func literalIsName(node *ast.NumericOrStringLikeLiteral) bool {
|
|
return ast.IsDeclarationName(node) ||
|
|
node.Parent.Kind == ast.KindExternalModuleReference ||
|
|
isArgumentOfElementAccessExpression(node) ||
|
|
ast.IsLiteralComputedPropertyDeclarationName(node)
|
|
}
|
|
|
|
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 isArgumentOfElementAccessExpression(node *ast.Node) bool {
|
|
return node != nil && node.Parent != nil &&
|
|
node.Parent.Kind == ast.KindElementAccessExpression &&
|
|
node.Parent.AsElementAccessExpression().ArgumentExpression == node
|
|
}
|
|
|
|
func isRightSideOfPropertyAccess(node *ast.Node) bool {
|
|
return 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 isTagName(node *ast.Node) bool {
|
|
return node.Parent != nil && ast.IsJSDocTag(node.Parent) && node.Parent.TagName() == node
|
|
}
|
|
|
|
// Assumes `candidate.pos <= position` holds.
|
|
func positionBelongsToNode(candidate *ast.Node, position int, file *ast.SourceFile) bool {
|
|
if candidate.Pos() > position {
|
|
panic("Expected candidate.pos <= position")
|
|
}
|
|
return position < candidate.End() || !isCompletedNode(candidate, file)
|
|
}
|
|
|
|
func isCompletedNode(n *ast.Node, sourceFile *ast.SourceFile) bool {
|
|
if n == nil || ast.NodeIsMissing(n) {
|
|
return false
|
|
}
|
|
|
|
switch n.Kind {
|
|
case ast.KindClassDeclaration,
|
|
ast.KindInterfaceDeclaration,
|
|
ast.KindEnumDeclaration,
|
|
ast.KindObjectLiteralExpression,
|
|
ast.KindObjectBindingPattern,
|
|
ast.KindTypeLiteral,
|
|
ast.KindBlock,
|
|
ast.KindModuleBlock,
|
|
ast.KindCaseBlock,
|
|
ast.KindNamedImports,
|
|
ast.KindNamedExports:
|
|
return nodeEndsWith(n, ast.KindCloseBraceToken, sourceFile)
|
|
|
|
case ast.KindCatchClause:
|
|
return isCompletedNode(n.AsCatchClause().Block, sourceFile)
|
|
|
|
case ast.KindNewExpression:
|
|
if n.AsNewExpression().Arguments == nil {
|
|
return true
|
|
}
|
|
fallthrough
|
|
|
|
case ast.KindCallExpression,
|
|
ast.KindParenthesizedExpression,
|
|
ast.KindParenthesizedType:
|
|
return nodeEndsWith(n, ast.KindCloseParenToken, sourceFile)
|
|
|
|
case ast.KindFunctionType,
|
|
ast.KindConstructorType:
|
|
return isCompletedNode(n.Type(), sourceFile)
|
|
|
|
case ast.KindConstructor,
|
|
ast.KindGetAccessor,
|
|
ast.KindSetAccessor,
|
|
ast.KindFunctionDeclaration,
|
|
ast.KindFunctionExpression,
|
|
ast.KindMethodDeclaration,
|
|
ast.KindMethodSignature,
|
|
ast.KindConstructSignature,
|
|
ast.KindCallSignature,
|
|
ast.KindArrowFunction:
|
|
if n.Body() != nil {
|
|
return isCompletedNode(n.Body(), sourceFile)
|
|
}
|
|
if n.Type() != nil {
|
|
return isCompletedNode(n.Type(), sourceFile)
|
|
}
|
|
// Even though type parameters can be unclosed, we can get away with
|
|
// having at least a closing paren.
|
|
return hasChildOfKind(n, ast.KindCloseParenToken, sourceFile)
|
|
|
|
case ast.KindModuleDeclaration:
|
|
return n.AsModuleDeclaration().Body != nil && isCompletedNode(n.AsModuleDeclaration().Body, sourceFile)
|
|
|
|
case ast.KindIfStatement:
|
|
if n.AsIfStatement().ElseStatement != nil {
|
|
return isCompletedNode(n.AsIfStatement().ElseStatement, sourceFile)
|
|
}
|
|
return isCompletedNode(n.AsIfStatement().ThenStatement, sourceFile)
|
|
|
|
case ast.KindExpressionStatement:
|
|
return isCompletedNode(n.AsExpressionStatement().Expression, sourceFile) ||
|
|
hasChildOfKind(n, ast.KindSemicolonToken, sourceFile)
|
|
|
|
case ast.KindArrayLiteralExpression,
|
|
ast.KindArrayBindingPattern,
|
|
ast.KindElementAccessExpression,
|
|
ast.KindComputedPropertyName,
|
|
ast.KindTupleType:
|
|
return nodeEndsWith(n, ast.KindCloseBracketToken, sourceFile)
|
|
|
|
case ast.KindIndexSignature:
|
|
if n.AsIndexSignatureDeclaration().Type != nil {
|
|
return isCompletedNode(n.AsIndexSignatureDeclaration().Type, sourceFile)
|
|
}
|
|
return hasChildOfKind(n, ast.KindCloseBracketToken, sourceFile)
|
|
|
|
case ast.KindCaseClause,
|
|
ast.KindDefaultClause:
|
|
// there is no such thing as terminator token for CaseClause/DefaultClause so for simplicity always consider them non-completed
|
|
return false
|
|
|
|
case ast.KindForStatement,
|
|
ast.KindForInStatement,
|
|
ast.KindForOfStatement,
|
|
ast.KindWhileStatement:
|
|
return isCompletedNode(n.Statement(), sourceFile)
|
|
case ast.KindDoStatement:
|
|
// rough approximation: if DoStatement has While keyword - then if node is completed is checking the presence of ')';
|
|
if hasChildOfKind(n, ast.KindWhileKeyword, sourceFile) {
|
|
return nodeEndsWith(n, ast.KindCloseParenToken, sourceFile)
|
|
}
|
|
return isCompletedNode(n.AsDoStatement().Statement, sourceFile)
|
|
|
|
case ast.KindTypeQuery:
|
|
return isCompletedNode(n.AsTypeQueryNode().ExprName, sourceFile)
|
|
|
|
case ast.KindTypeOfExpression,
|
|
ast.KindDeleteExpression,
|
|
ast.KindVoidExpression,
|
|
ast.KindYieldExpression,
|
|
ast.KindSpreadElement:
|
|
return isCompletedNode(n.Expression(), sourceFile)
|
|
|
|
case ast.KindTaggedTemplateExpression:
|
|
return isCompletedNode(n.AsTaggedTemplateExpression().Template, sourceFile)
|
|
|
|
case ast.KindTemplateExpression:
|
|
if n.AsTemplateExpression().TemplateSpans == nil {
|
|
return false
|
|
}
|
|
lastSpan := core.LastOrNil(n.AsTemplateExpression().TemplateSpans.Nodes)
|
|
return isCompletedNode(lastSpan, sourceFile)
|
|
|
|
case ast.KindTemplateSpan:
|
|
return ast.NodeIsPresent(n.AsTemplateSpan().Literal)
|
|
|
|
case ast.KindExportDeclaration,
|
|
ast.KindImportDeclaration:
|
|
return ast.NodeIsPresent(n.ModuleSpecifier())
|
|
|
|
case ast.KindPrefixUnaryExpression:
|
|
return isCompletedNode(n.AsPrefixUnaryExpression().Operand, sourceFile)
|
|
|
|
case ast.KindBinaryExpression:
|
|
return isCompletedNode(n.AsBinaryExpression().Right, sourceFile)
|
|
|
|
case ast.KindConditionalExpression:
|
|
return isCompletedNode(n.AsConditionalExpression().WhenFalse, sourceFile)
|
|
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
// Checks if node ends with 'expectedLastToken'.
|
|
// If child at position 'length - 1' is 'SemicolonToken' it is skipped and 'expectedLastToken' is compared with child at position 'length - 2'.
|
|
func nodeEndsWith(n *ast.Node, expectedLastToken ast.Kind, sourceFile *ast.SourceFile) bool {
|
|
lastChildNode := lsutil.GetLastVisitedChild(n, sourceFile)
|
|
var lastNodeAndTokens []*ast.Node
|
|
var tokenStartPos int
|
|
if lastChildNode != nil {
|
|
lastNodeAndTokens = []*ast.Node{lastChildNode}
|
|
tokenStartPos = lastChildNode.End()
|
|
} else {
|
|
tokenStartPos = n.Pos()
|
|
}
|
|
scanner := scanner.GetScannerForSourceFile(sourceFile, tokenStartPos)
|
|
for startPos := tokenStartPos; startPos < n.End(); {
|
|
tokenKind := scanner.Token()
|
|
tokenFullStart := scanner.TokenFullStart()
|
|
tokenEnd := scanner.TokenEnd()
|
|
token := sourceFile.GetOrCreateToken(tokenKind, tokenFullStart, tokenEnd, n)
|
|
lastNodeAndTokens = append(lastNodeAndTokens, token)
|
|
startPos = tokenEnd
|
|
scanner.Scan()
|
|
}
|
|
if len(lastNodeAndTokens) == 0 {
|
|
return false
|
|
}
|
|
lastChild := lastNodeAndTokens[len(lastNodeAndTokens)-1]
|
|
if lastChild.Kind == expectedLastToken {
|
|
return true
|
|
} else if lastChild.Kind == ast.KindSemicolonToken && len(lastNodeAndTokens) > 1 {
|
|
return lastNodeAndTokens[len(lastNodeAndTokens)-2].Kind == expectedLastToken
|
|
}
|
|
return false
|
|
}
|
|
|
|
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.Modifiers().NodeList.Nodes, 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| [|n:ame|] ...
|
|
if node.Kind == ast.KindVarKeyword || node.Kind == ast.KindConstKeyword || node.Kind == ast.KindLetKeyword &&
|
|
ast.IsVariableDeclarationList(parent) && len(parent.AsVariableDeclarationList().Declarations.Nodes) == 1 {
|
|
if decl := parent.AsVariableDeclarationList().Declarations.Nodes[0].AsVariableDeclaration(); ast.IsIdentifier(decl.Name()) {
|
|
return decl.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.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.AsImportSpecifier().PropertyName != nil ||
|
|
parent.Kind == ast.KindExportSpecifier && parent.AsExportSpecifier().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.AsExportAssignment().Expression, ast.OEKAll)
|
|
}
|
|
}
|
|
// import name = /**/require("[|module|]");
|
|
if node.Kind == ast.KindRequireKeyword && parent.Kind == ast.KindExternalModuleReference {
|
|
return parent.AsExternalModuleReference().Expression
|
|
}
|
|
// import ... /**/from "[|module|]";
|
|
// export ... /**/from "[|module|]";
|
|
if node.Kind == ast.KindFromKeyword {
|
|
if parent.Kind == ast.KindImportDeclaration && parent.AsImportDeclaration().ModuleSpecifier != nil {
|
|
return parent.AsImportDeclaration().ModuleSpecifier
|
|
}
|
|
if parent.Kind == ast.KindImportDeclaration && parent.AsExportDeclaration().ModuleSpecifier != nil {
|
|
return parent.AsExportDeclaration().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.AsTypeParameter().Constraint; constraint != nil && constraint.Kind == ast.KindTypeReference {
|
|
return constraint.AsTypeReference().TypeName
|
|
}
|
|
}
|
|
// ... T /**/extends [|U|] ? ...
|
|
if parent.Kind == ast.KindConditionalType {
|
|
if extendsType := parent.AsConditionalTypeNode().ExtendsType; extendsType != nil && extendsType.Kind == ast.KindTypeReference {
|
|
return extendsType.AsTypeReference().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.AsForInOrOfStatement().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.
|
|
if node.Modifiers() != nil {
|
|
return core.Find(node.Modifiers().NodeList.Nodes, 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 findChildOfKind(node, ast.KindClassKeyword, sourceFile)
|
|
case ast.KindFunctionExpression:
|
|
// for function expressions, use the `function` keyword when the function is unnamed
|
|
return 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.AsNamedImports().Elements
|
|
if len(elements.Nodes) != 1 {
|
|
return nil
|
|
}
|
|
return elements.Nodes[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.AsNamedExports().Elements
|
|
if len(elements.Nodes) != 1 {
|
|
return nil
|
|
}
|
|
return elements.Nodes[0].Name()
|
|
case ast.KindNamespaceExport:
|
|
return node.ExportClause.Name()
|
|
}
|
|
}
|
|
if !forRename {
|
|
// /**/export * from "[|module|]";
|
|
// export /**/type * from "[|module|]";
|
|
return node.ModuleSpecifier
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getMeaningFromLocation(node *ast.Node) ast.SemanticMeaning {
|
|
// todo: check if this function needs to be changed for jsdoc updates
|
|
node = getAdjustedLocation(node, false /*forRename*/, nil)
|
|
parent := node.Parent
|
|
switch {
|
|
case ast.IsSourceFile(node):
|
|
return ast.SemanticMeaningValue
|
|
case ast.NodeKindIs(node, 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.SemanticMeaningNamespace
|
|
}
|
|
return ast.SemanticMeaningAll
|
|
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.KindCommonJSExport, 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.KindJSExportAssignment, 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.AsLabeledStatement().Label.Text() == labelName {
|
|
return referenceNode.AsLabeledStatement().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
|
|
})
|
|
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))
|
|
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))
|
|
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 ast.IsObjectLiteralElement(node.Parent.Parent) {
|
|
return node.Parent.Parent
|
|
}
|
|
return nil
|
|
}
|
|
fallthrough
|
|
case ast.KindIdentifier:
|
|
if ast.IsObjectLiteralElement(node.Parent) && (node.Parent.Parent.Kind == ast.KindObjectLiteralExpression || node.Parent.Parent.Kind == ast.KindJsxAttributes) && node.Parent.Name() == node {
|
|
return node.Parent
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|