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 // /**/ [|name|] ... // /**/ [|name|] ... // /**/ [|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 } } // /**/ ... 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) } }