diff --git a/internal/ast/utilities.go b/internal/ast/utilities.go index ee0b42730f..22478614bb 100644 --- a/internal/ast/utilities.go +++ b/internal/ast/utilities.go @@ -1036,6 +1036,25 @@ func IsStatic(node *Node) bool { return IsClassElement(node) && HasStaticModifier(node) || IsClassStaticBlockDeclaration(node) } +func CanHaveSymbol(node *Node) bool { + switch node.Kind { + case KindArrowFunction, KindBinaryExpression, KindBindingElement, KindCallExpression, KindCallSignature, + KindClassDeclaration, KindClassExpression, KindClassStaticBlockDeclaration, KindConstructor, KindConstructorType, + KindConstructSignature, KindElementAccessExpression, KindEnumDeclaration, KindEnumMember, KindExportAssignment, + KindExportDeclaration, KindExportSpecifier, KindFunctionDeclaration, KindFunctionExpression, KindFunctionType, + KindGetAccessor, KindImportClause, KindImportEqualsDeclaration, KindImportSpecifier, KindIndexSignature, + KindInterfaceDeclaration, KindJSExportAssignment, KindJSTypeAliasDeclaration, KindCommonJSExport, + KindJsxAttribute, KindJsxAttributes, KindJsxSpreadAttribute, KindMappedType, KindMethodDeclaration, + KindMethodSignature, KindModuleDeclaration, KindNamedTupleMember, KindNamespaceExport, KindNamespaceExportDeclaration, + KindNamespaceImport, KindNewExpression, KindNoSubstitutionTemplateLiteral, KindNumericLiteral, KindObjectLiteralExpression, + KindParameter, KindPropertyAccessExpression, KindPropertyAssignment, KindPropertyDeclaration, KindPropertySignature, + KindSetAccessor, KindShorthandPropertyAssignment, KindSourceFile, KindSpreadAssignment, KindStringLiteral, + KindTypeAliasDeclaration, KindTypeLiteral, KindTypeParameter, KindVariableDeclaration: + return true + } + return false +} + func CanHaveIllegalDecorators(node *Node) bool { switch node.Kind { case KindPropertyAssignment, KindShorthandPropertyAssignment, @@ -1256,6 +1275,10 @@ func IsExternalModuleImportEqualsDeclaration(node *Node) bool { return node.Kind == KindImportEqualsDeclaration && node.AsImportEqualsDeclaration().ModuleReference.Kind == KindExternalModuleReference } +func IsModuleOrEnumDeclaration(node *Node) bool { + return node.Kind == KindModuleDeclaration || node.Kind == KindEnumDeclaration +} + func IsLiteralImportTypeNode(node *Node) bool { return IsImportTypeNode(node) && IsLiteralTypeNode(node.AsImportTypeNode().Argument) && IsStringLiteral(node.AsImportTypeNode().Argument.AsLiteralTypeNode().Literal) } @@ -1297,6 +1320,27 @@ func IsThisParameter(node *Node) bool { return IsParameter(node) && node.Name() != nil && IsThisIdentifier(node.Name()) } +func IsBindableStaticAccessExpression(node *Node, excludeThisKeyword bool) bool { + return IsPropertyAccessExpression(node) && + (!excludeThisKeyword && node.Expression().Kind == KindThisKeyword || IsIdentifier(node.Name()) && IsBindableStaticNameExpression(node.Expression() /*excludeThisKeyword*/, true)) || + IsBindableStaticElementAccessExpression(node, excludeThisKeyword) +} + +func IsBindableStaticElementAccessExpression(node *Node, excludeThisKeyword bool) bool { + return IsLiteralLikeElementAccess(node) && + ((!excludeThisKeyword && node.Expression().Kind == KindThisKeyword) || + IsEntityNameExpression(node.Expression()) || + IsBindableStaticAccessExpression(node.Expression() /*excludeThisKeyword*/, true)) +} + +func IsLiteralLikeElementAccess(node *Node) bool { + return IsElementAccessExpression(node) && IsStringOrNumericLiteralLike(node.AsElementAccessExpression().ArgumentExpression) +} + +func IsBindableStaticNameExpression(node *Node, excludeThisKeyword bool) bool { + return IsEntityNameExpression(node) || IsBindableStaticAccessExpression(node, excludeThisKeyword) +} + // Does not handle signed numeric names like `a[+0]` - handling those would require handling prefix unary expressions // throughout late binding handling as well, which is awkward (but ultimately probably doable if there is demand) func GetElementOrPropertyAccessName(node *Node) *Node { @@ -1315,6 +1359,13 @@ func GetElementOrPropertyAccessName(node *Node) *Node { panic("Unhandled case in GetElementOrPropertyAccessName") } +func GetInitializerOfBinaryExpression(expr *BinaryExpression) *Expression { + for IsBinaryExpression(expr.Right) { + expr = expr.Right.AsBinaryExpression() + } + return expr.Right.Expression() +} + func IsExpressionWithTypeArgumentsInClassExtendsClause(node *Node) bool { return TryGetClassExtendingExpressionWithTypeArguments(node) != nil } @@ -1658,6 +1709,40 @@ func GetThisContainer(node *Node, includeArrowFunctions bool, includeClassComput } } +func GetSuperContainer(node *Node, stopOnFunctions bool) *Node { + for { + node = node.Parent + if node == nil { + return nil + } + switch node.Kind { + case KindComputedPropertyName: + node = node.Parent + break + case KindFunctionDeclaration, KindFunctionExpression, KindArrowFunction: + if !stopOnFunctions { + continue + } + // falls through + + case KindPropertyDeclaration, KindPropertySignature, KindMethodDeclaration, KindMethodSignature, KindConstructor, KindGetAccessor, KindSetAccessor, KindClassStaticBlockDeclaration: + return node + case KindDecorator: + // Decorators are always applied outside of the body of a class or method. + if node.Parent.Kind == KindParameter && IsClassElement(node.Parent.Parent) { + // If the decorator's parent is a Parameter, we resolve the this container from + // the grandparent class declaration. + node = node.Parent.Parent + } else if IsClassElement(node.Parent) { + // If the decorator's parent is a class element, we resolve the 'this' container + // from the parent class declaration. + node = node.Parent + } + break + } + } +} + func GetImmediatelyInvokedFunctionExpression(fn *Node) *Node { if IsFunctionExpressionOrArrowFunction(fn) { prev := fn @@ -1766,13 +1851,13 @@ func IsExpressionNode(node *Node) bool { for node.Parent.Kind == KindQualifiedName { node = node.Parent } - return IsTypeQueryNode(node.Parent) || isJSDocLinkLike(node.Parent) || isJSXTagName(node) + return IsTypeQueryNode(node.Parent) || IsJSDocLinkLike(node.Parent) || isJSXTagName(node) case KindJSDocMemberName: - return IsTypeQueryNode(node.Parent) || isJSDocLinkLike(node.Parent) || isJSXTagName(node) + return IsTypeQueryNode(node.Parent) || IsJSDocLinkLike(node.Parent) || isJSXTagName(node) case KindPrivateIdentifier: return IsBinaryExpression(node.Parent) && node.Parent.AsBinaryExpression().Left == node && node.Parent.AsBinaryExpression().OperatorToken.Kind == KindInKeyword case KindIdentifier: - if IsTypeQueryNode(node.Parent) || isJSDocLinkLike(node.Parent) || isJSXTagName(node) { + if IsTypeQueryNode(node.Parent) || IsJSDocLinkLike(node.Parent) || isJSXTagName(node) { return true } fallthrough @@ -1915,7 +2000,7 @@ func isPartOfTypeExpressionWithTypeArguments(node *Node) bool { return IsHeritageClause(parent) && (!IsClassLike(parent.Parent) || parent.AsHeritageClause().Token == KindImplementsKeyword) } -func isJSDocLinkLike(node *Node) bool { +func IsJSDocLinkLike(node *Node) bool { return NodeKindIs(node, KindJSDocLink, KindJSDocLinkCode, KindJSDocLinkPlain) } @@ -1981,7 +2066,7 @@ func IsJSDocCommentContainingNode(node *Node) bool { node.Kind == KindJSDocText || node.Kind == KindJSDocTypeLiteral || node.Kind == KindJSDocSignature || - isJSDocLinkLike(node) || + IsJSDocLinkLike(node) || IsJSDocTag(node) } @@ -3451,25 +3536,6 @@ func IsCallOrNewExpression(node *Node) bool { return IsCallExpression(node) || IsNewExpression(node) } -func CanHaveSymbol(node *Node) bool { - switch node.Kind { - case KindArrowFunction, KindBinaryExpression, KindBindingElement, KindCallExpression, KindCallSignature, - KindClassDeclaration, KindClassExpression, KindClassStaticBlockDeclaration, KindConstructor, KindConstructorType, - KindConstructSignature, KindElementAccessExpression, KindEnumDeclaration, KindEnumMember, KindExportAssignment, KindJSExportAssignment, - KindExportDeclaration, KindExportSpecifier, KindFunctionDeclaration, KindFunctionExpression, KindFunctionType, - KindGetAccessor, KindIdentifier, KindImportClause, KindImportEqualsDeclaration, KindImportSpecifier, - KindIndexSignature, KindInterfaceDeclaration, KindJSDocSignature, KindJSDocTypeLiteral, - KindJsxAttribute, KindJsxAttributes, KindJsxSpreadAttribute, KindMappedType, KindMethodDeclaration, - KindMethodSignature, KindModuleDeclaration, KindNamedTupleMember, KindNamespaceExport, KindNamespaceExportDeclaration, - KindNamespaceImport, KindNewExpression, KindNoSubstitutionTemplateLiteral, KindNumericLiteral, KindObjectLiteralExpression, - KindParameter, KindPropertyAccessExpression, KindPropertyAssignment, KindPropertyDeclaration, KindPropertySignature, - KindSetAccessor, KindShorthandPropertyAssignment, KindSourceFile, KindSpreadAssignment, KindStringLiteral, - KindTypeAliasDeclaration, KindJSTypeAliasDeclaration, KindTypeLiteral, KindTypeParameter, KindVariableDeclaration: - return true - } - return false -} - func IndexOfNode(nodes []*Node, node *Node) int { index, ok := slices.BinarySearchFunc(nodes, node, compareNodePositions) if ok { diff --git a/internal/astnav/tokens.go b/internal/astnav/tokens.go index e332874f2c..2a3572b7d1 100644 --- a/internal/astnav/tokens.go +++ b/internal/astnav/tokens.go @@ -14,6 +14,10 @@ func GetTouchingPropertyName(sourceFile *ast.SourceFile, position int) *ast.Node }) } +func GetTouchingToken(sourceFile *ast.SourceFile, position int) *ast.Node { + return getTokenAtPosition(sourceFile, position, false /*allowPositionInLeadingTrivia*/, nil) +} + func GetTokenAtPosition(sourceFile *ast.SourceFile, position int) *ast.Node { return getTokenAtPosition(sourceFile, position, true /*allowPositionInLeadingTrivia*/, nil) } diff --git a/internal/binder/nameresolver.go b/internal/binder/nameresolver.go index ca8b26f28d..569b407cc9 100644 --- a/internal/binder/nameresolver.go +++ b/internal/binder/nameresolver.go @@ -40,7 +40,7 @@ loop: // (it refers to the constant type of the expression instead) return nil } - if isModuleOrEnumDeclaration(location) && lastLocation != nil && location.Name() == lastLocation { + if ast.IsModuleOrEnumDeclaration(location) && lastLocation != nil && location.Name() == lastLocation { // If lastLocation is the name of a namespace or enum, skip the parent since it will have is own locals that could // conflict. lastLocation = location @@ -99,7 +99,7 @@ loop: // name of that export default matches. result = moduleExports[ast.InternalSymbolNameDefault] if result != nil { - localSymbol := getLocalSymbolForExportDefault(result) + localSymbol := GetLocalSymbolForExportDefault(result) if localSymbol != nil && result.Flags&meaning != 0 && localSymbol.Name == name { break loop } @@ -448,11 +448,7 @@ func (r *NameResolver) argumentsSymbol() *ast.Symbol { return r.ArgumentsSymbol } -func isModuleOrEnumDeclaration(node *ast.Node) bool { - return node.Kind == ast.KindModuleDeclaration || node.Kind == ast.KindEnumDeclaration -} - -func getLocalSymbolForExportDefault(symbol *ast.Symbol) *ast.Symbol { +func GetLocalSymbolForExportDefault(symbol *ast.Symbol) *ast.Symbol { if !isExportDefaultSymbol(symbol) || len(symbol.Declarations) == 0 { return nil } diff --git a/internal/checker/checker.go b/internal/checker/checker.go index cdbff642a0..ff6e676b9b 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -1831,12 +1831,12 @@ func (c *Checker) isBlockScopedNameDeclaredBeforeUse(declaration *ast.Node, usag switch { case declaration.Kind == ast.KindBindingElement: // still might be illegal if declaration and usage are both binding elements (eg var [a = b, b = b] = [1, 2]) - errorBindingElement := getAncestor(usage, ast.KindBindingElement) + errorBindingElement := ast.FindAncestorKind(usage, ast.KindBindingElement) if errorBindingElement != nil { return ast.FindAncestor(errorBindingElement, ast.IsBindingElement) != ast.FindAncestor(declaration, ast.IsBindingElement) || declaration.Pos() < errorBindingElement.Pos() } // or it might be illegal if usage happens before parent variable is declared (eg var [a] = a) - return c.isBlockScopedNameDeclaredBeforeUse(getAncestor(declaration, ast.KindVariableDeclaration), usage) + return c.isBlockScopedNameDeclaredBeforeUse(ast.FindAncestorKind(declaration, ast.KindVariableDeclaration), usage) case declaration.Kind == ast.KindVariableDeclaration: // still might be illegal if usage is in the initializer of the variable declaration (eg var a = a) return !isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration, usage, declContainer) @@ -4197,7 +4197,7 @@ func (c *Checker) checkBaseTypeAccessibility(t *Type, node *ast.Node) { signatures := c.getSignaturesOfType(t, SignatureKindConstruct) if len(signatures) != 0 { declaration := signatures[0].declaration - if declaration != nil && hasModifier(declaration, ast.ModifierFlagsPrivate) { + if declaration != nil && HasModifier(declaration, ast.ModifierFlagsPrivate) { typeClassDeclaration := ast.GetClassLikeDeclarationOfSymbol(t.symbol) if !c.isNodeWithinClass(node, typeClassDeclaration) { c.error(node, diagnostics.Cannot_extend_a_class_0_Class_constructor_is_marked_as_private, c.getFullyQualifiedName(t.symbol, nil)) @@ -5649,7 +5649,7 @@ func (c *Checker) checkVarDeclaredNamesNotShadowed(node *ast.Node) { localDeclarationSymbol := c.resolveName(node, name.Text(), ast.SymbolFlagsVariable, nil /*nameNotFoundMessage*/, false /*isUse*/, false) if localDeclarationSymbol != nil && localDeclarationSymbol != symbol && localDeclarationSymbol.Flags&ast.SymbolFlagsBlockScopedVariable != 0 { if c.getDeclarationNodeFlagsFromSymbol(localDeclarationSymbol)&ast.NodeFlagsBlockScoped != 0 { - varDeclList := getAncestor(localDeclarationSymbol.ValueDeclaration, ast.KindVariableDeclarationList) + varDeclList := ast.FindAncestorKind(localDeclarationSymbol.ValueDeclaration, ast.KindVariableDeclarationList) var container *ast.Node if ast.IsVariableStatement(varDeclList.Parent) && varDeclList.Parent.Parent != nil { container = varDeclList.Parent.Parent @@ -6384,7 +6384,7 @@ func (c *Checker) checkAliasSymbol(node *ast.Node) { name := node.PropertyNameOrName().Text() c.addTypeOnlyDeclarationRelatedInfo(c.error(node, message, name), core.IfElse(isType, nil, typeOnlyAlias), name) } - if isType && node.Kind == ast.KindImportEqualsDeclaration && hasModifier(node, ast.ModifierFlagsExport) { + if isType && node.Kind == ast.KindImportEqualsDeclaration && HasModifier(node, ast.ModifierFlagsExport) { c.error(node, diagnostics.Cannot_use_export_import_on_a_type_or_type_only_namespace_when_0_is_enabled, c.getIsolatedModulesLikeFlagName()) } case ast.KindExportSpecifier: @@ -6674,7 +6674,7 @@ func (c *Checker) checkUnusedClassMembers(node *ast.Node) { break // Already would have reported an error on the getter. } symbol := c.getSymbolOfDeclaration(member) - if !c.isReferenced(symbol) && (hasModifier(member, ast.ModifierFlagsPrivate) || member.Name() != nil && ast.IsPrivateIdentifier(member.Name())) && member.Flags&ast.NodeFlagsAmbient == 0 { + if !c.isReferenced(symbol) && (HasModifier(member, ast.ModifierFlagsPrivate) || member.Name() != nil && ast.IsPrivateIdentifier(member.Name())) && member.Flags&ast.NodeFlagsAmbient == 0 { c.reportUnused(member, UnusedKindLocal, NewDiagnosticForNode(member.Name(), diagnostics.X_0_is_declared_but_its_value_is_never_read, c.symbolToString(symbol))) } case ast.KindConstructor: @@ -8229,7 +8229,7 @@ func (c *Checker) resolveNewExpression(node *ast.Node, candidatesOutArray *[]*Si } if expressionType.symbol != nil { valueDecl := ast.GetClassLikeDeclarationOfSymbol(expressionType.symbol) - if valueDecl != nil && hasModifier(valueDecl, ast.ModifierFlagsAbstract) { + if valueDecl != nil && HasModifier(valueDecl, ast.ModifierFlagsAbstract) { c.error(node, diagnostics.Cannot_create_an_instance_of_an_abstract_class) return c.resolveErrorCall(node) } @@ -18534,7 +18534,7 @@ func (c *Checker) getIndexInfosOfIndexSymbol(indexSymbol *ast.Symbol, siblingSym } forEachType(c.getTypeFromTypeNode(typeNode), func(keyType *Type) { if c.isValidIndexKeyType(keyType) && findIndexInfo(indexInfos, keyType) == nil { - indexInfo := c.newIndexInfo(keyType, valueType, hasModifier(declaration, ast.ModifierFlagsReadonly), declaration) + indexInfo := c.newIndexInfo(keyType, valueType, HasModifier(declaration, ast.ModifierFlagsReadonly), declaration) indexInfos = append(indexInfos, indexInfo) } }) @@ -26298,7 +26298,7 @@ func (c *Checker) markPropertyAsReferenced(prop *ast.Symbol, nodeForCheckWriteOn if prop.Flags&ast.SymbolFlagsClassMember == 0 || prop.ValueDeclaration == nil { return } - hasPrivateModifier := hasModifier(prop.ValueDeclaration, ast.ModifierFlagsPrivate) + hasPrivateModifier := HasModifier(prop.ValueDeclaration, ast.ModifierFlagsPrivate) hasPrivateIdentifier := prop.ValueDeclaration.Name() != nil && ast.IsPrivateIdentifier(prop.ValueDeclaration.Name()) if !hasPrivateModifier && !hasPrivateIdentifier { return @@ -29965,7 +29965,7 @@ func (c *Checker) getSymbolOfNameOrPropertyAccessExpression(name *ast.Node) *ast } } else if ast.IsEntityName(name) && isInRightSideOfImportOrExportAssignment(name) { // Since we already checked for ExportAssignment, this really could only be an Import - importEqualsDeclaration := getAncestor(name, ast.KindImportEqualsDeclaration) + importEqualsDeclaration := ast.FindAncestorKind(name, ast.KindImportEqualsDeclaration) if importEqualsDeclaration == nil { panic("ImportEqualsDeclaration should be defined") } diff --git a/internal/checker/exports.go b/internal/checker/exports.go index 48a4453eb9..52477ba12c 100644 --- a/internal/checker/exports.go +++ b/internal/checker/exports.go @@ -6,6 +6,10 @@ import ( "github.com/microsoft/typescript-go/internal/diagnostics" ) +func (c *Checker) GetStringType() *Type { + return c.stringType +} + func (c *Checker) GetUnknownSymbol() *ast.Symbol { return c.unknownSymbol } @@ -18,6 +22,10 @@ func (c *Checker) GetGlobalSymbol(name string, meaning ast.SymbolFlags, diagnost return c.getGlobalSymbol(name, meaning, diagnostic) } +func (c *Checker) GetMergedSymbol(symbol *ast.Symbol) *ast.Symbol { + return c.getMergedSymbol(symbol) +} + func (c *Checker) GetTypeFromTypeNode(node *ast.Node) *Type { return c.getTypeFromTypeNode(node) } @@ -30,6 +38,10 @@ func (c *Checker) GetPropertiesOfType(t *Type) []*ast.Symbol { return c.getPropertiesOfType(t) } +func (c *Checker) GetPropertyOfType(t *Type, name string) *ast.Symbol { + return c.getPropertyOfType(t, name) +} + func (c *Checker) TypeHasCallOrConstructSignatures(t *Type) bool { return c.typeHasCallOrConstructSignatures(t) } diff --git a/internal/checker/services.go b/internal/checker/services.go index 6eb89e044a..6adae744fa 100644 --- a/internal/checker/services.go +++ b/internal/checker/services.go @@ -407,6 +407,56 @@ func (c *Checker) GetExportSymbolOfSymbol(symbol *ast.Symbol) *ast.Symbol { return c.getMergedSymbol(core.IfElse(symbol.ExportSymbol != nil, symbol.ExportSymbol, symbol)) } +func (c *Checker) GetExportSpecifierLocalTargetSymbol(node *ast.Node) *ast.Symbol { + // node should be ExportSpecifier | Identifier + switch node.Kind { + case ast.KindExportSpecifier: + if node.Parent.Parent.AsExportDeclaration().ModuleSpecifier != nil { + return c.getExternalModuleMember(node.Parent.Parent, node, false /*dontResolveAlias*/) + } + name := node.PropertyName() + if name == nil { + name = node.Name() + } + if name.Kind == ast.KindStringLiteral { + // Skip for invalid syntax like this: export { "x" } + return nil + } + case ast.KindIdentifier: + // do nothing (don't panic) + default: + panic("Unhandled case in getExportSpecifierLocalTargetSymbol, node should be ExportSpecifier | Identifier") + } + return c.resolveEntityName(node, ast.SymbolFlagsValue|ast.SymbolFlagsType|ast.SymbolFlagsNamespace|ast.SymbolFlagsAlias, true /*ignoreErrors*/, false, nil) +} + +func (c *Checker) GetShorthandAssignmentValueSymbol(location *ast.Node) *ast.Symbol { + if location != nil && location.Kind == ast.KindShorthandPropertyAssignment { + return c.resolveEntityName(location.Name(), ast.SymbolFlagsValue|ast.SymbolFlagsAlias, true /*ignoreErrors*/, false, nil) + } + return nil +} + +/** +* Get symbols that represent parameter-property-declaration as parameter and as property declaration +* @param parameter a parameterDeclaration node +* @param parameterName a name of the parameter to get the symbols for. +* @return a tuple of two symbols + */ +func (c *Checker) GetSymbolsOfParameterPropertyDeclaration(parameter *ast.Node /*ParameterPropertyDeclaration*/, parameterName string) (*ast.Symbol, *ast.Symbol) { + constructorDeclaration := parameter.Parent + classDeclaration := parameter.Parent.Parent + + parameterSymbol := c.getSymbol(constructorDeclaration.Locals(), parameterName, ast.SymbolFlagsValue) + propertySymbol := c.getSymbol(c.getMembersOfSymbol(classDeclaration.Symbol()), parameterName, ast.SymbolFlagsValue) + + if parameterSymbol != nil && propertySymbol != nil { + return parameterSymbol, propertySymbol + } + + panic("There should exist two symbols, one as property declaration and one as parameter declaration") +} + func (c *Checker) GetTypeArgumentConstraint(node *ast.Node) *Type { if !ast.IsTypeNode(node) { return nil diff --git a/internal/checker/utilities.go b/internal/checker/utilities.go index 3d9e97e0ee..c09c9d99e7 100644 --- a/internal/checker/utilities.go +++ b/internal/checker/utilities.go @@ -91,12 +91,12 @@ func getSelectedModifierFlags(node *ast.Node, flags ast.ModifierFlags) ast.Modif return node.ModifierFlags() & flags } -func hasModifier(node *ast.Node, flags ast.ModifierFlags) bool { +func HasModifier(node *ast.Node, flags ast.ModifierFlags) bool { return node.ModifierFlags()&flags != 0 } func hasReadonlyModifier(node *ast.Node) bool { - return hasModifier(node, ast.ModifierFlagsReadonly) + return HasModifier(node, ast.ModifierFlagsReadonly) } func isStaticPrivateIdentifierProperty(s *ast.Symbol) bool { @@ -179,7 +179,7 @@ func isConstTypeReference(node *ast.Node) bool { return ast.IsTypeReferenceNode(node) && len(node.TypeArguments()) == 0 && ast.IsIdentifier(node.AsTypeReferenceNode().TypeName) && node.AsTypeReferenceNode().TypeName.Text() == "const" } -func getSingleVariableOfVariableStatement(node *ast.Node) *ast.Node { +func GetSingleVariableOfVariableStatement(node *ast.Node) *ast.Node { if !ast.IsVariableStatement(node) { return nil } @@ -420,7 +420,7 @@ func declarationBelongsToPrivateAmbientMember(declaration *ast.Node) bool { } func isPrivateWithinAmbient(node *ast.Node) bool { - return (hasModifier(node, ast.ModifierFlagsPrivate) || ast.IsPrivateIdentifierClassElementDeclaration(node)) && node.Flags&ast.NodeFlagsAmbient != 0 + return (HasModifier(node, ast.ModifierFlagsPrivate) || ast.IsPrivateIdentifierClassElementDeclaration(node)) && node.Flags&ast.NodeFlagsAmbient != 0 } func isTypeAssertion(node *ast.Node) bool { @@ -1202,13 +1202,6 @@ func isInAmbientOrTypeNode(node *ast.Node) bool { }) != nil } -func getAncestor(node *ast.Node, kind ast.Kind) *ast.Node { - for node != nil && node.Kind != kind { - node = node.Parent - } - return node -} - func isLiteralExpressionOfObject(node *ast.Node) bool { switch node.Kind { case ast.KindObjectLiteralExpression, ast.KindArrayLiteralExpression, ast.KindRegularExpressionLiteral, diff --git a/internal/compiler/program.go b/internal/compiler/program.go index eef24c592b..9e6ee60fd7 100644 --- a/internal/compiler/program.go +++ b/internal/compiler/program.go @@ -42,7 +42,9 @@ type Program struct { resolver *module.Resolver - comparePathsOptions tspath.ComparePathsOptions + comparePathsOptions tspath.ComparePathsOptions + supportedExtensions [][]string + supportedExtensionsWithJsonIfResolveJsonModule []string processedFiles @@ -877,6 +879,33 @@ func (p *Program) GetSourceFiles() []*ast.SourceFile { return p.files } +func (p *Program) GetLibFileFromReference(ref *ast.FileReference) *ast.SourceFile { + path, ok := tsoptions.GetLibFileName(ref.FileName) + if !ok { + return nil + } + if sourceFile, ok := p.filesByPath[tspath.Path(path)]; ok { + return sourceFile + } + return nil +} + +func (p *Program) GetResolvedTypeReferenceDirectiveFromTypeReferenceDirective(typeRef *ast.FileReference, sourceFile *ast.SourceFile) *module.ResolvedTypeReferenceDirective { + return p.resolver.ResolveTypeReferenceDirective( + typeRef.FileName, + sourceFile.FileName(), + p.getModeForTypeReferenceDirectiveInFile(typeRef, sourceFile), + nil, + ) +} + +func (p *Program) getModeForTypeReferenceDirectiveInFile(ref *ast.FileReference, sourceFile *ast.SourceFile) core.ResolutionMode { + if ref.ResolutionMode != core.ResolutionModeNone { + return ref.ResolutionMode + } + return p.GetDefaultResolutionModeForFile(sourceFile) +} + type FileIncludeKind int const ( diff --git a/internal/core/core.go b/internal/core/core.go index 79a95f3714..d73d9e395a 100644 --- a/internal/core/core.go +++ b/internal/core/core.go @@ -95,6 +95,17 @@ func MapNonNil[T any, U comparable](slice []T, f func(T) U) []U { return result } +func FlatMap[T any, U comparable](slice []T, f func(T) []U) []U { + var result []U + for _, value := range slice { + mapped := f(value) + if len(mapped) != 0 { + result = append(result, mapped...) + } + } + return result +} + func SameMap[T comparable](slice []T, f func(T) T) []T { for i, value := range slice { mapped := f(value) @@ -327,6 +338,12 @@ func Coalesce[T *U, U any](a T, b T) T { } } +// Returns the first element that is not `nil`; CoalesceList(a, b, c) is roughly analogous to `a ?? b ?? c` in JS, except that it +// non-shortcutting, so it is advised to only use a constant or precomputed value for non-first values in the list +func CoalesceList[T *U, U any](a ...T) T { + return FirstNonNil(a, func(t T) T { return t }) +} + func ComputeLineStarts(text string) []TextPos { result := make([]TextPos, 0, strings.Count(text, "\n")+1) return slices.AppendSeq(result, ComputeLineStartsSeq(text)) diff --git a/internal/ls/findallreferences.go b/internal/ls/findallreferences.go new file mode 100644 index 0000000000..16c435d74c --- /dev/null +++ b/internal/ls/findallreferences.go @@ -0,0 +1,1489 @@ +package ls + +import ( + "cmp" + "fmt" + "slices" + "strings" + "unicode/utf8" + + "github.com/microsoft/typescript-go/internal/ast" + "github.com/microsoft/typescript-go/internal/astnav" + "github.com/microsoft/typescript-go/internal/binder" + "github.com/microsoft/typescript-go/internal/checker" + "github.com/microsoft/typescript-go/internal/compiler" + "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/scanner" + + "github.com/microsoft/typescript-go/internal/tspath" +) + +// === types for settings === +type referenceUse int + +const ( + referenceUseNone referenceUse = 0 + referenceUseOther referenceUse = 1 + referenceUseReferences referenceUse = 2 + referenceUseRename referenceUse = 3 +) + +type refOptions struct { + findInStrings bool + findInComments bool + use referenceUse // other, references, rename + implementations bool + useAliasesForRename bool // renamed from providePrefixAndSuffixTextForRename. default: true +} + +// === types for results === + +type refInfo struct { + file *ast.SourceFile + fileName string + reference *ast.FileReference + unverified bool +} + +type SymbolAndEntries struct { + definition *Definition + references []*referenceEntry +} + +func NewSymbolAndEntries(kind definitionKind, node *ast.Node, symbol *ast.Symbol, references []*referenceEntry) *SymbolAndEntries { + return &SymbolAndEntries{ + &Definition{ + Kind: kind, + node: node, + symbol: symbol, + }, + references, + } +} + +type definitionKind int + +const ( + definitionKindSymbol definitionKind = 0 + definitionKindLabel definitionKind = 1 + definitionKindKeyword definitionKind = 2 + definitionKindThis definitionKind = 3 + definitionKindString definitionKind = 4 + definitionKindTripleSlashReference definitionKind = 5 +) + +type Definition struct { + Kind definitionKind + symbol *ast.Symbol + node *ast.Node + tripleSlashFileRef *tripleSlashDefinition +} +type tripleSlashDefinition struct { + reference *ast.FileReference + file *ast.SourceFile +} + +type entryKind int + +const ( + entryKindNone entryKind = 0 + entryKindRange entryKind = 1 + entryKindNode entryKind = 2 + entryKindStringLiteral entryKind = 3 + entryKindSearchedLocalFoundProperty entryKind = 4 + entryKindSearchedPropertyFoundLocal entryKind = 5 +) + +type referenceEntry struct { + kind entryKind + node *ast.Node + context *ast.Node // !!! ContextWithStartAndEndNode, optional + fileName string + textRange *lsproto.Range +} + +func (l *LanguageService) getRangeOfEntry(entry *referenceEntry) *lsproto.Range { + if entry.textRange == nil { + entry.textRange = l.getRangeOfNode(entry.node, nil, nil) + } + return entry.textRange +} + +func (l *LanguageService) newRangeEntry(file *ast.SourceFile, start, end int) *referenceEntry { + // !!! used in not-yet implemented features + return &referenceEntry{ + kind: entryKindRange, + fileName: file.FileName(), + textRange: l.createLspRangeFromBounds(start, end, file), + } +} + +func newNodeEntryWithKind(node *ast.Node, kind entryKind) *referenceEntry { + e := newNodeEntry(node) + e.kind = kind + return e +} + +func newNodeEntry(node *ast.Node) *referenceEntry { + // creates nodeEntry with `kind == entryKindNode` + n := node + if node != nil && node.Name() != nil { + n = node.Name() + } + return &referenceEntry{ + kind: entryKindNode, + node: node, + context: getContextNodeForNodeEntry(n), + } +} + +func getContextNodeForNodeEntry(node *ast.Node) *ast.Node { + if ast.IsDeclaration(node) { + return getContextNode(node) + } + + if node.Parent == nil { + return nil + } + + if !ast.IsDeclaration(node.Parent) && node.Parent.Kind != ast.KindExportAssignment && node.Parent.Kind != ast.KindJSExportAssignment { + // Special property assignment in javascript + if ast.IsInJSFile(node) { + // !!! jsdoc: check if branch still needed + binaryExpression := core.IfElse(node.Parent.Kind == ast.KindBinaryExpression, + node.Parent, + core.IfElse(ast.IsAccessExpression(node.Parent) && node.Parent.Parent.Kind == ast.KindBinaryExpression && node.Parent.Parent.AsBinaryExpression().Left == node.Parent, + node.Parent.Parent, + nil)) + if binaryExpression != nil && ast.GetAssignmentDeclarationKind(binaryExpression.AsBinaryExpression()) != ast.JSDeclarationKindNone { + return getContextNode(binaryExpression) + } + } + + // Jsx Tags + if node.Parent.Kind == ast.KindJsxOpeningElement || node.Parent.Kind == ast.KindJsxClosingElement { + return node.Parent.Parent + } else if node.Parent.Kind == ast.KindJsxSelfClosingElement || + node.Parent.Kind == ast.KindLabeledStatement || + node.Parent.Kind == ast.KindBreakStatement || + node.Parent.Kind == ast.KindContinueStatement { + return node.Parent + } else if node.Parent.Kind == ast.KindStringLiteral || node.Parent.Kind == ast.KindNoSubstitutionTemplateLiteral { + if validImport := tryGetImportFromModuleSpecifier(node); validImport != nil { + declOrStatement := ast.FindAncestor(validImport, func(*ast.Node) bool { + return ast.IsDeclaration(node) || ast.IsStatement(node) || ast.IsJSDocTag(node) + }) + if ast.IsDeclaration(declOrStatement) { + return getContextNode(declOrStatement) + } + return declOrStatement + } + } + + // Handle computed property name + propertyName := ast.FindAncestor(node, ast.IsComputedPropertyName) + if propertyName != nil { + return getContextNode(propertyName.Parent) + } + return nil + } + + if node.Parent.Name() == node || // node is name of declaration, use parent + node.Parent.Kind == ast.KindConstructor || + node.Parent.Kind == ast.KindExportAssignment || + node.Parent.Kind == ast.KindJSExportAssignment || + // Property name of the import export specifier or binding pattern, use parent + ((ast.IsImportOrExportSpecifier(node.Parent) || node.Parent.Kind == ast.KindBindingElement) && node.Parent.PropertyName() == node) || + // Is default export + (node.Kind == ast.KindDefaultKeyword && ast.HasSyntacticModifier(node.Parent, ast.ModifierFlagsExportDefault)) { + return getContextNode(node.Parent) + } + + return nil +} + +func getContextNode(node *ast.Node) *ast.Node { + if node == nil { + return nil + } + switch node.Kind { + case ast.KindVariableDeclaration: + if !ast.IsVariableDeclarationList(node.Parent) || len(node.Parent.AsVariableDeclarationList().Declarations.Nodes) != 1 { + return node + } else if ast.IsVariableStatement(node.Parent.Parent) { + return node.Parent.Parent + } else if ast.IsForInOrOfStatement(node.Parent.Parent) { + return getContextNode(node.Parent.Parent) + } + return node.Parent + + case ast.KindBindingElement: + return getContextNode(node.Parent.Parent) + + case ast.KindImportSpecifier: + return node.Parent.Parent.Parent + + case ast.KindExportSpecifier, ast.KindNamespaceImport: + return node.Parent.Parent + + case ast.KindImportClause, ast.KindNamespaceExport: + return node.Parent + + case ast.KindBinaryExpression: + return core.IfElse(node.Parent.Kind == ast.KindExpressionStatement, node.Parent, node) + + case ast.KindForOfStatement, ast.KindForInStatement: + // !!! not implemented + return nil + + case ast.KindPropertyAssignment, ast.KindShorthandPropertyAssignment: + if isArrayLiteralOrObjectLiteralDestructuringPattern(node.Parent) { + return getContextNode(ast.FindAncestor(node.Parent, func(node *ast.Node) bool { + return node.Kind == ast.KindBinaryExpression || ast.IsForInOrOfStatement(node) + })) + } + return node + case ast.KindSwitchStatement: + // !!! not implemented + return nil + default: + return node + } +} + +// utils +func (l *LanguageService) getRangeOfNode(node *ast.Node, sourceFile *ast.SourceFile, endNode *ast.Node) *lsproto.Range { + if sourceFile == nil { + sourceFile = ast.GetSourceFileOfNode(node) + } + start := scanner.GetTokenPosOfNode(node, sourceFile, false /*includeJsDoc*/) + end := core.IfElse(endNode != nil, endNode, node).End() + if ast.IsStringLiteralLike(node) && (end-start) > 2 { + if endNode != nil { + panic("endNode is not nil for stringLiteralLike") + } + start += 1 + end -= 1 + } + if endNode != nil && endNode.Kind == ast.KindCaseBlock { + end = endNode.Pos() + } + return l.createLspRangeFromBounds(start, end, sourceFile) +} + +func isValidReferencePosition(node *ast.Node, searchSymbolName string) bool { + switch node.Kind { + case ast.KindPrivateIdentifier: + // !!! + // if (isJSDocMemberName(node.Parent)) { + // return true; + // } + return len(node.Text()) == len(searchSymbolName) + case ast.KindIdentifier: + return len(node.Text()) == len(searchSymbolName) + case ast.KindNoSubstitutionTemplateLiteral, ast.KindStringLiteral: + return len(node.Text()) == len(searchSymbolName) && (isLiteralNameOfPropertyDeclarationOrIndexAccess(node) || + isNameOfModuleDeclaration(node) || + isExpressionOfExternalModuleImportEqualsDeclaration(node) || + // !!! object.defineProperty + // (ast.IsCallExpression(node.Parent) && ast.IsBindableObjectDefinePropertyCall(node.Parent) && node.Parent.Arguments()[1] == node) || + ast.IsImportOrExportSpecifier(node.Parent)) + case ast.KindNumericLiteral: + return isLiteralNameOfPropertyDeclarationOrIndexAccess(node) && len(node.Text()) == len(searchSymbolName) + case ast.KindDefaultKeyword: + return len("default") == len(searchSymbolName) + } + return false +} + +func isForRenameWithPrefixAndSuffixText(options refOptions) bool { + return options.use == referenceUseRename && options.useAliasesForRename +} + +func skipPastExportOrImportSpecifierOrUnion(symbol *ast.Symbol, node *ast.Node, checker *checker.Checker, useLocalSymbolForExportSpecifier bool) *ast.Symbol { + if node == nil { + return nil + } + parent := node.Parent + if parent.Kind == ast.KindExportSpecifier && useLocalSymbolForExportSpecifier { + return getLocalSymbolForExportSpecifier(node.AsIdentifier(), symbol, parent.AsExportSpecifier(), checker) + } + // If the symbol is declared as part of a declaration like `{ type: "a" } | { type: "b" }`, use the property on the union type to get more references. + return core.FirstNonNil(symbol.Declarations, func(decl *ast.Node) *ast.Symbol { + if decl.Parent == nil { + // Ignore UMD module and global merge + if symbol.Flags&ast.SymbolFlagsTransient != 0 { + return nil + } + // Assertions for GH#21814. We should be handling SourceFile symbols in `getReferencedSymbolsForModule` instead of getting here. + panic(fmt.Sprintf("Unexpected symbol at %s: %s", node.Kind.String(), symbol.Name)) + } + if decl.Parent.Kind == ast.KindTypeLiteral && decl.Parent.Parent.Kind == ast.KindUnionType { + return checker.GetPropertyOfType(checker.GetTypeFromTypeNode(decl.Parent.Parent), symbol.Name) + } + return nil + }) +} + +func getSymbolScope(symbol *ast.Symbol) *ast.Node { + // If this is the symbol of a named function expression or named class expression, + // then named references are limited to its own scope. + valueDeclaration := symbol.ValueDeclaration + if valueDeclaration != nil && (valueDeclaration.Kind == ast.KindFunctionExpression || valueDeclaration.Kind == ast.KindClassExpression) { + return valueDeclaration + } + + if len(symbol.Declarations) == 0 { + return nil + } + + declarations := symbol.Declarations + // If this is private property or method, the scope is the containing class + if symbol.Flags&(ast.SymbolFlagsProperty|ast.SymbolFlagsMethod) != 0 { + privateDeclaration := core.Find(declarations, func(d *ast.Node) bool { + return checker.HasModifier(d, ast.ModifierFlagsPrivate) || ast.IsPrivateIdentifierClassElementDeclaration(d) + }) + if privateDeclaration != nil { + return ast.FindAncestorKind(privateDeclaration, ast.KindClassDeclaration) + } + // Else this is a public property and could be accessed from anywhere. + return nil + } + + // If symbol is of object binding pattern element without property name we would want to + // look for property too and that could be anywhere + if core.Some(declarations, isObjectBindingElementWithoutPropertyName) { + return nil + } + + /* + If the symbol has a parent, it's globally visible unless: + - It's a private property (handled above). + - It's a type parameter. + - The parent is an external module: then we should only search in the module (and recurse on the export later). + - But if the parent has `export as namespace`, the symbol is globally visible through that namespace. + */ + exposedByParent := symbol.Parent != nil && symbol.Flags&ast.SymbolFlagsTypeParameter == 0 + if exposedByParent && !(checker.IsExternalModuleSymbol(symbol.Parent) && symbol.Parent.GlobalExports == nil) { + return nil + } + + var scope *ast.Node + for _, declaration := range declarations { + container := getContainerNode(declaration) + if scope != nil && scope != container { + // Different declarations have different containers, bail out + return nil + } + + if container == nil || (container.Kind == ast.KindSourceFile && !ast.IsExternalOrCommonJSModule(container.AsSourceFile())) { + // This is a global variable and not an external module, any declaration defined + // within this scope is visible outside the file + return nil + } + + scope = container + } + + // If symbol.parent, this means we are in an export of an external module. (Otherwise we would have returned `undefined` above.) + // For an export of a module, we may be in a declaration file, and it may be accessed elsewhere. E.g.: + // declare module "a" { export type T = number; } + // declare module "b" { import { T } from "a"; export const x: T; } + // So we must search the whole source file. (Because we will mark the source file as seen, we we won't return to it when searching for imports.) + if exposedByParent { + return ast.GetSourceFileOfNode(scope).AsNode() + } + return scope // TODO: GH#18217 +} + +// === functions on (*ls) === + +func (l *LanguageService) ProvideReferences(params *lsproto.ReferenceParams) []*lsproto.Location { + // `findReferencedSymbols` except only computes the information needed to return reference locations + program, sourceFile := l.getProgramAndFile(params.TextDocument.Uri) + position := int(l.converters.LineAndCharacterToPosition(sourceFile, params.Position)) + + node := astnav.GetTouchingPropertyName(sourceFile, position) + options := refOptions{use: referenceUseReferences} + + symbolsAndEntries := l.getReferencedSymbolsForNode(position, node, program, program.GetSourceFiles(), options, nil) + + return core.FlatMap(symbolsAndEntries, l.convertSymbolAndEntryToLocation) +} + +// == functions for conversions == +func (l *LanguageService) convertSymbolAndEntryToLocation(s *SymbolAndEntries) []*lsproto.Location { + var locations []*lsproto.Location + for _, ref := range s.references { + if ref.textRange == nil { + sourceFile := ast.GetSourceFileOfNode(ref.node) + ref.textRange = l.createLspRangeFromNode(ref.node, ast.GetSourceFileOfNode(ref.node)) + ref.fileName = sourceFile.FileName() + } + locations = append(locations, &lsproto.Location{ + Uri: FileNameToDocumentURI(ref.fileName), + Range: *ref.textRange, + }) + } + return locations +} + +func (l *LanguageService) mergeReferences(program *compiler.Program, referencesToMerge ...[]*SymbolAndEntries) []*SymbolAndEntries { + result := []*SymbolAndEntries{} + getSourceFileIndexOfEntry := func(program *compiler.Program, entry *referenceEntry) int { + var sourceFile *ast.SourceFile + if entry.kind == entryKindRange { + sourceFile = program.GetSourceFile(entry.fileName) + } else { + sourceFile = ast.GetSourceFileOfNode(entry.node) + } + return slices.Index(program.SourceFiles(), sourceFile) + } + + for _, references := range referencesToMerge { + if len(references) == 0 { + continue + } + if len(result) == 0 { + result = references + continue + } + for _, entry := range references { + if entry.definition == nil || entry.definition.Kind != definitionKindSymbol { + result = append(result, entry) + continue + } + symbol := entry.definition.symbol + refIndex := core.FindIndex(result, func(ref *SymbolAndEntries) bool { + return ref.definition != nil && + ref.definition.Kind == definitionKindSymbol && + ref.definition.symbol == symbol + }) + if refIndex == -1 { + result = append(result, entry) + continue + } + + reference := result[refIndex] + sortedRefs := append(reference.references, entry.references...) + slices.SortStableFunc(sortedRefs, func(entry1, entry2 *referenceEntry) int { + entry1File := getSourceFileIndexOfEntry(program, entry1) + entry2File := getSourceFileIndexOfEntry(program, entry2) + if entry1File != entry2File { + return cmp.Compare(entry1File, entry2File) + } + + return CompareRanges(l.getRangeOfEntry(entry1), l.getRangeOfEntry(entry2)) + }) + result[refIndex] = &SymbolAndEntries{ + definition: reference.definition, + references: sortedRefs, + } + } + } + return result +} + +// === functions for find all ref implementation === + +func (l *LanguageService) getReferencedSymbolsForNode(position int, node *ast.Node, program *compiler.Program, sourceFiles []*ast.SourceFile, options refOptions, sourceFilesSet *core.Set[string]) []*SymbolAndEntries { + // !!! cancellationToken + if sourceFilesSet == nil || sourceFilesSet.Len() == 0 { + sourceFilesSet = core.NewSetWithSizeHint[string](len(sourceFiles)) + for _, file := range sourceFiles { + sourceFilesSet.Add(file.FileName()) + } + } + + if node.Kind == ast.KindSourceFile { + resolvedRef := getReferenceAtPosition(node.AsSourceFile(), position, program) + if resolvedRef.file == nil { + return nil + } + + checker, done := program.GetTypeChecker(l.ctx) + defer done() + + if moduleSymbol := checker.GetMergedSymbol(resolvedRef.file.Symbol); moduleSymbol != nil { + return getReferencedSymbolsForModule(program, moduleSymbol /*excludeImportTypeOfExportEquals*/, false, sourceFiles, sourceFilesSet) + } + + // !!! not implemented + // fileIncludeReasons := program.getFileIncludeReasons(); + // if (!fileIncludeReasons) { + // return nil + // } + return []*SymbolAndEntries{{ + definition: &Definition{Kind: definitionKindTripleSlashReference, tripleSlashFileRef: &tripleSlashDefinition{reference: resolvedRef.reference}}, + references: getReferencesForNonModule(resolvedRef.file, program /*fileIncludeReasons,*/), + }} + } + + if !options.implementations { + // !!! cancellationToken + if special := getReferencedSymbolsSpecial(node, sourceFiles); special != nil { + return special + } + } + + checker, done := program.GetTypeChecker(l.ctx) + defer done() + + // constructors should use the class symbol, detected by name, if present + symbol := checker.GetSymbolAtLocation(core.IfElse(node.Kind == ast.KindConstructor && node.Parent.Name() != nil, node.Parent.Name(), node)) + // Could not find a symbol e.g. unknown identifier + if symbol == nil { + // String literal might be a property (and thus have a symbol), so do this here rather than in getReferencedSymbolsSpecial. + if !options.implementations && ast.IsStringLiteralLike(node) { + if isModuleSpecifierLike(node) { + // !!! not implemented + // fileIncludeReasons := program.GetFileIncludeReasons() + // if referencedFile := program.GetResolvedModuleFromModuleSpecifier(node, nil /*sourceFile*/); referencedFile != nil { + // return []*SymbolAndEntries{{ + // definition: &Definition{Kind: definitionKindString, node: node}, + // references: getReferencesForNonModule(referencedFile, program /*fileIncludeReasons,*/), + // }} + // } + // Fall through to string literal references. This is not very likely to return + // anything useful, but I guess it's better than nothing, and there's an existing + // test that expects this to happen (fourslash/cases/untypedModuleImport.ts). + } + // !!! not implemented + // return getReferencesForStringLiteral(node, sourceFiles, checker) // !!! cancellationToken + return nil + } + return nil + } + + if symbol.Name == ast.InternalSymbolNameExportEquals { + return getReferencedSymbolsForModule(program, symbol.Parent, false /*excludeImportTypeOfExportEquals*/, sourceFiles, sourceFilesSet) + } + + moduleReferences := l.getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol, program, sourceFiles, options, sourceFilesSet) // !!! cancellationToken + if moduleReferences != nil && symbol.Flags&ast.SymbolFlagsTransient != 0 { + return moduleReferences + } + + aliasedSymbol := getMergedAliasedSymbolOfNamespaceExportDeclaration(node, symbol, checker) + moduleReferencesOfExportTarget := l.getReferencedSymbolsForModuleIfDeclaredBySourceFile(aliasedSymbol, program, sourceFiles, options, sourceFilesSet) // !!! cancellationToken + + references := getReferencedSymbolsForSymbol(symbol, node, sourceFiles, sourceFilesSet, checker, options) // !!! cancellationToken + return l.mergeReferences(program, moduleReferences, references, moduleReferencesOfExportTarget) +} + +func (l *LanguageService) getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol *ast.Symbol, program *compiler.Program, sourceFiles []*ast.SourceFile, options refOptions, sourceFilesSet *core.Set[string]) []*SymbolAndEntries { + moduleSourceFileName := "" + if symbol == nil || !((symbol.Flags&ast.SymbolFlagsModule != 0) && len(symbol.Declarations) != 0) { + return nil + } + if moduleSourceFile := core.Find(symbol.Declarations, ast.IsSourceFile); moduleSourceFile != nil { + moduleSourceFileName = moduleSourceFile.AsSourceFile().FileName() + } else { + return nil + } + exportEquals := symbol.Exports[ast.InternalSymbolNameExportEquals] + // If exportEquals != nil, we're about to add references to `import("mod")` anyway, so don't double-count them. + moduleReferences := getReferencedSymbolsForModule(program, symbol, exportEquals != nil, sourceFiles, sourceFilesSet) + if exportEquals == nil || !sourceFilesSet.Has(moduleSourceFileName) { + return moduleReferences + } + // Continue to get references to 'export ='. + checker, done := program.GetTypeChecker(l.ctx) + defer done() + + symbol, _ = checker.ResolveAlias(exportEquals) + return l.mergeReferences(program, moduleReferences, getReferencedSymbolsForSymbol(symbol /*node*/, nil, sourceFiles, sourceFilesSet, checker /*, cancellationToken*/, options)) +} + +func getReferencedSymbolsSpecial(node *ast.Node, sourceFiles []*ast.SourceFile) []*SymbolAndEntries { + if isTypeKeyword(node.Kind) { + // A void expression (i.e., `void foo()`) is not special, but the `void` type is. + if node.Kind == ast.KindVoidKeyword && node.Parent.Kind == ast.KindVoidExpression { + return nil + } + + // A modifier readonly (like on a property declaration) is not special; + // a readonly type keyword (like `readonly string[]`) is. + if node.Kind == ast.KindReadonlyKeyword && !isReadonlyTypeOperator(node) { + return nil + } + // Likewise, when we *are* looking for a special keyword, make sure we + // *don't* include readonly member modifiers. + return getAllReferencesForKeyword( + sourceFiles, + node.Kind, + // cancellationToken, + node.Kind == ast.KindReadonlyKeyword, + ) + } + + if ast.IsImportMeta(node.Parent) && node.Parent.Name() == node { + // !!! unimplemented + return nil // getAllReferencesForImportMeta(sourceFiles /*, cancellationToken*/) + } + + if node.Kind == ast.KindStaticKeyword && node.Parent.Kind == ast.KindClassStaticBlockDeclaration { + return []*SymbolAndEntries{{definition: &Definition{Kind: definitionKindKeyword, node: node}, references: []*referenceEntry{newNodeEntry(node)}}} + } + + // Labels + if isJumpStatementTarget(node) { + // if we have a label definition, look within its statement for references, if not, then + // the label is undefined and we have no results.. + if labelDefinition := getTargetLabel(node.Parent, node.Text()); labelDefinition != nil { + return getLabelReferencesInNode(labelDefinition.Parent, labelDefinition) + } + return nil + } + + if isLabelOfLabeledStatement(node) { + // it is a label definition and not a target, search within the parent labeledStatement + return getLabelReferencesInNode(node.Parent, node.AsIdentifier()) + } + + if isThis(node) { + return getReferencesForThisKeyword(node, sourceFiles /*, cancellationToken*/) + } + + if node.Kind == ast.KindSuperKeyword { + return getReferencesForSuperKeyword(node) + } + + return nil +} + +func getLabelReferencesInNode(container *ast.Node, targetLabel *ast.Identifier) []*SymbolAndEntries { + sourceFile := ast.GetSourceFileOfNode(container) + labelName := targetLabel.Text + references := core.MapNonNil(getPossibleSymbolReferenceNodes(sourceFile, labelName, container), func(node *ast.Node) *referenceEntry { + // Only pick labels that are either the target label, or have a target that is the target label + if node == targetLabel.AsNode() || (isJumpStatementTarget(node) && getTargetLabel(node, labelName) == targetLabel) { + return newNodeEntry(node) + } + return nil + }) + return []*SymbolAndEntries{NewSymbolAndEntries(definitionKindLabel, targetLabel.AsNode(), nil, references)} +} + +func getReferencesForThisKeyword(thisOrSuperKeyword *ast.Node, sourceFiles []*ast.SourceFile) []*SymbolAndEntries { + searchSpaceNode := ast.GetThisContainer(thisOrSuperKeyword, false /*includeArrowFunctions*/, false /*includeClassComputedPropertyName*/) + + // Whether 'this' occurs in a static context within a class. + staticFlag := ast.ModifierFlagsStatic + isParameterName := func(node *ast.Node) bool { + return node.Kind == ast.KindIdentifier && node.Parent.Kind == ast.KindParameter && node.Parent.Name() == node + } + + switch searchSpaceNode.Kind { + case ast.KindMethodDeclaration, ast.KindMethodSignature, + ast.KindPropertyDeclaration, ast.KindPropertySignature, ast.KindConstructor, ast.KindGetAccessor, ast.KindSetAccessor: + if (searchSpaceNode.Kind == ast.KindMethodDeclaration || searchSpaceNode.Kind == ast.KindMethodSignature) && ast.IsObjectLiteralMethod(searchSpaceNode) { + staticFlag &= searchSpaceNode.ModifierFlags() + searchSpaceNode = searchSpaceNode.Parent // re-assign to be the owning object literals + break + } + staticFlag &= searchSpaceNode.ModifierFlags() + searchSpaceNode = searchSpaceNode.Parent // re-assign to be the owning class + break + case ast.KindSourceFile: + if ast.IsExternalModule(searchSpaceNode.AsSourceFile()) || isParameterName(thisOrSuperKeyword) { + return nil + } + case ast.KindFunctionDeclaration, ast.KindFunctionExpression: + break + // Computed properties in classes are not handled here because references to this are illegal, + // so there is no point finding references to them. + default: + return nil + } + + filesToSearch := sourceFiles + if searchSpaceNode.Kind == ast.KindSourceFile { + filesToSearch = []*ast.SourceFile{searchSpaceNode.AsSourceFile()} + } + references := core.Map( + core.FlatMap(filesToSearch, func(sourceFile *ast.SourceFile) []*ast.Node { + // cancellationToken.throwIfCancellationRequested(); + return core.Filter( + getPossibleSymbolReferenceNodes(sourceFile, "this", core.IfElse(searchSpaceNode.Kind == ast.KindSourceFile, sourceFile.AsNode(), searchSpaceNode)), + func(node *ast.Node) bool { + if !isThis(node) { + return false + } + container := ast.GetThisContainer(node /*includeArrowFunctions*/, false /*includeClassComputedPropertyName*/, false) + if !ast.CanHaveSymbol(container) { + return false + } + switch searchSpaceNode.Kind { + case ast.KindFunctionExpression, ast.KindFunctionDeclaration: + return searchSpaceNode.Symbol() == container.Symbol() + case ast.KindMethodDeclaration, ast.KindMethodSignature: + return ast.IsObjectLiteralMethod(searchSpaceNode) && searchSpaceNode.Symbol() == container.Symbol() + case ast.KindClassExpression, ast.KindClassDeclaration, ast.KindObjectLiteralExpression: + // Make sure the container belongs to the same class/object literals + // and has the appropriate static modifier from the original container. + return container.Parent != nil && ast.CanHaveSymbol(container.Parent) && searchSpaceNode.Symbol() == container.Parent.Symbol() && ast.IsStatic(container) == (staticFlag != ast.ModifierFlagsNone) + case ast.KindSourceFile: + return container.Kind == ast.KindSourceFile && !ast.IsExternalModule(container.AsSourceFile()) && !isParameterName(node) + } + return false + }) + }), + func(n *ast.Node) *referenceEntry { return newNodeEntry(n) }, + ) + + thisParameter := core.FirstNonNil(references, func(ref *referenceEntry) *ast.Node { + if ref.node.Parent.Kind == ast.KindParameter { + return ref.node + } + return nil + }) + if thisParameter == nil { + thisParameter = thisOrSuperKeyword + } + return []*SymbolAndEntries{NewSymbolAndEntries(definitionKindThis, thisParameter, nil, references)} +} + +func getReferencesForSuperKeyword(superKeyword *ast.Node) []*SymbolAndEntries { + searchSpaceNode := ast.GetSuperContainer(superKeyword, false /*stopOnFunctions*/) + if searchSpaceNode == nil { + return nil + } + // Whether 'super' occurs in a static context within a class. + staticFlag := ast.ModifierFlagsStatic + + switch searchSpaceNode.Kind { + case ast.KindPropertyDeclaration, ast.KindPropertySignature, ast.KindMethodDeclaration, ast.KindMethodSignature, ast.KindConstructor, ast.KindGetAccessor, ast.KindSetAccessor: + staticFlag &= searchSpaceNode.ModifierFlags() + searchSpaceNode = searchSpaceNode.Parent // re-assign to be the owning class + break + default: + return nil + } + + sourceFile := ast.GetSourceFileOfNode(searchSpaceNode) + references := core.MapNonNil(getPossibleSymbolReferenceNodes(sourceFile, "super", searchSpaceNode), func(node *ast.Node) *referenceEntry { + if node.Kind != ast.KindSuperKeyword { + return nil + } + + container := ast.GetSuperContainer(node, false /*stopOnFunctions*/) + + // If we have a 'super' container, we must have an enclosing class. + // Now make sure the owning class is the same as the search-space + // and has the same static qualifier as the original 'super's owner. + if container != nil && ast.IsStatic(container) == (staticFlag != ast.ModifierFlagsNone) && container.Parent.Symbol() == searchSpaceNode.Symbol() { + return newNodeEntry(node) + } + return nil + }) + + return []*SymbolAndEntries{NewSymbolAndEntries(definitionKindSymbol, nil, searchSpaceNode.Symbol(), references)} +} + +func getAllReferencesForKeyword(sourceFiles []*ast.SourceFile, keywordKind ast.Kind, filterReadOnlyTypeOperator bool) []*SymbolAndEntries { + // references is a list of NodeEntry + references := core.FlatMap(sourceFiles, func(sourceFile *ast.SourceFile) []*referenceEntry { + // cancellationToken.throwIfCancellationRequested(); + return core.MapNonNil(getPossibleSymbolReferenceNodes(sourceFile, scanner.TokenToString(keywordKind), sourceFile.AsNode()), func(referenceLocation *ast.Node) *referenceEntry { + if referenceLocation.Kind == keywordKind && (!filterReadOnlyTypeOperator || isReadonlyTypeOperator(referenceLocation)) { + return newNodeEntry(referenceLocation) + } + return nil + }) + }) + if len(references) == 0 { + return nil + } + return []*SymbolAndEntries{NewSymbolAndEntries(definitionKindKeyword, references[0].node, nil, references)} +} + +func getPossibleSymbolReferenceNodes(sourceFile *ast.SourceFile, symbolName string, container *ast.Node) []*ast.Node { + return core.MapNonNil(getPossibleSymbolReferencePositions(sourceFile, symbolName, container), func(pos int) *ast.Node { + if referenceLocation := astnav.GetTouchingPropertyName(sourceFile, pos); referenceLocation != sourceFile.AsNode() { + return referenceLocation + } + return nil + }) +} + +func getPossibleSymbolReferencePositions(sourceFile *ast.SourceFile, symbolName string, container *ast.Node) []int { + positions := []int{} + + /// TODO: Cache symbol existence for files to save text search + // Also, need to make this work for unicode escapes. + + // Be resilient in the face of a symbol with no name or zero length name + if symbolName == "" { + return positions + } + + text := sourceFile.Text() + sourceLength := len(text) + symbolNameLength := len(symbolName) + + if container == nil { + container = sourceFile.AsNode() + } + + position := strings.Index(text[container.Pos():], symbolName) + endPos := container.End() + for position >= 0 && position < endPos { + // We found a match. Make sure it's not part of a larger word (i.e. the char + // before and after it have to be a non-identifier char). + endPosition := position + symbolNameLength + + if (position == 0 || !scanner.IsIdentifierPart(rune(text[position-1]), core.ScriptTargetLatest)) && + (endPosition == sourceLength || !scanner.IsIdentifierPart(rune(text[endPosition]), core.ScriptTargetLatest)) { + // Found a real match. Keep searching. + positions = append(positions, position) + } + startIndex := position + symbolNameLength + 1 + if foundIndex := strings.Index(text[startIndex:], symbolName); foundIndex != -1 { + position = startIndex + foundIndex + } else { + break + } + } + + return positions +} + +func getReferencesForNonModule(referencedFile *ast.SourceFile, program *compiler.Program) []*referenceEntry { + // !!! not implemented + return []*referenceEntry{} +} + +func getMergedAliasedSymbolOfNamespaceExportDeclaration(node *ast.Node, symbol *ast.Symbol, checker *checker.Checker) *ast.Symbol { + if node.Parent != nil && node.Parent.Kind == ast.KindNamespaceExportDeclaration { + if aliasedSymbol, ok := checker.ResolveAlias(symbol); ok { + targetSymbol := checker.GetMergedSymbol(aliasedSymbol) + if aliasedSymbol != targetSymbol { + return targetSymbol + } + } + } + return nil +} + +func getReferencedSymbolsForModule(program *compiler.Program, symbol *ast.Symbol, excludeImportTypeOfExportEquals bool, sourceFiles []*ast.SourceFile, sourceFilesSet *core.Set[string]) []*SymbolAndEntries { + // !!! not implemented + return nil +} + +func getReferenceAtPosition(sourceFile *ast.SourceFile, position int, program *compiler.Program) *refInfo { + if referencePath := findReferenceInPosition(sourceFile.ReferencedFiles, position); referencePath != nil { + if file := program.GetSourceFileFromReference(sourceFile, referencePath); file != nil { + return &refInfo{reference: referencePath, fileName: file.FileName(), file: file, unverified: false} + } + return nil + } + + if typeReferenceDirective := findReferenceInPosition(sourceFile.TypeReferenceDirectives, position); typeReferenceDirective != nil { + if reference := program.GetResolvedTypeReferenceDirectiveFromTypeReferenceDirective(typeReferenceDirective, sourceFile); reference != nil { + if file := program.GetSourceFile(reference.ResolvedFileName); file != nil { + return &refInfo{reference: typeReferenceDirective, fileName: file.FileName(), file: file, unverified: false} + } + } + return nil + } + + if libReferenceDirective := findReferenceInPosition(sourceFile.LibReferenceDirectives, position); libReferenceDirective != nil { + if file := program.GetLibFileFromReference(libReferenceDirective); file != nil { + return &refInfo{reference: libReferenceDirective, fileName: file.FileName(), file: file, unverified: false} + } + return nil + } + + if len(sourceFile.Imports()) == 0 && len(sourceFile.ModuleAugmentations) == 0 { + return nil + } + + node := astnav.GetTouchingToken(sourceFile, position) + if !isModuleSpecifierLike(node) || !tspath.IsExternalModuleNameRelative(node.Text()) { + return nil + } + if resolution := program.GetResolvedModuleFromModuleSpecifier(sourceFile, node); resolution != nil { + verifiedFileName := resolution.ResolvedFileName + fileName := resolution.ResolvedFileName + if fileName == "" { + fileName = tspath.ResolvePath(tspath.GetDirectoryPath(sourceFile.FileName()), node.Text()) + } + return &refInfo{ + file: program.GetSourceFile(fileName), + fileName: fileName, + reference: nil, + unverified: verifiedFileName != "", + } + } + + return nil +} + +// -- Core algorithm for find all references -- +func getReferencedSymbolsForSymbol(originalSymbol *ast.Symbol, node *ast.Node, sourceFiles []*ast.SourceFile, sourceFilesSet *core.Set[string], checker *checker.Checker, options refOptions) []*SymbolAndEntries { + // Core find-all-references algorithm for a normal symbol. + + symbol := core.Coalesce(skipPastExportOrImportSpecifierOrUnion(originalSymbol, node, checker /*useLocalSymbolForExportSpecifier*/, !isForRenameWithPrefixAndSuffixText(options)), originalSymbol) + + // Compute the meaning from the location and the symbol it references + searchMeaning := getIntersectingMeaningFromDeclarations(node, symbol, ast.SemanticMeaningAll) + state := newState(sourceFiles, sourceFilesSet, node, checker /*, cancellationToken*/, searchMeaning, options) + + var exportSpecifier *ast.Node + if !isForRenameWithPrefixAndSuffixText(options) || len(symbol.Declarations) == 0 { + exportSpecifier = core.Find(symbol.Declarations, ast.IsExportSpecifier) + } + if exportSpecifier != nil { + // !!! not implemented + + // When renaming at an export specifier, rename the export and not the thing being exported. + // state.getReferencesAtExportSpecifier(exportSpecifier.Name(), symbol, exportSpecifier.AsExportSpecifier(), state.createSearch(node, originalSymbol, comingFromUnknown /*comingFrom*/, "", nil), true /*addReferencesHere*/, true /*alwaysGetReferences*/) + } else if node != nil && node.Kind == ast.KindDefaultKeyword && symbol.Name == ast.InternalSymbolNameDefault && symbol.Parent != nil { + state.addReference(node, symbol, entryKindNone) + // !!! not implemented + // state.searchForImportsOfExport(node, symbol, &ExportInfo{exportingModuleSymbol: symbol.Parent, exportKind: ExportKindDefault}) + } else { + search := state.createSearch(node, symbol, comingFromUnknown /*comingFrom*/, "", state.populateSearchSymbolSet(symbol, node, options.use == referenceUseRename, options.useAliasesForRename, options.implementations)) + state.getReferencesInContainerOrFiles(symbol, search) + } + + return state.result +} + +type ExportKind int + +const ( + ExportKindDefault ExportKind = 0 + ExportKindNamed ExportKind = 1 + ExportKindExportEquals ExportKind = 2 +) + +type ExportInfo struct { + exportingModuleSymbol *ast.Symbol + exportKind ExportKind +} + +type comingFromType int + +const ( + comingFromUnknown comingFromType = 0 + comingFromImport comingFromType = 1 + comingFromExport comingFromType = 2 +) + +// Symbol that is currently being searched for. +// This will be replaced if we find an alias for the symbol. +type refSearch struct { + // If coming from an export, we will not recursively search for the imported symbol (since that's where we came from). + comingFrom comingFromType // import, export + + symbol *ast.Symbol + text string + escapedText string + + // Only set if `options.implementations` is true. These are the symbols checked to get the implementations of a property access. + parents []*ast.Symbol + + allSearchSymbols []*ast.Symbol + + // Whether a symbol is in the search set. + // Do not compare directly to `symbol` because there may be related symbols to search for. See `populateSearchSymbolSet`. + includes func(symbol *ast.Symbol) bool +} + +// type ( +// ImportTracker = func(exportSymbol *ast.Symbol, exportInfo ExportInfo, isForRename bool) ImportsResult +// ImportsResult struct { +// importSearches []struct { +// importLocation *ast.ModuleExportName +// importSymbol *ast.Symbol +// } +// singleReferences []*ast.Node // ientifier | stringliteral +// indirectUsers []*ast.SourceFile +// } +// ) + +type inheritKey struct { + symbol, parent ast.SymbolId +} + +type refState struct { + sourceFiles []*ast.SourceFile + sourceFilesSet *core.Set[string] + specialSearchKind string // !!! none, constructor, class + checker *checker.Checker + // cancellationToken CancellationToken + searchMeaning ast.SemanticMeaning + options refOptions + result []*SymbolAndEntries + + inheritsFromCache map[inheritKey]bool + seenContainingTypeReferences *core.Set[*ast.Node] // node seen tracker + // seenReExportRHS *core.Set[*ast.Node] // node seen tracker + // importTracker ImportTracker + symbolIdToReferences map[ast.SymbolId]*SymbolAndEntries + sourceFileToSeenSymbols map[ast.NodeId]*core.Set[ast.SymbolId] +} + +func newState(sourceFiles []*ast.SourceFile, sourceFilesSet *core.Set[string], node *ast.Node, checker *checker.Checker, searchMeaning ast.SemanticMeaning, options refOptions) *refState { + return &refState{ + sourceFiles: sourceFiles, + sourceFilesSet: sourceFilesSet, + specialSearchKind: "none", // !!! other search kinds not implemented + checker: checker, + searchMeaning: searchMeaning, + options: options, + result: []*SymbolAndEntries{}, + inheritsFromCache: map[inheritKey]bool{}, + seenContainingTypeReferences: &core.Set[*ast.Node]{}, + // seenReExportRHS: &core.Set[*ast.Node]{}, + symbolIdToReferences: map[ast.SymbolId]*SymbolAndEntries{}, + sourceFileToSeenSymbols: map[ast.NodeId]*core.Set[ast.SymbolId]{}, + } +} + +// @param allSearchSymbols set of additional symbols for use by `includes` +func (state *refState) createSearch(location *ast.Node, symbol *ast.Symbol, comingFrom comingFromType, text string, allSearchSymbols []*ast.Symbol) *refSearch { + // Note: if this is an external module symbol, the name doesn't include quotes. + // Note: getLocalSymbolForExportDefault handles `export default class C {}`, but not `export default C` or `export { C as default }`. + // The other two forms seem to be handled downstream (e.g. in `skipPastExportOrImportSpecifier`), so special-casing the first form + // here appears to be intentional). + + symbolToSearchFor := binder.GetLocalSymbolForExportDefault(symbol) + if symbolToSearchFor == nil { + if s := getNonModuleSymbolOfMergedModuleSymbol(symbol); s != nil { + symbolToSearchFor = s + } else { + symbolToSearchFor = symbol + } + } + text = func() string { + var name string = ast.SymbolName(symbolToSearchFor) + firstChar, _ := utf8.DecodeRuneInString(name) + lastChar, _ := utf8.DecodeLastRuneInString(name) + if firstChar == lastChar && (firstChar == '\'' || firstChar == '"' || firstChar == '`') { + return name[1 : len(name)-1] + } + return name + }() + escapedText := text + if len(allSearchSymbols) == 0 { + allSearchSymbols = []*ast.Symbol{symbol} + } + includes := func(sym *ast.Symbol) bool { return slices.Contains(allSearchSymbols, sym) } + + search := &refSearch{symbol: symbol, comingFrom: comingFrom, text: text, escapedText: escapedText, allSearchSymbols: allSearchSymbols, includes: includes} + if state.options.implementations && location != nil { + search.parents = getParentSymbolsOfPropertyAccess(location, symbol, state.checker) + } + + return search +} + +func (state *refState) referenceAdder(searchSymbol *ast.Symbol) func(*ast.Node, entryKind) { + // !!! after find all references is fully implemented, rename this to something like 'getReferenceAdder' + symbolId := ast.GetSymbolId(searchSymbol) + symbolAndEntry := state.symbolIdToReferences[symbolId] + if symbolAndEntry == nil { + state.symbolIdToReferences[symbolId] = NewSymbolAndEntries(definitionKindSymbol, nil, searchSymbol, []*referenceEntry{}) + state.result = append(state.result, state.symbolIdToReferences[symbolId]) + symbolAndEntry = state.symbolIdToReferences[symbolId] + } + return func(node *ast.Node, kind entryKind) { + symbolAndEntry.references = append(symbolAndEntry.references, newNodeEntryWithKind(node, kind)) + } +} + +func (state *refState) addReference(referenceLocation *ast.Node, symbol *ast.Symbol, kind entryKind) { + // if rename symbol from default export anonymous function, for example `export default function() {}`, we do not need to add reference + if state.options.use == referenceUseRename && referenceLocation.Kind == ast.KindDefaultKeyword { + return + } + + addRef := state.referenceAdder(symbol) + if state.options.implementations { + state.addImplementationReferences(referenceLocation, func(n *ast.Node) { addRef(n, kind) }) + } else { + addRef(referenceLocation, kind) + } +} + +func (state *refState) addImplementationReferences(refNode *ast.Node, addRef func(*ast.Node)) { + // Check if we found a function/propertyAssignment/method with an implementation or initializer + if ast.IsDeclarationName(refNode) && isImplementation(refNode.Parent) { + addRef(refNode) + return + } + + if refNode.Kind != ast.KindIdentifier { + return + } + + if refNode.Parent.Kind == ast.KindShorthandPropertyAssignment { + // Go ahead and dereference the shorthand assignment by going to its definition + + // !!! not implemented + // getReferenceEntriesForShorthandPropertyAssignment(refNode, state.checker, addRef); + } + + // Check if the node is within an extends or implements clause + + if containingNode := getContainingNodeIfInHeritageClause(refNode); containingNode != nil { + addRef(containingNode) + return + } + + // If we got a type reference, try and see if the reference applies to any expressions that can implement an interface + // Find the first node whose parent isn't a type node -- i.e., the highest type node. + typeNode := ast.FindAncestor(refNode, func(a *ast.Node) bool { + return !ast.IsQualifiedName(a.Parent) && !ast.IsTypeNode(a.Parent) && !ast.IsTypeElement(a.Parent) + }) + + if typeNode == nil || typeNode.Parent.Type() == nil { + return + } + + typeHavingNode := typeNode.Parent + if typeHavingNode.Type() == typeNode && !state.seenContainingTypeReferences.AddIfAbsent(typeHavingNode) { + addIfImplementation := func(e *ast.Expression) { + if isImplementationExpression(e) { + addRef(e) + } + } + if ast.HasInitializer(typeHavingNode) { + addIfImplementation(typeHavingNode.Initializer()) + } else if ast.IsFunctionLike(typeHavingNode) && typeHavingNode.Body() != nil { + body := typeHavingNode.Body() + if body.Kind == ast.KindBlock { + ast.ForEachReturnStatement(body, func(returnStatement *ast.Node) bool { + if expr := returnStatement.Expression(); expr != nil { + addIfImplementation(expr) + } + return false + }) + } else { + addIfImplementation(body) + } + } else if ast.IsAssertionExpression(typeHavingNode) || ast.IsSatisfiesExpression(typeHavingNode) { + addIfImplementation(typeHavingNode.Expression()) + } + } +} + +func (state *refState) getReferencesInContainerOrFiles(symbol *ast.Symbol, search *refSearch) { + // Try to get the smallest valid scope that we can limit our search to; + // otherwise we'll need to search globally (i.e. include each file). + + if scope := getSymbolScope(symbol); scope != nil { + state.getReferencesInContainer(scope, ast.GetSourceFileOfNode(scope), search /*addReferencesHere*/, !(scope.Kind == ast.KindSourceFile && !slices.Contains(state.sourceFiles, scope.AsSourceFile()))) + } else { + // Global search + for _, sourceFile := range state.sourceFiles { + // state.cancellationToken.throwIfCancellationRequested(); + state.searchForName(sourceFile, search) + } + } +} + +func (state *refState) getReferencesInSourceFile(sourceFile *ast.SourceFile, search *refSearch, addReferencesHere bool) { + // state.cancellationToken.throwIfCancellationRequested(); + state.getReferencesInContainer(sourceFile.AsNode(), sourceFile, search, addReferencesHere) +} + +func (state *refState) getReferencesInContainer(container *ast.Node, sourceFile *ast.SourceFile, search *refSearch, addReferencesHere bool) { + // Search within node "container" for references for a search value, where the search value is defined as a + // tuple of (searchSymbol, searchText, searchLocation, and searchMeaning). + // searchLocation: a node where the search value + if !state.markSearchedSymbols(sourceFile, search.allSearchSymbols) { + return + } + + for _, position := range getPossibleSymbolReferencePositions(sourceFile, search.text, container) { + state.getReferencesAtLocation(sourceFile, position, search, addReferencesHere) + } +} + +func (state *refState) markSearchedSymbols(sourceFile *ast.SourceFile, symbols []*ast.Symbol) bool { + sourceId := ast.GetNodeId(sourceFile.AsNode()) + seenSymbols := state.sourceFileToSeenSymbols[sourceId] + if seenSymbols == nil { + seenSymbols = &core.Set[ast.SymbolId]{} + state.sourceFileToSeenSymbols[sourceId] = seenSymbols + } + + anyNewSymbols := false + for _, sym := range symbols { + symbolId := ast.GetSymbolId(sym) + if seenSymbols.Has(symbolId) { + continue + } + anyNewSymbols = true + seenSymbols.Add(symbolId) + } + return anyNewSymbols +} + +func (state *refState) getReferencesAtLocation(sourceFile *ast.SourceFile, position int, search *refSearch, addReferencesHere bool) { + referenceLocation := astnav.GetTouchingPropertyName(sourceFile, position) + + if !isValidReferencePosition(referenceLocation, search.text) { + // This wasn't the start of a token. Check to see if it might be a + // match in a comment or string if that's what the caller is asking + // for. + + // !!! not implemented + // if (!state.options.implementations && (state.options.findInStrings && isInString(sourceFile, position) || state.options.findInComments && isInNonReferenceComment(sourceFile, position))) { + // // In the case where we're looking inside comments/strings, we don't have + // // an actual definition. So just use 'undefined' here. Features like + // // 'Rename' won't care (as they ignore the definitions), and features like + // // 'FindReferences' will just filter out these results. + // state.addStringOrCommentReference(sourceFile.FileName, createTextSpan(position, search.text.length)); + // } + + return + } + + if getMeaningFromLocation(referenceLocation)&state.searchMeaning == 0 { + return + } + + referenceSymbol := state.checker.GetSymbolAtLocation(referenceLocation) + if referenceSymbol == nil { + return + } + + parent := referenceLocation.Parent + if parent.Kind == ast.KindImportSpecifier && parent.PropertyName() == referenceLocation { + // This is added through `singleReferences` in ImportsResult. If we happen to see it again, don't add it again. + return + } + + if parent.Kind == ast.KindExportSpecifier { + // !!! not implemented + // debug.Assert(referenceLocation.Kind == ast.KindIdentifier || referenceLocation.Kind == ast.KindStringLiteral) + // state.getReferencesAtExportSpecifier(referenceLocation /* Identifier | StringLiteral*/, referenceSymbol, parent.AsExportSpecifier(), search, addReferencesHere, false /*alwaysGetReferences*/) + return + } + + relatedSymbol, relatedSymbolKind := state.getRelatedSymbol(search, referenceSymbol, referenceLocation) + if relatedSymbol == nil { + state.getReferenceForShorthandProperty(referenceSymbol, search) + return + } + + switch state.specialSearchKind { + case "none": + if addReferencesHere { + state.addReference(referenceLocation, relatedSymbol, relatedSymbolKind) + } + case "constructor": + // !!! not implemented + // state.addConstructorReferences(referenceLocation, sourceFile, search) + case "class": + // !!! not implemented + // state.addClassStaticThisReferences(referenceLocation, search) + } + + // Use the parent symbol if the location is commonjs require syntax on javascript files only. + if ast.IsInJSFile(referenceLocation) && referenceLocation.Parent.Kind == ast.KindBindingElement && + ast.IsVariableDeclarationInitializedToRequire(referenceLocation.Parent.Parent.Parent) { + referenceSymbol = referenceLocation.Parent.Symbol() + // The parent will not have a symbol if it's an ObjectBindingPattern (when destructuring is used). In + // this case, just skip it, since the bound identifiers are not an alias of the import. + if referenceSymbol == nil { + return + } + } + + // !!! not implemented + // state.getImportOrExportReferences(referenceLocation, referenceSymbol, search) +} + +func (state *refState) getReferenceForShorthandProperty(referenceSymbol *ast.Symbol, search *refSearch) { + if referenceSymbol.Flags&ast.SymbolFlagsTransient != 0 || referenceSymbol.ValueDeclaration == nil { + return + } + shorthandValueSymbol := state.checker.GetShorthandAssignmentValueSymbol(referenceSymbol.ValueDeclaration) + name := ast.GetNameOfDeclaration(referenceSymbol.ValueDeclaration) + + // Because in short-hand property assignment, an identifier which stored as name of the short-hand property assignment + // has two meanings: property name and property value. Therefore when we do findAllReference at the position where + // an identifier is declared, the language service should return the position of the variable declaration as well as + // the position in short-hand property assignment excluding property accessing. However, if we do findAllReference at the + // position of property accessing, the referenceEntry of such position will be handled in the first case. + if name != nil && search.includes(shorthandValueSymbol) { + state.addReference(name, shorthandValueSymbol, entryKindNone) + } +} + +// === search === +func (state *refState) populateSearchSymbolSet(symbol *ast.Symbol, location *ast.Node, isForRename, providePrefixAndSuffixText, implementations bool) []*ast.Symbol { + if location == nil { + return []*ast.Symbol{symbol} + } + result := []*ast.Symbol{} + state.forEachRelatedSymbol( + symbol, + location, + isForRename, + !(isForRename && providePrefixAndSuffixText), + func(sym *ast.Symbol, root *ast.Symbol, base *ast.Symbol, _ entryKind) (*ast.Symbol, entryKind) { + // static method/property and instance method/property might have the same name. Only include static or only include instance. + if base != nil { + if isStaticSymbol(symbol) != isStaticSymbol(base) { + base = nil + } + } + + result = append(result, core.CoalesceList(base, root, sym)) + return nil, entryKindNone + }, // when try to find implementation, implementations is true, and not allowed to find base class + /*allowBaseTypes*/ func(_ *ast.Symbol) bool { return !implementations }, + ) + return result +} + +func (state *refState) getRelatedSymbol(search *refSearch, referenceSymbol *ast.Symbol, referenceLocation *ast.Node) (*ast.Symbol, entryKind) { + return state.forEachRelatedSymbol( + referenceSymbol, + referenceLocation, + false, /*isForRenamePopulateSearchSymbolSet*/ + state.options.use != referenceUseRename || state.options.useAliasesForRename, /*onlyIncludeBindingElementAtReferenceLocation*/ + func(sym *ast.Symbol, rootSymbol *ast.Symbol, baseSymbol *ast.Symbol, kind entryKind) (*ast.Symbol, entryKind) { + // check whether the symbol used to search itself is just the searched one. + if baseSymbol != nil { + // static method/property and instance method/property might have the same name. Only check static or only check instance. + if isStaticSymbol(referenceSymbol) != isStaticSymbol(baseSymbol) { + baseSymbol = nil + } + } + searchSym := core.CoalesceList(baseSymbol, rootSymbol, sym) + if searchSym != nil && search.includes(searchSym) { + if rootSymbol != nil && sym.CheckFlags&ast.CheckFlagsSynthetic == 0 { + return rootSymbol, kind + } + return sym, kind + } + // For a base type, use the symbol for the derived type. For a synthetic (e.g. union) property, use the union symbol. + return nil, entryKindNone + }, + func(rootSymbol *ast.Symbol) bool { + return !(len(search.parents) != 0 && !core.Some(search.parents, func(parent *ast.Symbol) bool { + return false + // !!! not implemented + // return state.explicitlyInheritsFrom(rootSymbol.Parent, parent) + })) + }, + ) +} + +func (state *refState) forEachRelatedSymbol( + symbol *ast.Symbol, + location *ast.Node, + isForRenamePopulateSearchSymbolSet, + onlyIncludeBindingElementAtReferenceLocation bool, + cbSymbol func(*ast.Symbol, *ast.Symbol, *ast.Symbol, entryKind) (*ast.Symbol, entryKind), + allowBaseTypes func(*ast.Symbol) bool, +) (*ast.Symbol, entryKind) { + fromRoot := func(sym *ast.Symbol, kind entryKind) (*ast.Symbol, entryKind) { + // If this is a union property: + // - In populateSearchSymbolsSet we will add all the symbols from all its source symbols in all unioned types. + // - In findRelatedSymbol, we will just use the union symbol if any source symbol is included in the search. + // If the symbol is an instantiation from a another symbol (e.g. widened symbol): + // - In populateSearchSymbolsSet, add the root the list + // - In findRelatedSymbol, return the source symbol if that is in the search. (Do not return the instantiation symbol.) + returnKind := entryKindNone + return core.FirstNonNil(state.checker.GetRootSymbols(sym), func(rootSymbol *ast.Symbol) *ast.Symbol { + if s, currKind := cbSymbol(sym, rootSymbol, nil /*baseSymbol*/, kind); s != nil { + returnKind = currKind + return s + } + // Add symbol of properties/methods of the same name in base classes and implemented interfaces definitions + if rootSymbol.Parent != nil && rootSymbol.Parent.Flags&(ast.SymbolFlagsClass|ast.SymbolFlagsInterface) != 0 && allowBaseTypes(rootSymbol) { + return getPropertySymbolsFromBaseTypes(rootSymbol.Parent, rootSymbol.Name, state.checker, func(base *ast.Symbol) *ast.Symbol { + s, currKind := cbSymbol(sym, rootSymbol, base, kind) + if s != nil { + returnKind = currKind + } + return s + }) + } + return nil + }), returnKind + } + // !!! not yet implemented + // const containingObjectLiteralElement = getContainingObjectLiteralElement(location); + // if (containingObjectLiteralElement) {} + + if aliasedSymbol := getMergedAliasedSymbolOfNamespaceExportDeclaration(location, symbol, state.checker); aliasedSymbol != nil { + // In case of UMD module and global merging, search for global as well + if res, kind := cbSymbol(aliasedSymbol, nil /*rootSymbol*/, nil /*baseSymbol*/, entryKindNode); res != nil { + return res, kind + } + } + + if res, kind := fromRoot(symbol, entryKindNone); res != nil { + return res, core.IfElse(kind != entryKindNone, kind, entryKindNode) + } + + if symbol.ValueDeclaration != nil && ast.IsParameterPropertyDeclaration(symbol.ValueDeclaration, symbol.ValueDeclaration.Parent) { + // For a parameter property, now try on the other symbol (property if this was a parameter, parameter if this was a property). + if symbol.ValueDeclaration == nil || symbol.ValueDeclaration.Kind != ast.KindParameter { + panic("expected symbol.ValueDeclaration to be a parameter") + } + paramProp1, paramProp2 := state.checker.GetSymbolsOfParameterPropertyDeclaration(symbol.ValueDeclaration, symbol.Name) + // Debug.assert(paramProps.length == 2 && (paramProps[0].flags & SymbolFlags.FunctionScopedVariable) && (paramProps[1].flags & SymbolFlags.Property)); // is [parameter, property] + if !(paramProp1.Flags&ast.SymbolFlagsFunctionScopedVariable != 0 && paramProp2.Flags&ast.SymbolFlagsProperty != 0) { + panic("Expected a parameter and a property") + } + return fromRoot(core.IfElse(symbol.Flags&ast.SymbolFlagsFunctionScopedVariable != 0, paramProp2, paramProp1), entryKindNone) + } + + if exportSpecifier := ast.GetDeclarationOfKind(symbol, ast.KindExportSpecifier); exportSpecifier != nil && (!isForRenamePopulateSearchSymbolSet || exportSpecifier.PropertyName() == nil) { + if localSymbol := state.checker.GetExportSpecifierLocalTargetSymbol(exportSpecifier); localSymbol != nil { + if res, kind := cbSymbol(localSymbol, nil /*rootSymbol*/, nil /*baseSymbol*/, entryKindNode); res != nil { + return res, kind + } + } + } + + // symbolAtLocation for a binding element is the local symbol. See if the search symbol is the property. + // Don't do this when populating search set for a rename when prefix and suffix text will be provided -- just rename the local. + if !isForRenamePopulateSearchSymbolSet { + var bindingElementPropertySymbol *ast.Symbol + if onlyIncludeBindingElementAtReferenceLocation { + if !isObjectBindingElementWithoutPropertyName(location.Parent) { + return nil, entryKindNone + } + bindingElementPropertySymbol = getPropertySymbolFromBindingElement(state.checker, location.Parent) + } else { + bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, state.checker) + } + if bindingElementPropertySymbol == nil { + return nil, entryKindNone + } + return fromRoot(bindingElementPropertySymbol, entryKindSearchedPropertyFoundLocal) + } + + // Debug.assert(isForRenamePopulateSearchSymbolSet); + + // due to the above assert and the arguments at the uses of this function, + // (onlyIncludeBindingElementAtReferenceLocation <=> !providePrefixAndSuffixTextForRename) holds + includeOriginalSymbolOfBindingElement := onlyIncludeBindingElementAtReferenceLocation + + if includeOriginalSymbolOfBindingElement { + if bindingElementPropertySymbol := getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, state.checker); bindingElementPropertySymbol != nil { + return fromRoot(bindingElementPropertySymbol, entryKindSearchedPropertyFoundLocal) + } + } + return nil, entryKindNone +} + +// Search for all occurrences of an identifier in a source file (and filter out the ones that match). +func (state *refState) searchForName(sourceFile *ast.SourceFile, search *refSearch) { + if _, ok := getNameTable(sourceFile)[search.escapedText]; ok { + state.getReferencesInSourceFile(sourceFile, search, true /*addReferencesHere*/) + } +} diff --git a/internal/ls/findallreferences_test.go b/internal/ls/findallreferences_test.go new file mode 100644 index 0000000000..bdddb69034 --- /dev/null +++ b/internal/ls/findallreferences_test.go @@ -0,0 +1,258 @@ +package ls_test + +import ( + "strings" + "testing" + + "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/testutil/lstestutil" + "github.com/microsoft/typescript-go/internal/testutil/projecttestutil" + "gotest.tools/v3/assert" +) + +func runFindReferencesTest(t *testing.T, input string, expectedLocations map[string]*core.Set[string]) { + testData := lstestutil.ParseTestData("/testing", input, "/file1.ts") + markerPositions := testData.MarkerPositions + ctx := projecttestutil.WithRequestID(t.Context()) + service, done := createLanguageService(ctx, testData.Files[0].Filename, map[string]any{ + testData.Files[0].Filename: testData.Files[0].Content, + }) + defer done() + + // for each marker location, calculate the expected ref location ahead of time so we don't have to re-calculate each location for every reference call + allExpectedLocations := map[lsproto.Location]string{} + for _, marker := range testData.MarkerPositions { + allExpectedLocations[*service.GetExpectedReferenceFromMarker(marker.Filename, marker.Position)] = marker.Name + } + + for requestMarkerName, expectedSet := range expectedLocations { + marker, ok := markerPositions[requestMarkerName] + if !ok { + t.Fatalf("No marker found for '%s'", requestMarkerName) + } + + referencesResult := service.TestProvideReferences(marker.Filename, marker.Position) + libReference := 0 + + for _, loc := range referencesResult { + if name, ok := allExpectedLocations[*loc]; ok { + // check if returned ref location is in this request's expected set + assert.Assert(t, expectedSet.Has(name), "Reference to '%s' not expected when find all references requested at %s", name, requestMarkerName) + } else if strings.Contains(string(loc.Uri), "///bundled:///libs") { + libReference += 1 + } else { + t.Fatalf("Found reference at loc '%v' when find all references triggered at '%s'", loc, requestMarkerName) + } + } + expectedNum := expectedSet.Len() + libReference + assert.Assert(t, len(referencesResult) == expectedNum, "assertion failed: expected %d references at marker %s, got %d", expectedNum, requestMarkerName, len(referencesResult)) + } +} + +func TestFindReferences(t *testing.T) { + t.Parallel() + + testCases := []struct { + title string + input string + expectedLocations map[string]*core.Set[string] + }{ + { + title: "getOccurencesIsDefinitionOfParameter", + input: `function f(/*1*/x: number) { + return /*2*/x + 1 +}`, + expectedLocations: map[string]*core.Set[string]{ + "1": core.NewSetFromItems("1", "2"), + "2": core.NewSetFromItems("1", "2"), + }, + }, + { + title: "findAllRefsUnresolvedSymbols1", + input: `let a: /*a0*/Bar; +let b: /*a1*/Bar; +let c: /*a2*/Bar; +let d: /*b0*/Bar./*c0*/X; +let e: /*b1*/Bar./*c1*/X; +let f: /*b2*/Bar./*d0*/X./*e0*/Y;`, + expectedLocations: map[string]*core.Set[string]{ + "a0": core.NewSetFromItems("a0", "a1", "a2"), + "a1": core.NewSetFromItems("a0", "a1", "a2"), + "a2": core.NewSetFromItems("a0", "a1", "a2"), + "b0": core.NewSetFromItems("b0", "b1", "b2"), + "b1": core.NewSetFromItems("b0", "b1", "b2"), + "b2": core.NewSetFromItems("b0", "b1", "b2"), + "c0": core.NewSetFromItems("c0", "c1"), + "c1": core.NewSetFromItems("c0", "c1"), + "d0": core.NewSetFromItems("d0"), + "e0": core.NewSetFromItems("e0"), + }, + }, + { + title: "findAllRefsPrimitive partial", + input: `const x: /*1*/any = 0; +const any = 2; +const y: /*2*/any = any; +function f(b: /*3*/boolean): /*4*/boolean; +type T = /*5*/never; type U = /*6*/never; +function n(x: /*7*/number): /*8*/number; +function o(x: /*9*/object): /*10*/object; +function s(x: /*11*/string): /*12*/string; +function sy(s: /*13*/symbol): /*14*/symbol; +function v(v: /*15*/void): /*16*/void; +`, + expectedLocations: map[string]*core.Set[string]{ + "1": core.NewSetFromItems("1", "2"), + "2": core.NewSetFromItems("1", "2"), + "3": core.NewSetFromItems("3", "4"), + "4": core.NewSetFromItems("3", "4"), + "5": core.NewSetFromItems("5", "6"), + "6": core.NewSetFromItems("5", "6"), + "7": core.NewSetFromItems("7", "8"), + "8": core.NewSetFromItems("7", "8"), + "9": core.NewSetFromItems("9", "10"), + "10": core.NewSetFromItems("9", "10"), + "11": core.NewSetFromItems("11", "12"), + "12": core.NewSetFromItems("11", "12"), + "13": core.NewSetFromItems("13", "14"), + "14": core.NewSetFromItems("13", "14"), + "15": core.NewSetFromItems("15", "16"), + "16": core.NewSetFromItems("15", "16"), + }, + }, + { + title: "findAllReferencesDynamicImport1Partial", + input: `export function foo() { return "foo"; } +/*1*/import("/*2*/./foo") +/*3*/var x = import("/*4*/./foo")`, + expectedLocations: map[string]*core.Set[string]{ + "1": {}, + }, + }, + { + title: "findAllRefsForDefaultExport02 partial", + input: `/*1*/export default function /*2*/DefaultExportedFunction() { + return /*3*/DefaultExportedFunction; +} + +var x: typeof /*4*/DefaultExportedFunction; + +var y = /*5*/DefaultExportedFunction(); + +/*6*/namespace /*7*/DefaultExportedFunction { +}`, + expectedLocations: map[string]*core.Set[string]{ + "2": core.NewSetFromItems("2", "3", "4", "5"), + "3": core.NewSetFromItems("2", "3", "4", "5"), + "4": core.NewSetFromItems("2", "3", "4", "5"), + "5": core.NewSetFromItems("2", "3", "4", "5"), + "7": core.NewSetFromItems("7"), + }, + }, + { + title: "findAllReferPropertyAccessExpressionHeritageClause", + input: `class B {} +function foo() { + return {/*1*/B: B}; +} +class C extends (foo())./*2*/B {} +class C1 extends foo()./*3*/B {}`, + expectedLocations: map[string]*core.Set[string]{ + "1": core.NewSetFromItems("1", "2", "3"), + "2": core.NewSetFromItems("1", "2", "3"), + "3": core.NewSetFromItems("1", "2", "3"), + }, + }, + { + title: "findAllRefsForFunctionExpression01 partial", + input: `var foo = /*1*/function /*2*/foo(a = /*3*/foo(), b = () => /*4*/foo) { + /*5*/foo(/*6*/foo, /*7*/foo); +}`, + expectedLocations: map[string]*core.Set[string]{ + "1": core.NewSetFromItems("1", "2", "3", "4", "5", "6", "7"), + "2": core.NewSetFromItems("1", "2", "3", "4", "5", "6", "7"), + "3": core.NewSetFromItems("1", "2", "3", "4", "5", "6", "7"), + "4": core.NewSetFromItems("1", "2", "3", "4", "5", "6", "7"), + "5": core.NewSetFromItems("1", "2", "3", "4", "5", "6", "7"), + "6": core.NewSetFromItems("1", "2", "3", "4", "5", "6", "7"), + "7": core.NewSetFromItems("1", "2", "3", "4", "5", "6", "7"), + }, + }, + { + title: "findAllRefsForObjectSpread", + input: `interface A1 { readonly /*0*/a: string }; +interface A2 { /*1*/a?: number }; +let a1: A1; +let a2: A2; +let a12 = { ...a1, ...a2 }; +a12./*2*/a; +a1./*3*/a;`, + expectedLocations: map[string]*core.Set[string]{ + "0": core.NewSetFromItems("0", "2", "3"), + "1": core.NewSetFromItems("1", "2"), + "2": core.NewSetFromItems("0", "1", "2"), + "3": core.NewSetFromItems("0", "2", "3"), + }, + }, + { + title: "findAllRefsForObjectLiteralProperties", + input: `var x = { + /*1*/property: {} +}; + +x./*2*/property; + +/*3*/let {/*4*/property: pVar} = x;`, + expectedLocations: map[string]*core.Set[string]{ + "0": core.NewSetFromItems("0", "2", "3", "4"), + "1": core.NewSetFromItems("1", "2", "3", "4"), + "2": core.NewSetFromItems("1", "2", "3", "4"), + "3": core.NewSetFromItems("1", "2", "3", "4"), + }, + }, + { + title: "findAllRefsImportEquals", + input: `import j = N./*0*/q; +namespace N { export const /*1*/q = 0; }`, + expectedLocations: map[string]*core.Set[string]{ + "0": core.NewSetFromItems("0", "1"), + }, + }, + { + title: "findAllRefsForRest", + input: `interface Gen { +x: number +/*0*/parent: Gen; +millennial: string; +} +let t: Gen; +var { x, ...rest } = t; +rest./*1*/parent;`, + expectedLocations: map[string]*core.Set[string]{ + "0": core.NewSetFromItems("0", "1"), + "1": core.NewSetFromItems("0", "1"), + }, + }, + { + title: "findAllRefsForVariableInExtendsClause01", + input: `/*1*/var /*2*/Base = class { }; +class C extends /*3*/Base { }`, + expectedLocations: map[string]*core.Set[string]{ + "1": core.NewSetFromItems("1", "2", "3"), + "2": core.NewSetFromItems("1", "2", "3"), + "3": core.NewSetFromItems("1", "2", "3"), + }, + }, + } + + for _, testCase := range testCases { + if testCase.title != "findAllReferPropertyAccessExpressionHeritageClause" { + continue + } + t.Run(testCase.title, func(t *testing.T) { + t.Parallel() + runFindReferencesTest(t, testCase.input, testCase.expectedLocations) + }) + } +} diff --git a/internal/ls/findallreferencesexport_test.go b/internal/ls/findallreferencesexport_test.go new file mode 100644 index 0000000000..0077a3d7a6 --- /dev/null +++ b/internal/ls/findallreferencesexport_test.go @@ -0,0 +1,31 @@ +package ls + +import ( + "github.com/microsoft/typescript-go/internal/astnav" + "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" +) + +func (l *LanguageService) GetExpectedReferenceFromMarker(fileName string, pos int) *lsproto.Location { + // Temporary testing function--this function only works for markers that are on symbols/names. + // We won't need this once marker ranges are implemented, or once reference tests are baselined + _, sourceFile := l.tryGetProgramAndFile(fileName) + node := astnav.GetTouchingPropertyName(sourceFile, pos) + return &lsproto.Location{ + Uri: FileNameToDocumentURI(fileName), + Range: *l.createLspRangeFromNode(node, sourceFile), + } +} + +func (l *LanguageService) TestProvideReferences(fileName string, pos int) []*lsproto.Location { + _, sourceFile := l.tryGetProgramAndFile(fileName) + lsPos := l.converters.PositionToLineAndCharacter(sourceFile, core.TextPos(pos)) + return l.ProvideReferences(&lsproto.ReferenceParams{ + TextDocumentPositionParams: lsproto.TextDocumentPositionParams{ + TextDocument: lsproto.TextDocumentIdentifier{ + Uri: FileNameToDocumentURI(fileName), + }, + Position: lsPos, + }, + }) +} diff --git a/internal/ls/utilities.go b/internal/ls/utilities.go index 2a45bb486a..47bfb5193f 100644 --- a/internal/ls/utilities.go +++ b/internal/ls/utilities.go @@ -1,7 +1,9 @@ package ls import ( + "cmp" "fmt" + "slices" "strings" "github.com/microsoft/typescript-go/internal/ast" @@ -14,6 +16,26 @@ import ( "github.com/microsoft/typescript-go/internal/stringutil" ) +// 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.Line, other.Line) +} + +// 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 { @@ -59,6 +81,56 @@ func tryGetImportFromModuleSpecifier(node *ast.StringLiteralLike) *ast.Node { return nil } +func isModuleSpecifierLike(node *ast.Node) bool { + if !ast.IsStringLiteralLike(node) { + return false + } + + if ast.IsVariableDeclarationInitializedToRequire(node.Parent) || 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 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 || exportSpecifier.Name == referenceLocation); + if !(exportSpecifier.PropertyName == referenceLocation.AsNode() || exportSpecifier.Name() == referenceLocation.AsNode()) { + panic("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 + } +} + // !!! formatting function func isInComment(file *ast.SourceFile, position int, tokenAtPosition *ast.Node) *ast.CommentRange { return nil @@ -356,6 +428,92 @@ func getPossibleTypeArgumentsInfo(tokenIn *ast.Node, sourceFile *ast.SourceFile) 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 { for node.Parent.Kind == ast.KindQualifiedName { node = node.Parent @@ -623,12 +781,121 @@ func literalIsName(node *ast.NumericOrStringLikeLiteral) bool { 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 isArrayLiteralOrObjectLiteralDestructuringPattern(node *ast.Node) bool { + if node.Kind == ast.KindArrayLiteralExpression || node.Kind == ast.KindObjectLiteralExpression { + // [a,b,c] from: + // [a, b, c] = someExpression; + if node.Parent.Kind == ast.KindBinaryExpression && node.Parent.AsBinaryExpression().Left == node && node.Parent.AsBinaryExpression().OperatorToken.Kind == ast.KindEqualsToken { + return true + } + + // [a, b, c] from: + // for([a, b, c] of expression) + if node.Parent.Kind == ast.KindForOfStatement && node.Parent.AsForInOrOfStatement().Initializer == node { + return true + } + + // [a, b, c] of + // [x, [a, b, c] ] = someExpression + // or + // {x, a: {a, b, c} } = someExpression + if isArrayLiteralOrObjectLiteralDestructuringPattern(core.IfElse(node.Parent.Kind == ast.KindPropertyAssignment, node.Parent.Parent, node.Parent)) { + return true + } + } + + 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 } @@ -816,6 +1083,475 @@ func nodeEndsWith(n *ast.Node, expectedLastToken ast.Kind, sourceFile *ast.Sourc 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 { + if namedBindings.Kind == 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() + } else if namedBindings.Kind == 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|] ... + if node.ExportClause.Kind == 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() + } else if node.ExportClause.Kind == 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 + if node.Kind == ast.KindSourceFile { + return ast.SemanticMeaningValue + } else if ast.NodeKindIs(node, ast.KindExportAssignment, ast.KindExportSpecifier, ast.KindExternalModuleReference, ast.KindImportSpecifier, ast.KindImportClause) || parent.Kind == ast.KindImportEqualsDeclaration && node == parent.Name() { + return ast.SemanticMeaningAll + } else if 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 + } else if ast.IsDeclarationName(node) { + return getMeaningFromDeclaration(parent) + } else if ast.IsEntityName(node) && ast.FindAncestor(node, func(*ast.Node) bool { + return node.Kind == ast.KindJSDocNameReference || ast.IsJSDocLinkLike(node) || node.Kind == ast.KindJSDocMemberName + }) != nil { + return ast.SemanticMeaningAll + } else if isTypeReference(node) { + return ast.SemanticMeaningType + } else if isNamespaceReference(node) { + return ast.SemanticMeaningNamespace + } else if parent.Kind == ast.KindTypeParameter { + return ast.SemanticMeaningType + } else if parent.Kind == ast.KindLiteralType { + // This might be T["name"], which is actually referencing a property and not a type. So allow both meanings. + return ast.SemanticMeaningType | ast.SemanticMeaningValue + } else { + 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) { @@ -830,6 +1566,96 @@ func getAllSuperTypeNodes(node *ast.Node) []*ast.TypeNode { return nil } +func getParentSymbolsOfPropertyAccess(location *ast.Node, symbol *ast.Symbol, ch *checker.Checker) []*ast.Symbol { + propertyAccessExpression := core.IfElse(isRightSideOfPropertyAccess(location), location.Parent, nil) + if propertyAccessExpression == nil { + return nil + } + + lhsType := ch.GetTypeAtLocation(propertyAccessExpression.Expression()) + if lhsType == nil { + return nil + } + + var possibleSymbols []*checker.Type + if lhsType.Flags() != 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 { + seen := core.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 + } + + return core.FirstNonNil(symbol.Declarations, func(declaration *ast.Declaration) *ast.Symbol { + return core.FirstNonNil(getAllSuperTypeNodes(declaration), func(typeReference *ast.TypeNode) *ast.Symbol { + propertyType := checker.GetTypeAtLocation(typeReference) + if propertyType == nil || propertyType.Symbol() == nil { + return nil + } + propertySymbol := checker.GetPropertyOfType(propertyType, propertyName) + // Visit the typeReference as well to see if it directly or indirectly uses that property + if propertySymbol != nil { + if r := core.FirstNonNil(checker.GetRootSymbols(propertySymbol), cb); r != nil { + return r + } + } + return recur(propertyType.Symbol()) + }) + }) + } + 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.Identifier { + // todo: rewrite as `ast.FindAncestor` + for referenceNode != nil { + if referenceNode.Kind == ast.KindLabeledStatement && referenceNode.AsLabeledStatement().Label.Text() == labelName { + return referenceNode.AsLabeledStatement().Label.AsIdentifier() + } + referenceNode = referenceNode.Parent + } + return nil +} + func skipConstraint(t *checker.Type, typeChecker *checker.Checker) *checker.Type { if t.IsTypeParameter() { c := typeChecker.GetBaseConstraintOfType(t) diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 25c42458f8..7d0ec50dc3 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -410,6 +410,8 @@ func (s *Server) handleRequestOrNotification(ctx context.Context, req *lsproto.R return s.handleDefinition(ctx, req) case *lsproto.CompletionParams: return s.handleCompletion(ctx, req) + case *lsproto.ReferenceParams: + return s.handleReferences(ctx, req) case *lsproto.SignatureHelpParams: return s.handleSignatureHelp(ctx, req) default: @@ -464,6 +466,9 @@ func (s *Server) handleInitialize(req *lsproto.RequestMessage) { DefinitionProvider: &lsproto.BooleanOrDefinitionOptions{ Boolean: ptrTo(true), }, + ReferencesProvider: &lsproto.BooleanOrReferenceOptions{ + Boolean: ptrTo(true), + }, DiagnosticProvider: &lsproto.DiagnosticOptionsOrDiagnosticRegistrationOptions{ DiagnosticOptions: &lsproto.DiagnosticOptions{ InterFileDependencies: true, @@ -583,6 +588,26 @@ func (s *Server) handleDefinition(ctx context.Context, req *lsproto.RequestMessa return nil } +func (s *Server) handleReferences(ctx context.Context, req *lsproto.RequestMessage) error { + // findAllReferences + params := req.Params.(*lsproto.ReferenceParams) + project := s.projectService.EnsureDefaultProjectForURI(params.TextDocument.Uri) + languageService, done := project.GetLanguageServiceForRequest(ctx) + defer done() + // !!! remove this after find all references is fully ported/tested + defer func() { + if r := recover(); r != nil { + stack := debug.Stack() + s.Log("panic obtaining references:", r, string(stack)) + s.sendResult(req.ID, []*lsproto.Location{}) + } + }() + + locations := languageService.ProvideReferences(params) + s.sendResult(req.ID, locations) + return nil +} + func (s *Server) handleCompletion(ctx context.Context, req *lsproto.RequestMessage) error { params := req.Params.(*lsproto.CompletionParams) project := s.projectService.EnsureDefaultProjectForURI(params.TextDocument.Uri) diff --git a/internal/tspath/path.go b/internal/tspath/path.go index 1fc4229152..caf1740cac 100644 --- a/internal/tspath/path.go +++ b/internal/tspath/path.go @@ -316,6 +316,14 @@ func ResolvePath(path string, paths ...string) string { return NormalizePath(combinedPath) } +func ResolveTripleslashReference(moduleName string, containingFile string) string { + basePath := GetDirectoryPath(containingFile) + if IsRootedDiskPath(moduleName) { + return NormalizePath(moduleName) + } + return NormalizePath(CombinePaths(basePath, moduleName)) +} + func GetNormalizedPathComponents(path string, currentDirectory string) []string { return reducePathComponents(GetPathComponents(path, currentDirectory)) }