From db09dbbf4242b9f12be8d4d2a069412f78a85930 Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Thu, 15 May 2025 16:04:24 -0700 Subject: [PATCH 01/13] initial --- internal/ast/utilities.go | 208 +++- internal/astnav/tokens.go | 4 + internal/binder/nameresolver.go | 6 +- internal/checker/checker.go | 60 +- internal/checker/exports.go | 12 + internal/checker/printer.go | 2 +- internal/checker/services.go | 20 + internal/checker/utilities.go | 31 +- internal/compiler/program.go | 104 +- internal/core/core.go | 17 + internal/ls/findallreferences.go | 1609 ++++++++++++++++++++++++++++++ internal/ls/utilities.go | 899 +++++++++++++++++ internal/lsp/server.go | 15 + internal/tspath/path.go | 8 + 14 files changed, 2959 insertions(+), 36 deletions(-) create mode 100644 internal/ls/findallreferences.go diff --git a/internal/ast/utilities.go b/internal/ast/utilities.go index 36cd8b764c..96e2e3907f 100644 --- a/internal/ast/utilities.go +++ b/internal/ast/utilities.go @@ -1029,6 +1029,26 @@ 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, KindIdentifier, KindImportClause, KindImportEqualsDeclaration, KindImportSpecifier, + KindIndexSignature, KindInterfaceDeclaration, KindJSDocCallbackTag, + KindJSDocParameterTag, KindJSDocPropertyTag, KindJSDocSignature, KindJSDocTypedefTag, 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, KindTypeLiteral, KindTypeParameter, KindVariableDeclaration: + return true + } + return false +} + func CanHaveIllegalDecorators(node *Node) bool { switch node.Kind { case KindPropertyAssignment, KindShorthandPropertyAssignment, @@ -1249,6 +1269,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) } @@ -1290,6 +1314,37 @@ func IsThisParameter(node *Node) bool { return IsParameter(node) && node.Name() != nil && IsThisIdentifier(node.Name()) } +func IsBindableObjectDefinePropertyCall(expr *Node) bool { + return len(expr.Arguments()) == 3 && + IsPropertyAccessExpression(expr.Expression()) && + IsIdentifier(expr.Expression().Expression()) && + // IdText(expr.Expression().Expression()) == "Object" && + // IdText(expr.Expression().Name()) == "defineProperty" && + IsStringOrNumericLiteralLike(expr.Arguments()[1]) && + IsBindableStaticNameExpression(expr.Arguments()[0] /*excludeThisKeyword*/, true) +} + +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 GetElementOrPropertyAccessArgumentExpressionOrName(node *Node) *Node { @@ -1314,6 +1369,13 @@ func GetElementOrPropertyAccessName(node *Node) string { return name.Text() } +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 } @@ -1535,14 +1597,14 @@ func GetImplementsHeritageClauseElements(node *Node) []*ExpressionWithTypeArgume } func GetHeritageElements(node *Node, kind Kind) []*Node { - clause := getHeritageClause(node, kind) + clause := GetHeritageClause(node, kind) if clause != nil { return clause.AsHeritageClause().Types.Nodes } return nil } -func getHeritageClause(node *Node, kind Kind) *Node { +func GetHeritageClause(node *Node, kind Kind) *Node { clauses := getHeritageClauses(node) if clauses != nil { for _, clause := range clauses.Nodes { @@ -1631,6 +1693,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 @@ -1739,13 +1835,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 @@ -1888,7 +1984,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) } @@ -1951,7 +2047,7 @@ func IsJSDocCommentContainingNode(node *Node) bool { node.Kind == KindJSDocText || node.Kind == KindJSDocTypeLiteral || node.Kind == KindJSDocSignature || - isJSDocLinkLike(node) || + IsJSDocLinkLike(node) || IsJSDocTag(node) } @@ -2429,6 +2525,104 @@ func GetDeclarationContainer(node *Node) *Node { }).Parent } +type AssignmentDeclarationKind = int32 + +const ( + AssignmentDeclarationKindNone = AssignmentDeclarationKind(iota) + /// exports.name = expr + /// module.exports.name = expr + AssignmentDeclarationKindExportsProperty + /// module.exports = expr + AssignmentDeclarationKindModuleExports + /// className.prototype.name = expr + AssignmentDeclarationKindPrototypeProperty + /// this.name = expr + AssignmentDeclarationKindThisProperty + // F.name = expr + AssignmentDeclarationKindProperty + // F.prototype = { ... } + AssignmentDeclarationKindPrototype + // Object.defineProperty(x, 'name', { value: any, writable?: boolean (false by default) }); + // Object.defineProperty(x, 'name', { get: Function, set: Function }); + // Object.defineProperty(x, 'name', { get: Function }); + // Object.defineProperty(x, 'name', { set: Function }); + AssignmentDeclarationKindObjectDefinePropertyValue + // Object.defineProperty(exports || module.exports, 'name', ...); + AssignmentDeclarationKindObjectDefinePropertyExports + // Object.defineProperty(Foo.prototype, 'name', ...); + AssignmentDeclarationKindObjectDefinePrototypeProperty +) + +// / Given a BinaryExpression, returns SpecialPropertyAssignmentKind for the various kinds of property +// / assignments we treat as special in the binder +func GetAssignmentDeclarationKind(expr *Expression /*BinaryExpression | CallExpression*/) AssignmentDeclarationKind { + var special AssignmentDeclarationKind + switch expr.Kind { + case KindCallExpression: + if !IsBindableObjectDefinePropertyCall(expr) { + special = AssignmentDeclarationKindNone + } else { + entityName := expr.Arguments()[0] + if IsExportsIdentifier(entityName) || IsModuleExportsAccessExpression(entityName) { + special = AssignmentDeclarationKindObjectDefinePropertyExports + } else if IsBindableStaticAccessExpression(entityName, false) && GetElementOrPropertyAccessName(entityName) == "prototype" { + special = AssignmentDeclarationKindObjectDefinePrototypeProperty + } else { + special = AssignmentDeclarationKindObjectDefinePropertyValue + } + } + default: // KindBinaryExpression + binExpr := expr.AsBinaryExpression() + if binExpr.OperatorToken.Kind != KindEqualsToken || !IsAccessExpression(binExpr.Left) || isVoidZero(GetRightMostAssignedExpression(expr)) { + special = AssignmentDeclarationKindNone + } else if IsBindableStaticNameExpression(binExpr.Left.Expression() /*excludeThisKeyword*/, true) && + GetElementOrPropertyAccessName(binExpr.Left) == "prototype" && + IsObjectLiteralExpression(GetInitializerOfBinaryExpression(binExpr)) { + // F.prototype = { ... } + special = AssignmentDeclarationKindPrototype + } else { + special = GetAssignmentDeclarationPropertyAccessKind(binExpr.Left) + } + } + return core.IfElse(special == AssignmentDeclarationKindProperty || IsInJSFile(expr), special, AssignmentDeclarationKindNone) +} + +func GetAssignmentDeclarationPropertyAccessKind(lhs *Node) AssignmentDeclarationKind { + if lhs.Expression().Kind == KindThisKeyword { + return AssignmentDeclarationKindThisProperty + } else if IsModuleExportsAccessExpression(lhs) { + // module.exports = expr + return AssignmentDeclarationKindModuleExports + } else if IsBindableStaticNameExpression(lhs.Expression() /*excludeThisKeyword*/, true) { + if IsPrototypeAccess(lhs.Expression()) { + // F.G....prototype.x = expr + return AssignmentDeclarationKindPrototypeProperty + } + + nextToLast := lhs + for nextToLast.Expression().Kind != KindIdentifier { + nextToLast = nextToLast.Expression() + } + idText := nextToLast.Expression().AsIdentifier().Text + if (idText == "exports" || idText == "module" && GetElementOrPropertyAccessName(nextToLast) == "exports") && + // ExportsProperty does not support binding with computed names + IsBindableStaticAccessExpression(lhs, false) { + // exports.name = expr OR module.exports.name = expr OR exports["name"] = expr ... + return AssignmentDeclarationKindExportsProperty + } + if IsBindableStaticNameExpression(lhs /*excludeThisKeyword*/, true) || (IsElementAccessExpression(lhs) && IsDynamicName(lhs)) { + // F.G...x = expr + return AssignmentDeclarationKindProperty + } + } + + return AssignmentDeclarationKindNone +} + +func IsPrototypeAccess(node *Node) bool { + return IsBindableStaticAccessExpression(node, false) && GetElementOrPropertyAccessName(node) == "prototype" +} + // Indicates that a symbol is an alias that does not merge with a local declaration. // OR Is a JSContainer which may merge an alias with a local declaration func IsNonLocalAlias(symbol *Symbol, excludes SymbolFlags) bool { 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..b356da2218 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 @@ -448,10 +448,6 @@ 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 { if !isExportDefaultSymbol(symbol) || len(symbol.Declarations) == 0 { return nil diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 993804de3d..2281fe6a7e 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -1799,12 +1799,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 := GetAncestor(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(GetAncestor(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) @@ -4162,7 +4162,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)) @@ -5610,7 +5610,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 := GetAncestor(localDeclarationSymbol.ValueDeclaration, ast.KindVariableDeclarationList) var container *ast.Node if ast.IsVariableStatement(varDeclList.Parent) && varDeclList.Parent.Parent != nil { container = varDeclList.Parent.Parent @@ -6345,7 +6345,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: @@ -6631,7 +6631,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: @@ -8186,7 +8186,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) } @@ -13584,6 +13584,36 @@ func (c *Checker) getExportSymbolOfValueSymbolIfExported(symbol *ast.Symbol) *as return c.getMergedSymbol(symbol) } +func (c *Checker) GetExportSpecifierLocalTargetSymbol(node *ast.Node) *ast.Symbol { + switch node.Kind { + case ast.KindExportSpecifier: + if node.Parent.Parent.AsExportDeclaration().ModuleSpecifier != nil { + return c.getExternalModuleMember(node.Parent.Parent, node, false /*dontResolveAlias*/) + } + name := node.AsExportSpecifier().PropertyName + if name == nil { + name = node.Name() + } + if name.Kind == ast.KindStringLiteral { + // Skip for invalid syntax like this: export { "x" } + return nil + } + // fall through + case ast.KindIdentifier: + // fall through + default: + panic("Unhandled case in getExportSpecifierLocalTargetSymbol") + } + 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 +} + func (c *Checker) getSymbolOfDeclaration(node *ast.Node) *ast.Symbol { symbol := node.Symbol() if symbol != nil { @@ -13638,7 +13668,7 @@ func (c *Checker) getTargetOfImportEqualsDeclaration(node *ast.Node, dontResolve if ast.IsVariableDeclaration(node) || node.AsImportEqualsDeclaration().ModuleReference.Kind == ast.KindExternalModuleReference { moduleReference := getExternalModuleRequireArgument(node) if moduleReference == nil { - moduleReference = getExternalModuleImportEqualsDeclarationExpression(node) + moduleReference = GetExternalModuleImportEqualsDeclarationExpression(node) } immediate := c.resolveExternalModuleName(node, moduleReference, false /*ignoreErrors*/) resolved := c.resolveExternalModuleSymbol(immediate, false /*dontResolveAlias*/) @@ -13669,7 +13699,7 @@ func (c *Checker) getSymbolOfPartOfRightHandSideOfImportEquals(entityName *ast.N // import a = |b|; // Namespace // import a = |b.c|; // Value, type, namespace // import a = |b.c|.d; // Namespace - if entityName.Kind == ast.KindIdentifier && isRightSideOfQualifiedNameOrPropertyAccess(entityName) { + if entityName.Kind == ast.KindIdentifier && IsRightSideOfQualifiedNameOrPropertyAccess(entityName) { entityName = entityName.Parent // QualifiedName } // Check for case 1 and 3 in the above example @@ -18089,7 +18119,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) } }) @@ -25849,7 +25879,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 @@ -29409,7 +29439,7 @@ func (c *Checker) getSymbolAtLocation(node *ast.Node, ignoreErrors bool) *ast.Sy // 2). External module name in an import declaration // 3). Require in Javascript // 4). type A = import("./f/*gotToDefinitionHere*/oo") - if (ast.IsExternalModuleImportEqualsDeclaration(grandParent) && getExternalModuleImportEqualsDeclarationExpression(grandParent) == node) || + if (ast.IsExternalModuleImportEqualsDeclaration(grandParent) && GetExternalModuleImportEqualsDeclarationExpression(grandParent) == node) || ((parent.Kind == ast.KindImportDeclaration || parent.Kind == ast.KindJSImportDeclaration || parent.Kind == ast.KindExportDeclaration) && parent.AsImportDeclaration().ModuleSpecifier == node) || ast.IsVariableDeclarationInitializedToRequire(grandParent) || (ast.IsLiteralTypeNode(parent) && ast.IsLiteralImportTypeNode(grandParent) && grandParent.AsImportTypeNode().Argument == parent) { @@ -29494,7 +29524,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 := GetAncestor(name, ast.KindImportEqualsDeclaration) if importEqualsDeclaration == nil { panic("ImportEqualsDeclaration should be defined") } @@ -29510,7 +29540,7 @@ func (c *Checker) getSymbolOfNameOrPropertyAccessExpression(name *ast.Node) *ast } } - for isRightSideOfQualifiedNameOrPropertyAccess(name) { + for IsRightSideOfQualifiedNameOrPropertyAccess(name) { name = name.Parent } @@ -29803,7 +29833,7 @@ func (c *Checker) getApplicableIndexSymbol(t *Type, keyType *Type) *ast.Symbol { } func (c *Checker) getRegularTypeOfExpression(expr *ast.Node) *Type { - if isRightSideOfQualifiedNameOrPropertyAccess(expr) { + if IsRightSideOfQualifiedNameOrPropertyAccess(expr) { expr = expr.Parent } return c.getRegularTypeOfLiteralType(c.getTypeOfExpression(expr)) diff --git a/internal/checker/exports.go b/internal/checker/exports.go index 4bf67fbba0..ad0c264d93 100644 --- a/internal/checker/exports.go +++ b/internal/checker/exports.go @@ -5,6 +5,10 @@ import ( "github.com/microsoft/typescript-go/internal/diagnostics" ) +func (c *Checker) GetStringType() *Type { + return c.stringType +} + func (c *Checker) GetUnionType(types []*Type) *Type { return c.getUnionType(types) } @@ -13,6 +17,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) } @@ -25,6 +33,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/printer.go b/internal/checker/printer.go index 994caffa02..9cf405f4a3 100644 --- a/internal/checker/printer.go +++ b/internal/checker/printer.go @@ -753,7 +753,7 @@ func (c *Checker) getTextAndTypeOfNode(node *ast.Node) (string, *Type, bool) { } } } - if ast.IsExpressionNode(node) && !isRightSideOfQualifiedNameOrPropertyAccess(node) { + if ast.IsExpressionNode(node) && !IsRightSideOfQualifiedNameOrPropertyAccess(node) { return scanner.GetTextOfNode(node), c.getTypeOfExpression(node), false } return "", nil, false diff --git a/internal/checker/services.go b/internal/checker/services.go index 9fd5a8b0a9..05c8ae253f 100644 --- a/internal/checker/services.go +++ b/internal/checker/services.go @@ -399,6 +399,26 @@ func (c *Checker) GetExportSymbolOfSymbol(symbol *ast.Symbol) *ast.Symbol { return c.getMergedSymbol(core.IfElse(symbol.ExportSymbol != nil, symbol.ExportSymbol, symbol)) } +/** +* 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 ca8704fc60..cb634094ba 100644 --- a/internal/checker/utilities.go +++ b/internal/checker/utilities.go @@ -88,12 +88,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 { @@ -176,7 +176,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 } @@ -359,12 +359,12 @@ func getExternalModuleRequireArgument(node *ast.Node) *ast.Node { return nil } -func getExternalModuleImportEqualsDeclarationExpression(node *ast.Node) *ast.Node { +func GetExternalModuleImportEqualsDeclarationExpression(node *ast.Node) *ast.Node { // Debug.assert(isExternalModuleImportEqualsDeclaration(node)) return node.AsImportEqualsDeclaration().ModuleReference.AsExternalModuleReference().Expression } -func isRightSideOfQualifiedNameOrPropertyAccess(node *ast.Node) bool { +func IsRightSideOfQualifiedNameOrPropertyAccess(node *ast.Node) bool { parent := node.Parent switch parent.Kind { case ast.KindQualifiedName: @@ -464,7 +464,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 { @@ -1007,6 +1007,23 @@ func getPropertyNameFromType(t *Type) string { panic("Unhandled case in getPropertyNameFromType") } +func GetEffectiveImplementsTypeNodes(node *ast.Node) []*ast.TypeNode { + // compiler utilities + if ast.IsInJSFile(node) { + // not implmented jsdoc + // return getJSDocImplementsTags(node).map(n => n.class); + } else if heritageClause := ast.GetHeritageClause(node, ast.KindImplementsKeyword); heritageClause != nil { + return heritageClause.AsHeritageClause().Types.Nodes + } + return nil +} + +func GetEffectiveBaseTypeNode(node *ast.Node) *ast.TypeNode { + baseType := ast.GetExtendsHeritageClauseElement(node) + // !!! not implemented jsdoc cases + return baseType +} + func isNumericLiteralName(name string) bool { // The intent of numeric names is that // - they are names with text in a numeric form, and that @@ -1270,7 +1287,7 @@ func isInAmbientOrTypeNode(node *ast.Node) bool { }) != nil } -func getAncestor(node *ast.Node, kind ast.Kind) *ast.Node { +func GetAncestor(node *ast.Node, kind ast.Kind) *ast.Node { for node != nil && node.Kind != kind { node = node.Parent } diff --git a/internal/compiler/program.go b/internal/compiler/program.go index cb1a6c5e3e..8faf8621df 100644 --- a/internal/compiler/program.go +++ b/internal/compiler/program.go @@ -3,6 +3,7 @@ package compiler import ( "context" "slices" + "strings" "sync" "github.com/microsoft/typescript-go/internal/ast" @@ -46,7 +47,9 @@ type Program struct { resolver *module.Resolver - comparePathsOptions tspath.ComparePathsOptions + comparePathsOptions tspath.ComparePathsOptions + supportedExtensions [][]string + supportedExtensionsWithJsonIfResolveJsonModule []string processedFiles @@ -282,6 +285,15 @@ func (p *Program) findSourceFile(candidate string, reason FileIncludeReason) *as return p.filesByPath[path] } +func (p *Program) GetResolvedModuleFromModuleSpecifier(moduleSpecifier *ast.Node /*!!! stringliterallike*/, sourceFile *ast.SourceFile) *ast.SourceFile { + if sourceFile == nil { + if sourceFile = ast.GetSourceFileOfNode(moduleSpecifier); sourceFile == nil { + panic("`moduleSpecifier` must have a `SourceFile` ancestor. Use `program.getResolvedModule` instead to provide the containing file and resolution mode.") + } + } + return p.GetResolvedModule(sourceFile, moduleSpecifier.Text()) //, getModeForUsageLocation(sourceFile, moduleSpecifier)) +} + func (p *Program) GetSyntacticDiagnostics(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic { return p.getDiagnosticsHelper(ctx, sourceFile, false /*ensureBound*/, false /*ensureChecked*/, p.getSyntacticDiagnosticsForFile) } @@ -694,6 +706,96 @@ 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 { + return ref.ResolutionMode + // TODO + // !!! || p.getDefaultResolutionModeForFile(sourceFile) +} + +/** This should have similar behavior to 'processSourceFile' without diagnostics or mutation. */ +func (p *Program) GetSourceFileFromReference(referencingFile *ast.SourceFile, ref *ast.FileReference) *ast.SourceFile { + return p.getSourceFileFromReferenceWorker(tspath.ResolveTripleslashReference(ref.FileName, referencingFile.FileName()), p.GetSourceFile, nil /*fail*/) +} + +func (p *Program) getSourceFileFromReferenceWorker( + fileName string, + getSourceFile func(fileName string) *ast.SourceFile, + fail func(diagnostic *diagnostics.Message, argument ...string), + // reason *ReferencedFile, // !!! strada uses FileIncludeReason, but this function only performs extra work for ReferencedFiles +) *ast.SourceFile { + if tspath.HasExtension(fileName) { + canonicalFileName := tspath.GetCanonicalFileName(fileName, p.host.FS().UseCaseSensitiveFileNames()) + if !p.Options().AllowNonTsExtensions.IsTrue() { + if !core.FirstNonNil(p.supportedExtensionsWithJsonIfResolveJsonModule, func(extension string) bool { + return tspath.FileExtensionIs(canonicalFileName, extension) + }) { + if fail != nil { + if tspath.HasJSFileExtension(canonicalFileName) { + fail(diagnostics.File_0_is_a_JavaScript_file_Did_you_mean_to_enable_the_allowJs_option, fileName) + } else { + fail(diagnostics.File_0_has_an_unsupported_extension_The_only_supported_extensions_are_1, fileName, "'"+strings.Join(core.Flatten(p.supportedExtensions), "', '")+"'") + } + } + return nil + } + } + + sourceFile := getSourceFile(fileName) + // todo: fail not use in inputs yet, so leaving this unimplemented for now + // if fail != nil { + // if sourceFile == nil { + // redirect := getProjectReferenceRedirect(fileName) + // if redirect { + // fail(diagnostics.Output_file_0_has_not_been_built_from_source_file_1, redirect, fileName) + // } else { + // fail(diagnostics.File_0_not_found, fileName) + // } + // } else if reason != nil && canonicalFileName == tspath.GetCanonicalFileName(p.GetSourceFileByPath(reason.File).FileName(), p.host.FS().UseCaseSensitiveFileNames()) { + // fail(diagnostics.A_file_cannot_have_a_reference_to_itself) + // } + // } + return sourceFile + } else { + if p.Options().AllowNonTsExtensions.IsTrue() { + if sourceFileNoExtension := getSourceFile(fileName); sourceFileNoExtension != nil { + return sourceFileNoExtension + } + } + + if fail != nil && p.Options().AllowNonTsExtensions.IsTrue() { + fail(diagnostics.File_0_not_found, fileName) + return nil + } + + // Only try adding extensions from the first supported group (which should be .ts/.tsx/.d.ts) + sourceFileWithAddedExtension := core.FirstNonNil(p.supportedExtensions[0], func(extension string) *ast.SourceFile { return getSourceFile(fileName + extension) }) + if fail != nil && sourceFileWithAddedExtension == nil { + fail(diagnostics.Could_not_resolve_the_path_0_with_the_extensions_Colon_1, fileName, "'"+strings.Join(core.Flatten(p.supportedExtensions), "', '")+"'") + } + return sourceFileWithAddedExtension + } +} + type FileIncludeKind int const ( diff --git a/internal/core/core.go b/internal/core/core.go index 811922c0fc..5e71c8678c 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..51ec3f227f --- /dev/null +++ b/internal/ls/findallreferences.go @@ -0,0 +1,1609 @@ +package ls + +import ( + "cmp" + "slices" + "strings" + + "github.com/microsoft/typescript-go/internal/ast" + "github.com/microsoft/typescript-go/internal/astnav" + "github.com/microsoft/typescript-go/internal/checker" + "github.com/microsoft/typescript-go/internal/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 + providePrefixAndSuffixTextForRename bool +} + +// === types for results === + +type refInfo struct { + file *ast.SourceFile + fileName string + reference *ast.FileReference + unverified bool +} + +type SymbolAndEntries struct { + definition *Definition + references []Entry +} + +func NewSymbolAndEntries(kind definitionKind, node *ast.Node, symbol *ast.Symbol, references []Entry) *SymbolAndEntries { + return &SymbolAndEntries{ + &Definition{ + Kind: kind, + node: node, + symbol: symbol, + }, + references, + } +} + +type RangeEntry struct { + kind entryKind + fileName string + textRange *lsproto.Range +} + +func (r *RangeEntry) Kind() entryKind { + return r.kind +} + +func (r *RangeEntry) TextRange() *lsproto.Range { + return r.textRange +} + +func NewRangeEntry(file *ast.SourceFile, start, end int) *RangeEntry { + return &RangeEntry{ + kind: entryKindRange, + fileName: file.FileName(), + textRange: createRangeFromPosAndSourceFile(start, end, file), + } +} + +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 + reference *tripleSlashDefinition +} +type tripleSlashDefinition struct { + reference *ast.FileReference + file *ast.SourceFile +} + +func (d *Definition) getSymbolReference() *ast.Symbol { + if d.Kind != definitionKindSymbol { + return nil + } + return d.symbol +} + +func (d *Definition) GetLabelReference() *ast.Identifier { + if d.Kind != definitionKindLabel { + return nil + } + return d.node.AsIdentifier() +} + +func (d *Definition) GetKeywordReference() *ast.Node { + if d.Kind != definitionKindKeyword { + return nil + } + return d.node +} + +func (d *Definition) GetThisReference() *ast.Node { + if d.Kind != definitionKindThis { + return nil + } + return d.node +} + +func (d *Definition) GetStringReference() *ast.Node { + // will return a StringLiteralLike or nil + if d.Kind != definitionKindString && ast.IsStringLiteralLike(d.node) { + return nil + } + return d.node +} + +func (d *Definition) GetTripleSlashReference() *tripleSlashDefinition { + if d.Kind != definitionKindTripleSlashReference { + return nil + } + return d.reference +} + +type entryKind int + +const ( + entryKindNone entryKind = 0 + entryKindRange entryKind = 1 + entryKindNode entryKind = 2 + entryKindStringLiteral entryKind = 3 + entryKindSearchedLocalFoundProperty entryKind = 4 + entryKindSearchedPropertyFoundLocal entryKind = 5 +) + +func GetNodeEntry(e Entry) *NodeEntry { + if e.Kind() == entryKindRange { + return nil + } + return e.(*NodeEntry) +} + +func GetRangeEntry(e Entry) *RangeEntry { + if e.Kind() == entryKindRange { + return e.(*RangeEntry) + } + return nil +} + +type Entry interface { + Kind() entryKind + TextRange() *lsproto.Range +} + +type NodeEntry struct { + kind entryKind + node *ast.Node + context *ast.Node // !!! ContextWithStartAndEndNode, optional +} + +func (entry *NodeEntry) Kind() entryKind { + return entry.kind +} + +func (entry *NodeEntry) TextRange() *lsproto.Range { + return getRangeOfNode(entry.node, nil, nil) +} + +func NewNodeEntryWithKind(node *ast.Node, kind entryKind) *NodeEntry { + e := NewNodeEntry(node) + e.kind = kind + return e +} + +func NewNodeEntry(node *ast.Node) *NodeEntry { + // creates nodeEntry with `kind == entryKindNode` + n := node + if node != nil && node.Name() != nil { + n = node.Name() + } + return &NodeEntry{ + 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 { + // Special property assignment in javascript + if ast.IsInJSFile(node) { + 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) != ast.AssignmentDeclarationKindNone { + 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) + }) + return core.IfElse(ast.IsDeclaration(declOrStatement), + getContextNode(declOrStatement), + declOrStatement) + } + } + + // Handle computed property name + propertyName := ast.FindAncestor(node, ast.IsComputedPropertyName) + return core.IfElse(propertyName != nil, + getContextNode(propertyName.Parent), + nil) + } + + if node.Parent.Name() == node || // node is name of declaration, use parent + node.Parent.Kind == ast.KindConstructor || + node.Parent.Kind == ast.KindExportAssignment || + // 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: + // TODO ContextWithStartAndEndNode + // return { + // start: (node as ForInOrOfStatement).initializer, + // end: (node as ForInOrOfStatement).expression, + // }; + 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: + // TODO ContextWithStartAndEndNode + // return { + // start: find(node.getChildren(node.getSourceFile()), node => node.Kind == ast.KindSwitchKeyword)!, + // end: (node as SwitchStatement).caseBlock, + // }; + return nil + default: + return node + } +} + +// utils? +func 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 createRangeFromPosAndSourceFile(start, end, sourceFile) +} + +func createRangeFromPosAndSourceFile(start, end int, sourceFile *ast.SourceFile) *lsproto.Range { + return &lsproto.Range{ + Start: getPositionFromIntAndSourceFile(start, sourceFile), + End: getPositionFromIntAndSourceFile(end, sourceFile), + } +} + +func getPositionFromIntAndSourceFile(pos int, sourceFile *ast.SourceFile) lsproto.Position { + panic("positionFromIntAndSourceFile not implemented") + // l, c:= scanner.GetLineAndCharacterOfPosition(sourceFile, pos) + // return lsproto.Position{Line: l, Character: c} +} + +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) || + (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.providePrefixAndSuffixTextForRename +} + +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. + // const { declarations, flags, parent, valueDeclaration } = symbol; + 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 checker.GetAncestor(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 scope.Kind == ast.KindFunctionExpression { + for next := getNextJSDocCommentLocation(scope); next != nil; next = getNextJSDocCommentLocation(scope) { + scope = next + } + } + } + + // 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( + fileName string, + position int, + context *lsproto.ReferenceContext, +) []*lsproto.Location { + // does findReferencedSymbols except only computes the conversions needed for reference locations + program, sourceFile := l.getProgramAndFile(fileName) + + node := astnav.GetTouchingPropertyName(sourceFile, position) + options := refOptions{use: referenceUseReferences} + + references := getReferencedSymbolsForNode(position, node, program, program.GetSourceFiles(), options, nil) + + return convertReferencesToLocations(references, sourceFile, program, context) + // checker := program.getTypeChecker(); + // // Unless the starting node is a declaration (vs e.g. JSDoc), don't attempt to compute isDefinition + // adjustedNode := getAdjustedNode(node, options) + // var symbol *ast.Symbol + // if isDefinitionForReference(adjustedNode) { + // symbol = isDefinitionForReference(adjustedNode) + // } + + // references = core.Map(s.references, func (r Entry) {toReferencedSymbolEntry(r, symbol)}) + + // for each element in references + // referenceEntryToReferencesResponseItem(projectService: ProjectService, { fileName, textSpan, contextSpan, isWriteAccess, isDefinition }: ReferencedSymbolEntry, { disableLineTextInReferences }: protocol.UserPreferences): protocol.ReferencesResponseItem { + // const scriptInfo = Debug.checkDefined(projectService.getScriptInfo(fileName)); + // const span = toProtocolTextSpanWithContext(textSpan, contextSpan, scriptInfo); + // const lineText = disableLineTextInReferences ? undefined : getLineText(scriptInfo, span); + // return { + // file: fileName, + // ...span, + // lineText, + // isWriteAccess, + // isDefinition, + // }; + // } +} + +// == functions for converting == + +func convertReferencesToLocations(references []*SymbolAndEntries, sourceFile *ast.SourceFile, program *compiler.Program, context *lsproto.ReferenceContext) []*lsproto.Location { + panic("unimplemented") +} + +func mergeReferences(program *compiler.Program, referencesToMerge ...[]*SymbolAndEntries) []*SymbolAndEntries { + result := []*SymbolAndEntries{} + getSourceFileIndexOfEntry := func(program *compiler.Program, entry Entry) int { + var sourceFile *ast.SourceFile + if rangeEntry := GetRangeEntry(entry); rangeEntry != nil { + sourceFile = program.GetSourceFile(rangeEntry.fileName) + } else { + sourceFile = ast.GetSourceFileOfNode(GetNodeEntry(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.getSymbolReference() == symbol + }) + if refIndex == -1 { + result = append(result, entry) + continue + } + + reference := result[refIndex] + sortedRefs := append(reference.references, entry.references...) + slices.SortStableFunc(sortedRefs, func(entry1, entry2 Entry) int { + entry1File := getSourceFileIndexOfEntry(program, entry1) + entry2File := getSourceFileIndexOfEntry(program, entry2) + if entry1File != entry2File { + return cmp.Compare(entry1File, entry2File) + } + + return CompareRanges(entry1.TextRange(), entry2.TextRange()) + }) + result[refIndex] = &SymbolAndEntries{ + definition: reference.definition, + references: sortedRefs, + } + } + } + return result +} + +// p.Compare(other) == cmp.Compare(p, other) +func ComparePositions(p, other lsproto.Position) int { + if lineComp := cmp.Compare(p.Line, other.Line); lineComp != 0 { + return lineComp + } + return cmp.Compare(p.Line, other.Line) +} + +// t.Compare(other) == cmp.Compare(t, other) +// +// compares Range.Start and then Range.End +func CompareRanges(t, other *lsproto.Range) int { + if startComp := ComparePositions(t.Start, other.Start); startComp != 0 { + return startComp + } + return ComparePositions(t.End, other.End) +} + +// === functions for find all ref implementation === + +func 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 { + // !!! not implemented + resolvedRef := getReferenceAtPosition(node.AsSourceFile(), position, program) + if resolvedRef.file == nil { + return nil + } + + if moduleSymbol := program.GetTypeChecker().GetMergedSymbol(resolvedRef.file.Symbol); moduleSymbol != nil { + return getReferencedSymbolsForModule(program, moduleSymbol /*excludeImportTypeOfExportEquals*/, false, sourceFiles, sourceFilesSet) + } + + // fileIncludeReasons := program.getFileIncludeReasons(); + // if (!fileIncludeReasons) { + // return nil + // } + return []*SymbolAndEntries{{ + definition: &Definition{Kind: definitionKindTripleSlashReference, reference: &tripleSlashDefinition{reference: resolvedRef.reference}}, + references: getReferencesForNonModule(resolvedRef.file, program /*fileIncludeReasons,*/), + }} + } + + if !options.implementations { + // !!! cancellationToken + if special := getReferencedSymbolsSpecial(node, sourceFiles); special != nil { + return special + } + } + + checker := program.GetTypeChecker() + // 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) { + // !!! 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 := getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol, program, sourceFiles, options, sourceFilesSet) // !!! cancellationToken + if moduleReferences != nil && symbol.Flags&ast.SymbolFlagsTransient != 0 { + return moduleReferences + } + + aliasedSymbol := getMergedAliasedSymbolOfNamespaceExportDeclaration(node, symbol, checker) + moduleReferencesOfExportTarget := getReferencedSymbolsForModuleIfDeclaredBySourceFile(aliasedSymbol, program, sourceFiles, options, sourceFilesSet) // !!! cancellationToken + + references := getReferencedSymbolsForSymbol(symbol, node, sourceFiles, sourceFilesSet, checker, options) // !!! cancellationToken + return mergeReferences(program, moduleReferences, references, moduleReferencesOfExportTarget) +} + +func 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, 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 := program.GetTypeChecker() + symbol, _ = checker.ResolveAlias(exportEquals) + return 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: []Entry{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) Entry { + // 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 + } + + // !!! check fall throughs + switch searchSpaceNode.Kind { + case ast.KindMethodDeclaration, ast.KindMethodSignature: + if ast.IsObjectLiteralMethod(searchSpaceNode) { + staticFlag &= searchSpaceNode.ModifierFlags() + searchSpaceNode = searchSpaceNode.Parent // re-assign to be the owning object literals + break + } + // falls through + case ast.KindPropertyDeclaration, ast.KindPropertySignature, ast.KindConstructor, ast.KindGetAccessor, ast.KindSetAccessor: + 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 + } + // falls through + 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) Entry { return NewNodeEntry(n) }, + ) + + thisParameter := core.FirstNonNil(references, func(ref Entry) *ast.Node { + if ref.(*NodeEntry).node.Parent.Kind == ast.KindParameter { + return ref.(*NodeEntry).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) Entry { + 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) []Entry { + // cancellationToken.throwIfCancellationRequested(); + return core.MapNonNil(getPossibleSymbolReferenceNodes(sourceFile, scanner.TokenToString(keywordKind), sourceFile.AsNode()), func(referenceLocation *ast.Node) Entry { + if referenceLocation.Kind == keywordKind && (!filterReadOnlyTypeOperator || isReadonlyTypeOperator(referenceLocation)) { + return NewNodeEntry(referenceLocation) + } + return nil + }) + }) + if len(references) == 0 { + return nil + } + return []*SymbolAndEntries{NewSymbolAndEntries(definitionKindKeyword, references[0].(*NodeEntry).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.AsSourceFile() != sourceFile { // todo check if this is the right way to check for equality of nodes + 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) + } + position = strings.Index(text[position+symbolNameLength+1:], symbolName) + } + + return positions +} + +func getReferencesForNonModule(referencedFile *ast.SourceFile, program *compiler.Program) []Entry { + // !!! not implemented + return []Entry{} +} + +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(node, sourceFile); resolution != nil { + verifiedFileName := resolution.FileName() + fileName := verifiedFileName + 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.providePrefixAndSuffixTextForRename, !!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 seenTracker[T comparable] struct { + m core.Set[T] +} + +func (t *seenTracker[T]) mark(key T) bool { + if t.m.Has(key) { + return true + } + t.m.Add(key) + return false +} + +func (t *seenTracker[T]) addToSeen(key T) bool { + if t.m.Has(key) { + return false + } + t.m.Add(key) + return true +} + +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 seenTracker[*ast.Node] // node seen tracker + // seenReExportRHS seenTracker[*ast.Node] // node seen tracker + // importTracker ImportTracker + symbolIdToReferences [][]Entry + 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: seenTracker[*ast.Node]{}, + // seenReExportRHS: seenTracker[*ast.Node]{}, + symbolIdToReferences: [][]Entry{}, + 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). + + // TODO + // if text == "" { + // text = stripQuotes(symbolName(getLocalSymbolForExportDefault(symbol) || getNonModuleSymbolOfMergedModuleSymbol(symbol) || symbol)) + // } + // escapedText := escapeLeadingUnderscores(text); + 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) { + // todo: rename to getReferenceAdder + symbolId := ast.GetSymbolId(searchSymbol) + references := state.symbolIdToReferences[symbolId] + if references == nil { + references = []Entry{} + state.symbolIdToReferences[symbolId] = references + state.result = append(state.result, NewSymbolAndEntries(definitionKindSymbol, nil, searchSymbol, references)) + } + return func(node *ast.Node, kind entryKind) { + references = append(references, NewNodeEntryWithKind(node, kind)) + } +} + +func (state *refState) addReference(referenceLocation *ast.Node, symbol *ast.Symbol, kind entryKind) { + // { kind, symbol } = "kind" in relatedSymbol ? relatedSymbol : { kind: undefined, symbol: relatedSymbol }; // eslint-disable-line local/no-in-operator + + // 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 + // TODO + // 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.mark(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. + + // TODO + // 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 + } + + // TODO + // if isJSDocPropertyLikeTag(parent) && parent.isNameFirst && + // parent.TypeExpression && isJSDocTypeLiteral(parent.TypeExpression.Type()) && + // parent.TypeExpression.Type().jsDocPropertyTags && length(parent.TypeExpression.Type().jsDocPropertyTags) { + // getReferencesAtJSDocTypeLiteral(parent.TypeExpression.Type().jsDocPropertyTags, referenceLocation, search, state); + // 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) { + // !!! when findAllReferences has been fully implemented, check if the behavior is the same since isVariableDeclarationInitializedToBareOrAccessedRequire has been removed + 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{} + // TODO + 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 + } + } + if base != nil { + result = append(result, base) + } else if root != nil { + result = append(result, root) + } else { + result = append(result, 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.providePrefixAndSuffixTextForRename, /*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/utilities.go b/internal/ls/utilities.go index 2f159d5639..07c4887a35 100644 --- a/internal/ls/utilities.go +++ b/internal/ls/utilities.go @@ -1,6 +1,7 @@ package ls import ( + "slices" "strings" "github.com/microsoft/typescript-go/internal/ast" @@ -18,6 +19,31 @@ func isInString(file *ast.SourceFile, position int, previousToken *ast.Node) boo return false } +func skipPastExportOrImportSpecifierOrUnion(originalSymbol *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(), originalSymbol, 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(originalSymbol.Declarations, func(decl *ast.Node) *ast.Symbol { + if decl.Parent == nil { + // Ignore UMD module and global merge + if originalSymbol.Flags&ast.SymbolFlagsTransient != 0 { + return nil + } + // Assertions for GH#21814. We should be handling SourceFile symbols in `getReferencedSymbolsForModule` instead of getting here. + panic(`Unexpected symbol at ${Debug.formatast.Kind(node.Kind)}: ${Debug.formatSymbol(symbol)}`) + } + if decl.Parent.Kind == ast.KindTypeLiteral && decl.Parent.Parent.Kind == ast.KindUnionType { + return checker.GetPropertyOfType(checker.GetTypeFromTypeNode(decl.Parent.Parent), originalSymbol.Name) + } + return nil + }) +} + func tryGetImportFromModuleSpecifier(node *ast.StringLiteralLike) *ast.Node { switch node.Parent.Kind { case ast.KindImportDeclaration, ast.KindJSImportDeclaration, ast.KindExportDeclaration: @@ -41,6 +67,47 @@ func tryGetImportFromModuleSpecifier(node *ast.StringLiteralLike) *ast.Node { return nil } +func isModuleSpecifierLike(node *ast.Node) bool { + // return node.Kind == ast.KindStringLiteral || node.Kind == ast.KindNoSubstitutionTemplateLiteral || + // node.Kind == ast.KindTemplateHead || node.Kind == ast.KindTemplateMiddle || node.Kind == ast.KindTemplateTail + if !ast.IsStringLiteralLike(node) { + return false + } + + if ast.IsRequireCall(node.Parent /*requireStringLiteralLikeArgument*/) || ast.IsImportCall(node.Parent) { + return node.Parent.AsCallExpression().Arguments.Nodes[0] == node + } + + return node.Parent.Kind == ast.KindExternalModuleReference || + node.Parent.Kind == ast.KindImportDeclaration || + node.Parent.Kind == ast.KindJSDocImportTag +} + +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 + } +} + // !!! func isInComment(file *ast.SourceFile, position int, tokenAtPosition *ast.Node) *ast.CommentRange { return nil @@ -255,6 +322,92 @@ func getPossibleGenericSignatures(called *ast.Expression, typeArgumentCount int, 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) && checker.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 checker.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 @@ -394,6 +547,36 @@ func nodeIsASICandidate(node *ast.Node, file *ast.SourceFile) bool { return startLine != endLine } +func getNextJSDocCommentLocation(node *ast.Node) *ast.Node { + parent := node.Parent + if parent.Kind == ast.KindPropertyAssignment || + parent.Kind == ast.KindExportAssignment || + parent.Kind == ast.KindPropertyDeclaration || + parent.Kind == ast.KindExpressionStatement && node.Kind == ast.KindPropertyAccessExpression || + parent.Kind == ast.KindReturnStatement || + getNestedModuleDeclaration(parent) != nil || + ast.IsAssignmentExpression(node, false) { + return parent + } + if parent.Parent != nil { + // Try to recognize this pattern when node is initializer of variable declaration and JSDoc comments are on containing variable statement. + // /** + // * @param {number} name + // * @returns {number} + // */ + // var x = function(name) { return name.length; } + if checker.GetSingleVariableOfVariableStatement(parent.Parent) == node || ast.IsAssignmentExpression(parent, false) { + return parent.Parent + } + if parent.Parent.Parent != nil && (checker.GetSingleVariableOfVariableStatement(parent.Parent.Parent) != nil || + getSingleInitializerOfVariableStatementOrPropertyDeclaration(parent.Parent.Parent) == node || + getSourceOfDefaultedAssignment(parent.Parent.Parent) != nil) { + return parent.Parent.Parent + } + } + return nil +} + func isNonContextualKeyword(token ast.Kind) bool { return ast.IsKeywordKind(token) && !ast.IsContextualKeyword(token) } @@ -521,12 +704,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 } @@ -714,6 +1006,512 @@ 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 { + // if (isJSDocTypeAlias(node)) { + // // This doesn't just apply to the node immediately under the comment, but to everything in its parent's scope. + // // node.Parent = the JSDoc comment, node.Parent.Parent = the node having the comment. + // // Then we get parent again in the loop. + // node = node.Parent.Parent; + // } + + for { + if node = node.Parent; node == nil { + return nil + } + switch node.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 node + } + } +} + +func getAdjustedLocation(node *ast.Node, forRename bool, sourceFile *ast.SourceFile) *ast.Node { + 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 { + 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 { + // todo jsdoc not implemented + // if !ast.IsJSDocTemplateTag(parent.Parent) { + // panic("should be handled by isDeclarationName") + // } + 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: + // if ast.IsInJSFile(node) { // !!! && ast.GetJSDocEnumTag(node) { + // return ast.SemanticMeaningAll + // } + return ast.SemanticMeaningValue + + case 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.KindTypeLiteral: + return ast.SemanticMeaningType + + case ast.KindJSDocTypedefTag: + // If it has no name node, it shares the name with the value declaration below it. + if node.Name() == nil { + return ast.SemanticMeaningValue | ast.SemanticMeaningType + } + 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.KindExportAssignment, ast.KindExportDeclaration: + return ast.SemanticMeaningAll + + // An external module can be a Value + case ast.KindSourceFile: + return ast.SemanticMeaningNamespace | ast.SemanticMeaningValue + } + + return ast.SemanticMeaningAll +} + +func getIntersectingMeaningFromDeclarations(node *ast.Node, symbol *ast.Symbol, defaultMeaning ast.SemanticMeaning) ast.SemanticMeaning { + if node == nil { + return defaultMeaning + } + + meaning := getMeaningFromLocation(node) + declarations := symbol.Declarations + if len(declarations) == 0 { + return meaning + } + + lastIterationMeaning := meaning + + // !!! TODO check if the port is correct and the for loop is needed + iteration := func(m ast.SemanticMeaning) ast.SemanticMeaning { + for _, declaration := range declarations { + declarationMeaning := getMeaningFromDeclaration(declaration) + + if declarationMeaning&m != 0 { + m |= declarationMeaning + } + } + return m + } + meaning = iteration(meaning) + + for meaning != lastIterationMeaning { + // The result is order-sensitive, for instance if initialMeaning == Namespace, and declarations = [class, instantiated module] + // we need to consider both as they 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 +} + +func getNestedModuleDeclaration(node *ast.Node) *ast.Node { + if ast.IsModuleDeclaration(node) && node.Body() != nil && node.Body().Kind == ast.KindModuleDeclaration { + return node.Body() + } + return nil +} + +func getSingleInitializerOfVariableStatementOrPropertyDeclaration(node *ast.Node) *ast.Expression { + switch node.Kind { + case ast.KindVariableStatement: + if v := checker.GetSingleVariableOfVariableStatement(node); v != nil { + return v.Initializer() + } + case ast.KindPropertyDeclaration, ast.KindPropertyAssignment: + return node.Initializer() + } + return nil +} + // 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) { @@ -727,3 +1525,104 @@ 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 + } + + res := core.MapNonNil( + core.IfElse(lhsType.Flags() != 0, lhsType.Types(), core.IfElse(lhsType.Symbol() == symbol.Parent, nil, []*checker.Type{lhsType})), + func(t *checker.Type) *ast.Symbol { + return core.IfElse(t.Symbol() != nil && t.Symbol().Flags&(ast.SymbolFlagsClass|ast.SymbolFlagsInterface) != 0, t.Symbol(), nil) + }, + ) + if len(res) == 0 { + return nil + } + return res +} + +/** +* 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 result an array of symbol of found property symbols +* @param previousIterationSymbolsCache 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 := seenTracker[*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.addToSeen(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 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 getSourceOfDefaultedAssignment(node *ast.Node) *ast.Node { + if node.Kind == ast.KindExpressionStatement && node.Expression().Kind == ast.KindBinaryExpression && ast.GetAssignmentDeclarationKind(node.Expression()) != ast.AssignmentDeclarationKindNone { + binExprRight := node.Expression().AsBinaryExpression().Right + if binExprRight.Kind == ast.KindBinaryExpression { + binExprDefault := binExprRight.AsBinaryExpression() + switch binExprDefault.OperatorToken.Kind { + case ast.KindBarBarToken, ast.KindQuestionQuestionToken: + return binExprDefault.Right + } + } + } + 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 +} diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 2931f40178..2ff9158560 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -297,6 +297,8 @@ func (s *Server) handleMessage(req *lsproto.RequestMessage) error { return s.handleDefinition(req) case *lsproto.CompletionParams: return s.handleCompletion(req) + case *lsproto.ReferenceParams: + return s.handleReferences(req) default: switch req.Method { case lsproto.MethodShutdown: @@ -494,6 +496,19 @@ func (s *Server) handleDefinition(req *lsproto.RequestMessage) error { return s.sendResult(req.ID, &lsproto.Definition{Locations: &lspLocations}) } +func (s *Server) handleReferences(req *lsproto.RequestMessage) error { + // findAllReferences + params := req.Params.(*lsproto.ReferenceParams) + file, project := s.getFileAndProject(params.TextDocument.Uri) + pos, err := s.converters.LineAndCharacterToPositionForFile(params.Position, file.FileName()) + if err != nil { + return s.sendError(req.ID, err) + } + locations := project.LanguageService().ProvideReferences(file.FileName(), pos, params.Context) + + return s.sendResult(req.ID, locations) +} + func (s *Server) handleCompletion(req *lsproto.RequestMessage) (messageErr error) { params := req.Params.(*lsproto.CompletionParams) file, project := s.getFileAndProject(params.TextDocument.Uri) diff --git a/internal/tspath/path.go b/internal/tspath/path.go index 9b5b41492b..3e971ef14f 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)) } From 95937fb5176c1d2eab2b507e2640ab048b22812d Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Mon, 19 May 2025 22:36:33 -0700 Subject: [PATCH 02/13] tests, some bug fixes, cleanup --- internal/binder/nameresolver.go | 4 +- internal/checker/services.go | 1 + internal/ls/findallreferences.go | 343 +++++++------------- internal/ls/findallreferences_test.go | 271 ++++++++++++++++ internal/ls/findallreferencesexport_test.go | 18 + internal/ls/utilities.go | 36 +- internal/lsp/server.go | 3 + 7 files changed, 439 insertions(+), 237 deletions(-) create mode 100644 internal/ls/findallreferences_test.go create mode 100644 internal/ls/findallreferencesexport_test.go diff --git a/internal/binder/nameresolver.go b/internal/binder/nameresolver.go index b356da2218..569b407cc9 100644 --- a/internal/binder/nameresolver.go +++ b/internal/binder/nameresolver.go @@ -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,7 +448,7 @@ func (r *NameResolver) argumentsSymbol() *ast.Symbol { return r.ArgumentsSymbol } -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/services.go b/internal/checker/services.go index 05c8ae253f..30ed0ebbac 100644 --- a/internal/checker/services.go +++ b/internal/checker/services.go @@ -341,6 +341,7 @@ func (c *Checker) GetRootSymbols(symbol *ast.Symbol) []*ast.Symbol { for _, root := range roots { result = append(result, c.GetRootSymbols(root)...) } + return result } return []*ast.Symbol{symbol} } diff --git a/internal/ls/findallreferences.go b/internal/ls/findallreferences.go index 51ec3f227f..03f85220d1 100644 --- a/internal/ls/findallreferences.go +++ b/internal/ls/findallreferences.go @@ -7,6 +7,7 @@ import ( "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" @@ -44,10 +45,10 @@ type refInfo struct { type SymbolAndEntries struct { definition *Definition - references []Entry + references []*Entry } -func NewSymbolAndEntries(kind definitionKind, node *ast.Node, symbol *ast.Symbol, references []Entry) *SymbolAndEntries { +func NewSymbolAndEntries(kind definitionKind, node *ast.Node, symbol *ast.Symbol, references []*Entry) *SymbolAndEntries { return &SymbolAndEntries{ &Definition{ Kind: kind, @@ -58,28 +59,6 @@ func NewSymbolAndEntries(kind definitionKind, node *ast.Node, symbol *ast.Symbol } } -type RangeEntry struct { - kind entryKind - fileName string - textRange *lsproto.Range -} - -func (r *RangeEntry) Kind() entryKind { - return r.kind -} - -func (r *RangeEntry) TextRange() *lsproto.Range { - return r.textRange -} - -func NewRangeEntry(file *ast.SourceFile, start, end int) *RangeEntry { - return &RangeEntry{ - kind: entryKindRange, - fileName: file.FileName(), - textRange: createRangeFromPosAndSourceFile(start, end, file), - } -} - type definitionKind int const ( @@ -92,59 +71,16 @@ const ( ) type Definition struct { - Kind definitionKind - symbol *ast.Symbol - node *ast.Node - reference *tripleSlashDefinition + Kind definitionKind + symbol *ast.Symbol + node *ast.Node + tripleSlashFileRef *tripleSlashDefinition } type tripleSlashDefinition struct { reference *ast.FileReference file *ast.SourceFile } -func (d *Definition) getSymbolReference() *ast.Symbol { - if d.Kind != definitionKindSymbol { - return nil - } - return d.symbol -} - -func (d *Definition) GetLabelReference() *ast.Identifier { - if d.Kind != definitionKindLabel { - return nil - } - return d.node.AsIdentifier() -} - -func (d *Definition) GetKeywordReference() *ast.Node { - if d.Kind != definitionKindKeyword { - return nil - } - return d.node -} - -func (d *Definition) GetThisReference() *ast.Node { - if d.Kind != definitionKindThis { - return nil - } - return d.node -} - -func (d *Definition) GetStringReference() *ast.Node { - // will return a StringLiteralLike or nil - if d.Kind != definitionKindString && ast.IsStringLiteralLike(d.node) { - return nil - } - return d.node -} - -func (d *Definition) GetTripleSlashReference() *tripleSlashDefinition { - if d.Kind != definitionKindTripleSlashReference { - return nil - } - return d.reference -} - type entryKind int const ( @@ -156,52 +92,42 @@ const ( entryKindSearchedPropertyFoundLocal entryKind = 5 ) -func GetNodeEntry(e Entry) *NodeEntry { - if e.Kind() == entryKindRange { - return nil - } - return e.(*NodeEntry) +type Entry struct { + kind entryKind + node *ast.Node + context *ast.Node // !!! ContextWithStartAndEndNode, optional + fileName string + textRange *lsproto.Range } -func GetRangeEntry(e Entry) *RangeEntry { - if e.Kind() == entryKindRange { - return e.(*RangeEntry) +func (l *LanguageService) getRangeOfEntry(entry *Entry) *lsproto.Range { + if entry.textRange == nil { + entry.textRange = l.getRangeOfNode(entry.node, nil, nil) } - return nil + return entry.textRange } -type Entry interface { - Kind() entryKind - TextRange() *lsproto.Range -} - -type NodeEntry struct { - kind entryKind - node *ast.Node - context *ast.Node // !!! ContextWithStartAndEndNode, optional -} - -func (entry *NodeEntry) Kind() entryKind { - return entry.kind -} - -func (entry *NodeEntry) TextRange() *lsproto.Range { - return getRangeOfNode(entry.node, nil, nil) +func (l *LanguageService) NewRangeEntry(file *ast.SourceFile, start, end int) *Entry { + return &Entry{ + kind: entryKindRange, + fileName: file.FileName(), + textRange: l.createLspRangeFromBounds(start, end, file), + } } -func NewNodeEntryWithKind(node *ast.Node, kind entryKind) *NodeEntry { +func NewNodeEntryWithKind(node *ast.Node, kind entryKind) *Entry { e := NewNodeEntry(node) e.kind = kind return e } -func NewNodeEntry(node *ast.Node) *NodeEntry { +func NewNodeEntry(node *ast.Node) *Entry { // creates nodeEntry with `kind == entryKindNode` n := node if node != nil && node.Name() != nil { n = node.Name() } - return &NodeEntry{ + return &Entry{ kind: entryKindNode, node: node, context: getContextNodeForNodeEntry(n), @@ -251,9 +177,10 @@ func getContextNodeForNodeEntry(node *ast.Node) *ast.Node { // Handle computed property name propertyName := ast.FindAncestor(node, ast.IsComputedPropertyName) - return core.IfElse(propertyName != nil, - getContextNode(propertyName.Parent), - nil) + if propertyName != nil { + return getContextNode(propertyName.Parent) + } + return nil } if node.Parent.Name() == node || // node is name of declaration, use parent @@ -300,11 +227,7 @@ func getContextNode(node *ast.Node) *ast.Node { return core.IfElse(node.Parent.Kind == ast.KindExpressionStatement, node.Parent, node) case ast.KindForOfStatement, ast.KindForInStatement: - // TODO ContextWithStartAndEndNode - // return { - // start: (node as ForInOrOfStatement).initializer, - // end: (node as ForInOrOfStatement).expression, - // }; + // !!! not implemented return nil case ast.KindPropertyAssignment, ast.KindShorthandPropertyAssignment: @@ -315,19 +238,15 @@ func getContextNode(node *ast.Node) *ast.Node { } return node case ast.KindSwitchStatement: - // TODO ContextWithStartAndEndNode - // return { - // start: find(node.getChildren(node.getSourceFile()), node => node.Kind == ast.KindSwitchKeyword)!, - // end: (node as SwitchStatement).caseBlock, - // }; + // !!! not implemented return nil default: return node } } -// utils? -func getRangeOfNode(node *ast.Node, sourceFile *ast.SourceFile, endNode *ast.Node) *lsproto.Range { +// utils +func (l *LanguageService) getRangeOfNode(node *ast.Node, sourceFile *ast.SourceFile, endNode *ast.Node) *lsproto.Range { if sourceFile == nil { sourceFile = ast.GetSourceFileOfNode(node) } @@ -343,20 +262,7 @@ func getRangeOfNode(node *ast.Node, sourceFile *ast.SourceFile, endNode *ast.Nod if endNode != nil && endNode.Kind == ast.KindCaseBlock { end = endNode.Pos() } - return createRangeFromPosAndSourceFile(start, end, sourceFile) -} - -func createRangeFromPosAndSourceFile(start, end int, sourceFile *ast.SourceFile) *lsproto.Range { - return &lsproto.Range{ - Start: getPositionFromIntAndSourceFile(start, sourceFile), - End: getPositionFromIntAndSourceFile(end, sourceFile), - } -} - -func getPositionFromIntAndSourceFile(pos int, sourceFile *ast.SourceFile) lsproto.Position { - panic("positionFromIntAndSourceFile not implemented") - // l, c:= scanner.GetLineAndCharacterOfPosition(sourceFile, pos) - // return lsproto.Position{Line: l, Character: c} + return l.createLspRangeFromBounds(start, end, sourceFile) } func isValidReferencePosition(node *ast.Node, searchSymbolName string) bool { @@ -390,7 +296,6 @@ func isForRenameWithPrefixAndSuffixText(options refOptions) bool { 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. - // const { declarations, flags, parent, valueDeclaration } = symbol; valueDeclaration := symbol.ValueDeclaration if valueDeclaration != nil && (valueDeclaration.Kind == ast.KindFunctionExpression || valueDeclaration.Kind == ast.KindClassExpression) { return valueDeclaration @@ -477,48 +382,36 @@ func (l *LanguageService) ProvideReferences( node := astnav.GetTouchingPropertyName(sourceFile, position) options := refOptions{use: referenceUseReferences} - references := getReferencedSymbolsForNode(position, node, program, program.GetSourceFiles(), options, nil) + symbolsAndEntries := l.getReferencedSymbolsForNode(position, node, program, program.GetSourceFiles(), options, nil) - return convertReferencesToLocations(references, sourceFile, program, context) - // checker := program.getTypeChecker(); - // // Unless the starting node is a declaration (vs e.g. JSDoc), don't attempt to compute isDefinition - // adjustedNode := getAdjustedNode(node, options) - // var symbol *ast.Symbol - // if isDefinitionForReference(adjustedNode) { - // symbol = isDefinitionForReference(adjustedNode) - // } - - // references = core.Map(s.references, func (r Entry) {toReferencedSymbolEntry(r, symbol)}) - - // for each element in references - // referenceEntryToReferencesResponseItem(projectService: ProjectService, { fileName, textSpan, contextSpan, isWriteAccess, isDefinition }: ReferencedSymbolEntry, { disableLineTextInReferences }: protocol.UserPreferences): protocol.ReferencesResponseItem { - // const scriptInfo = Debug.checkDefined(projectService.getScriptInfo(fileName)); - // const span = toProtocolTextSpanWithContext(textSpan, contextSpan, scriptInfo); - // const lineText = disableLineTextInReferences ? undefined : getLineText(scriptInfo, span); - // return { - // file: fileName, - // ...span, - // lineText, - // isWriteAccess, - // isDefinition, - // }; - // } + return core.FlatMap(symbolsAndEntries, l.convertSymbolAndEntryToLocation) } -// == functions for converting == - -func convertReferencesToLocations(references []*SymbolAndEntries, sourceFile *ast.SourceFile, program *compiler.Program, context *lsproto.ReferenceContext) []*lsproto.Location { - panic("unimplemented") +// == 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 mergeReferences(program *compiler.Program, referencesToMerge ...[]*SymbolAndEntries) []*SymbolAndEntries { +func (l *LanguageService) mergeReferences(program *compiler.Program, referencesToMerge ...[]*SymbolAndEntries) []*SymbolAndEntries { result := []*SymbolAndEntries{} - getSourceFileIndexOfEntry := func(program *compiler.Program, entry Entry) int { + getSourceFileIndexOfEntry := func(program *compiler.Program, entry *Entry) int { var sourceFile *ast.SourceFile - if rangeEntry := GetRangeEntry(entry); rangeEntry != nil { - sourceFile = program.GetSourceFile(rangeEntry.fileName) + if entry.kind == entryKindRange { + sourceFile = program.GetSourceFile(entry.fileName) } else { - sourceFile = ast.GetSourceFileOfNode(GetNodeEntry(entry).node) + sourceFile = ast.GetSourceFileOfNode(entry.node) } return slices.Index(program.SourceFiles(), sourceFile) } @@ -540,7 +433,7 @@ func mergeReferences(program *compiler.Program, referencesToMerge ...[]*SymbolAn refIndex := core.FindIndex(result, func(ref *SymbolAndEntries) bool { return ref.definition != nil && ref.definition.Kind == definitionKindSymbol && - ref.definition.getSymbolReference() == symbol + ref.definition.symbol == symbol }) if refIndex == -1 { result = append(result, entry) @@ -549,14 +442,14 @@ func mergeReferences(program *compiler.Program, referencesToMerge ...[]*SymbolAn reference := result[refIndex] sortedRefs := append(reference.references, entry.references...) - slices.SortStableFunc(sortedRefs, func(entry1, entry2 Entry) int { + slices.SortStableFunc(sortedRefs, func(entry1, entry2 *Entry) int { entry1File := getSourceFileIndexOfEntry(program, entry1) entry2File := getSourceFileIndexOfEntry(program, entry2) if entry1File != entry2File { return cmp.Compare(entry1File, entry2File) } - return CompareRanges(entry1.TextRange(), entry2.TextRange()) + return CompareRanges(l.getRangeOfEntry(entry1), l.getRangeOfEntry(entry2)) }) result[refIndex] = &SymbolAndEntries{ definition: reference.definition, @@ -567,27 +460,9 @@ func mergeReferences(program *compiler.Program, referencesToMerge ...[]*SymbolAn return result } -// p.Compare(other) == cmp.Compare(p, other) -func ComparePositions(p, other lsproto.Position) int { - if lineComp := cmp.Compare(p.Line, other.Line); lineComp != 0 { - return lineComp - } - return cmp.Compare(p.Line, other.Line) -} - -// t.Compare(other) == cmp.Compare(t, other) -// -// compares Range.Start and then Range.End -func CompareRanges(t, other *lsproto.Range) int { - if startComp := ComparePositions(t.Start, other.Start); startComp != 0 { - return startComp - } - return ComparePositions(t.End, other.End) -} - // === functions for find all ref implementation === -func getReferencedSymbolsForNode(position int, node *ast.Node, program *compiler.Program, sourceFiles []*ast.SourceFile, options refOptions, sourceFilesSet *core.Set[string]) []*SymbolAndEntries { +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)) @@ -612,7 +487,7 @@ func getReferencedSymbolsForNode(position int, node *ast.Node, program *compiler // return nil // } return []*SymbolAndEntries{{ - definition: &Definition{Kind: definitionKindTripleSlashReference, reference: &tripleSlashDefinition{reference: resolvedRef.reference}}, + definition: &Definition{Kind: definitionKindTripleSlashReference, tripleSlashFileRef: &tripleSlashDefinition{reference: resolvedRef.reference}}, references: getReferencesForNonModule(resolvedRef.file, program /*fileIncludeReasons,*/), }} } @@ -654,19 +529,19 @@ func getReferencedSymbolsForNode(position int, node *ast.Node, program *compiler return getReferencedSymbolsForModule(program, symbol.Parent, false /*excludeImportTypeOfExportEquals*/, sourceFiles, sourceFilesSet) } - moduleReferences := getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol, program, sourceFiles, options, sourceFilesSet) // !!! cancellationToken + 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 := getReferencedSymbolsForModuleIfDeclaredBySourceFile(aliasedSymbol, program, sourceFiles, options, sourceFilesSet) // !!! cancellationToken + moduleReferencesOfExportTarget := l.getReferencedSymbolsForModuleIfDeclaredBySourceFile(aliasedSymbol, program, sourceFiles, options, sourceFilesSet) // !!! cancellationToken references := getReferencedSymbolsForSymbol(symbol, node, sourceFiles, sourceFilesSet, checker, options) // !!! cancellationToken - return mergeReferences(program, moduleReferences, references, moduleReferencesOfExportTarget) + return l.mergeReferences(program, moduleReferences, references, moduleReferencesOfExportTarget) } -func getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol *ast.Symbol, program *compiler.Program, sourceFiles []*ast.SourceFile, options refOptions, sourceFilesSet *core.Set[string]) []*SymbolAndEntries { +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 @@ -685,7 +560,7 @@ func getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol *ast.Symbol, pro // Continue to get references to 'export ='. checker := program.GetTypeChecker() symbol, _ = checker.ResolveAlias(exportEquals) - return mergeReferences(program, moduleReferences, getReferencedSymbolsForSymbol(symbol /*node*/, nil, sourceFiles, sourceFilesSet, checker /*, cancellationToken*/, options)) + return l.mergeReferences(program, moduleReferences, getReferencedSymbolsForSymbol(symbol /*node*/, nil, sourceFiles, sourceFilesSet, checker /*, cancellationToken*/, options)) } func getReferencedSymbolsSpecial(node *ast.Node, sourceFiles []*ast.SourceFile) []*SymbolAndEntries { @@ -716,7 +591,7 @@ func getReferencedSymbolsSpecial(node *ast.Node, sourceFiles []*ast.SourceFile) } if node.Kind == ast.KindStaticKeyword && node.Parent.Kind == ast.KindClassStaticBlockDeclaration { - return []*SymbolAndEntries{{definition: &Definition{Kind: definitionKindKeyword, node: node}, references: []Entry{NewNodeEntry(node)}}} + return []*SymbolAndEntries{{definition: &Definition{Kind: definitionKindKeyword, node: node}, references: []*Entry{NewNodeEntry(node)}}} } // Labels @@ -748,7 +623,7 @@ func getReferencedSymbolsSpecial(node *ast.Node, sourceFiles []*ast.SourceFile) 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) Entry { + references := core.MapNonNil(getPossibleSymbolReferenceNodes(sourceFile, labelName, container), func(node *ast.Node) *Entry { // 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) @@ -825,12 +700,12 @@ func getReferencesForThisKeyword(thisOrSuperKeyword *ast.Node, sourceFiles []*as return false }) }), - func(n *ast.Node) Entry { return NewNodeEntry(n) }, + func(n *ast.Node) *Entry { return NewNodeEntry(n) }, ) - thisParameter := core.FirstNonNil(references, func(ref Entry) *ast.Node { - if ref.(*NodeEntry).node.Parent.Kind == ast.KindParameter { - return ref.(*NodeEntry).node + thisParameter := core.FirstNonNil(references, func(ref *Entry) *ast.Node { + if ref.node.Parent.Kind == ast.KindParameter { + return ref.node } return nil }) @@ -858,7 +733,7 @@ func getReferencesForSuperKeyword(superKeyword *ast.Node) []*SymbolAndEntries { } sourceFile := ast.GetSourceFileOfNode(searchSpaceNode) - references := core.MapNonNil(getPossibleSymbolReferenceNodes(sourceFile, "super", searchSpaceNode), func(node *ast.Node) Entry { + references := core.MapNonNil(getPossibleSymbolReferenceNodes(sourceFile, "super", searchSpaceNode), func(node *ast.Node) *Entry { if node.Kind != ast.KindSuperKeyword { return nil } @@ -879,9 +754,9 @@ func getReferencesForSuperKeyword(superKeyword *ast.Node) []*SymbolAndEntries { 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) []Entry { + references := core.FlatMap(sourceFiles, func(sourceFile *ast.SourceFile) []*Entry { // cancellationToken.throwIfCancellationRequested(); - return core.MapNonNil(getPossibleSymbolReferenceNodes(sourceFile, scanner.TokenToString(keywordKind), sourceFile.AsNode()), func(referenceLocation *ast.Node) Entry { + return core.MapNonNil(getPossibleSymbolReferenceNodes(sourceFile, scanner.TokenToString(keywordKind), sourceFile.AsNode()), func(referenceLocation *ast.Node) *Entry { if referenceLocation.Kind == keywordKind && (!filterReadOnlyTypeOperator || isReadonlyTypeOperator(referenceLocation)) { return NewNodeEntry(referenceLocation) } @@ -891,12 +766,12 @@ func getAllReferencesForKeyword(sourceFiles []*ast.SourceFile, keywordKind ast.K if len(references) == 0 { return nil } - return []*SymbolAndEntries{NewSymbolAndEntries(definitionKindKeyword, references[0].(*NodeEntry).node, nil, references)} + 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.AsSourceFile() != sourceFile { // todo check if this is the right way to check for equality of nodes + if referenceLocation := astnav.GetTouchingPropertyName(sourceFile, pos); referenceLocation != sourceFile.AsNode() { return referenceLocation } return nil @@ -934,15 +809,20 @@ func getPossibleSymbolReferencePositions(sourceFile *ast.SourceFile, symbolName // Found a real match. Keep searching. positions = append(positions, position) } - position = strings.Index(text[position+symbolNameLength+1:], symbolName) + 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) []Entry { +func getReferencesForNonModule(referencedFile *ast.SourceFile, program *compiler.Program) []*Entry { // !!! not implemented - return []Entry{} + return []*Entry{} } func getMergedAliasedSymbolOfNamespaceExportDeclaration(node *ast.Node, symbol *ast.Symbol, checker *checker.Checker) *ast.Symbol { @@ -1136,7 +1016,7 @@ type refState struct { seenContainingTypeReferences seenTracker[*ast.Node] // node seen tracker // seenReExportRHS seenTracker[*ast.Node] // node seen tracker // importTracker ImportTracker - symbolIdToReferences [][]Entry + symbolIdToReferences map[ast.SymbolId]*SymbolAndEntries sourceFileToSeenSymbols map[ast.NodeId]*core.Set[ast.SymbolId] } @@ -1152,7 +1032,7 @@ func newState(sourceFiles []*ast.SourceFile, sourceFilesSet *core.Set[string], n inheritsFromCache: map[inheritKey]bool{}, seenContainingTypeReferences: seenTracker[*ast.Node]{}, // seenReExportRHS: seenTracker[*ast.Node]{}, - symbolIdToReferences: [][]Entry{}, + symbolIdToReferences: map[ast.SymbolId]*SymbolAndEntries{}, sourceFileToSeenSymbols: map[ast.NodeId]*core.Set[ast.SymbolId]{}, } } @@ -1164,11 +1044,15 @@ func (state *refState) createSearch(location *ast.Node, symbol *ast.Symbol, comi // The other two forms seem to be handled downstream (e.g. in `skipPastExportOrImportSpecifier`), so special-casing the first form // here appears to be intentional). - // TODO - // if text == "" { - // text = stripQuotes(symbolName(getLocalSymbolForExportDefault(symbol) || getNonModuleSymbolOfMergedModuleSymbol(symbol) || symbol)) - // } - // escapedText := escapeLeadingUnderscores(text); + symbolToSearchFor := binder.GetLocalSymbolForExportDefault(symbol) + if symbolToSearchFor == nil { + if s := getNonModuleSymbolOfMergedModuleSymbol(symbol); s != nil { + symbolToSearchFor = s + } else { + symbolToSearchFor = symbol + } + } + text = core.StripQuotes(ast.SymbolName(symbolToSearchFor)) escapedText := text if len(allSearchSymbols) == 0 { allSearchSymbols = []*ast.Symbol{symbol} @@ -1184,22 +1068,20 @@ func (state *refState) createSearch(location *ast.Node, symbol *ast.Symbol, comi } func (state *refState) referenceAdder(searchSymbol *ast.Symbol) func(*ast.Node, entryKind) { - // todo: rename to getReferenceAdder + // !!! after find all references is fully implemented, rename this to something like 'getReferenceAdder' symbolId := ast.GetSymbolId(searchSymbol) - references := state.symbolIdToReferences[symbolId] - if references == nil { - references = []Entry{} - state.symbolIdToReferences[symbolId] = references - state.result = append(state.result, NewSymbolAndEntries(definitionKindSymbol, nil, searchSymbol, references)) + symbolAndEntry := state.symbolIdToReferences[symbolId] + if symbolAndEntry == nil { + state.symbolIdToReferences[symbolId] = NewSymbolAndEntries(definitionKindSymbol, nil, searchSymbol, []*Entry{}) + state.result = append(state.result, state.symbolIdToReferences[symbolId]) + symbolAndEntry = state.symbolIdToReferences[symbolId] } return func(node *ast.Node, kind entryKind) { - references = append(references, NewNodeEntryWithKind(node, kind)) + symbolAndEntry.references = append(symbolAndEntry.references, NewNodeEntryWithKind(node, kind)) } } func (state *refState) addReference(referenceLocation *ast.Node, symbol *ast.Symbol, kind entryKind) { - // { kind, symbol } = "kind" in relatedSymbol ? relatedSymbol : { kind: undefined, symbol: relatedSymbol }; // eslint-disable-line local/no-in-operator - // 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 @@ -1226,7 +1108,8 @@ func (state *refState) addImplementationReferences(refNode *ast.Node, addRef fun if refNode.Parent.Kind == ast.KindShorthandPropertyAssignment { // Go ahead and dereference the shorthand assignment by going to its definition - // TODO + + // !!! not implemented // getReferenceEntriesForShorthandPropertyAssignment(refNode, state.checker, addRef); } @@ -1337,7 +1220,7 @@ func (state *refState) getReferencesAtLocation(sourceFile *ast.SourceFile, posit // match in a comment or string if that's what the caller is asking // for. - // TODO + // !!! 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 @@ -1371,7 +1254,7 @@ func (state *refState) getReferencesAtLocation(sourceFile *ast.SourceFile, posit return } - // TODO + // !!! not implemented // if isJSDocPropertyLikeTag(parent) && parent.isNameFirst && // parent.TypeExpression && isJSDocTypeLiteral(parent.TypeExpression.Type()) && // parent.TypeExpression.Type().jsDocPropertyTags && length(parent.TypeExpression.Type().jsDocPropertyTags) { @@ -1438,7 +1321,6 @@ func (state *refState) populateSearchSymbolSet(symbol *ast.Symbol, location *ast return []*ast.Symbol{symbol} } result := []*ast.Symbol{} - // TODO state.forEachRelatedSymbol( symbol, location, @@ -1451,13 +1333,8 @@ func (state *refState) populateSearchSymbolSet(symbol *ast.Symbol, location *ast base = nil } } - if base != nil { - result = append(result, base) - } else if root != nil { - result = append(result, root) - } else { - result = append(result, sym) - } + + 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 }, diff --git a/internal/ls/findallreferences_test.go b/internal/ls/findallreferences_test.go new file mode 100644 index 0000000000..3a723d2e41 --- /dev/null +++ b/internal/ls/findallreferences_test.go @@ -0,0 +1,271 @@ +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" + "gotest.tools/v3/assert" +) + +func runFindReferencesTest(t *testing.T, input string, expectedLocations map[string]*core.Set[string]) { + testData := lstestutil.ParseTestData("/testing", input, "/file1.ts") + file := testData.Files[0].Filename + markerPositions := testData.MarkerPositions + service := createLanguageService(testData.Files[0].Filename, map[string]string{ + testData.Files[0].Filename: testData.Files[0].Content, + }) + context := &lsproto.ReferenceContext{} + // ptrTrue := ptrTo(true) + // capabilities := &lsproto.SignatureHelpClientCapabilities{ + // SignatureInformation: &lsproto.ClientSignatureInformationOptions{ + // ActiveParameterSupport: ptrTrue, + // ParameterInformation: &lsproto.ClientSignatureParameterInformationOptions{ + // LabelOffsetSupport: ptrTrue, + // }, + // }, + // } + // input { + // expected map[marker][]marker: {name called at: expected ref locations} + // get locations of markers + // check if each returned reference (which is a location) is in the expected map locations + //} + // preferences := &ls.UserPreferences{} + // from expected locations, + // for each expected location, get the range of the marker. put into a set [start loc]{end loc} + allExpectedLocations := map[lsproto.Location]string{} + for _, marker := range testData.MarkerPositions { + allExpectedLocations[*service.GetExpectedReferenceFromMarker(marker)] = marker.Name + } + + for requestMarkerName, expectedSet := range expectedLocations { + marker, ok := markerPositions[requestMarkerName] + if !ok { + t.Fatalf("No marker found for '%s'", requestMarkerName) + } + referencesResult := service.ProvideReferences(file, marker.Position, context) + + 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..c5359d4671 --- /dev/null +++ b/internal/ls/findallreferencesexport_test.go @@ -0,0 +1,18 @@ +package ls + +import ( + "github.com/microsoft/typescript-go/internal/astnav" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/testutil/lstestutil" +) + +func (l *LanguageService) GetExpectedReferenceFromMarker(marker *lstestutil.Marker) *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.getProgramAndFile(marker.Filename) + node := astnav.GetTouchingPropertyName(sourceFile, marker.Position) + return &lsproto.Location{ + Uri: FileNameToDocumentURI(marker.Filename), + Range: *l.createLspRangeFromNode(node, sourceFile), + } +} diff --git a/internal/ls/utilities.go b/internal/ls/utilities.go index 07c4887a35..cd026f11f5 100644 --- a/internal/ls/utilities.go +++ b/internal/ls/utilities.go @@ -1,6 +1,7 @@ package ls import ( + "cmp" "slices" "strings" @@ -12,6 +13,24 @@ import ( "github.com/microsoft/typescript-go/internal/scanner" ) +// p.Compare(other) == cmp.Compare(p, other) +func ComparePositions(p, other lsproto.Position) int { + if lineComp := cmp.Compare(p.Line, other.Line); lineComp != 0 { + return lineComp + } + return cmp.Compare(p.Line, other.Line) +} + +// t.Compare(other) == cmp.Compare(t, other) +// +// compares Range.Start and then Range.End +func CompareRanges(t, other *lsproto.Range) int { + if startComp := ComparePositions(t.Start, other.Start); startComp != 0 { + return startComp + } + return ComparePositions(t.End, other.End) +} + var quoteReplacer = strings.NewReplacer("'", `\'`, `\"`, `"`) // !!! @@ -83,6 +102,17 @@ func isModuleSpecifierLike(node *ast.Node) bool { node.Parent.Kind == ast.KindJSDocImportTag } +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 { @@ -1577,8 +1607,10 @@ func getPropertySymbolsFromBaseTypes(symbol *ast.Symbol, propertyName string, ch } propertySymbol := checker.GetPropertyOfType(propertyType, propertyName) // Visit the typeReference as well to see if it directly or indirectly uses that property - if r := core.FirstNonNil(checker.GetRootSymbols(propertySymbol), cb); r != nil { - return r + if propertySymbol != nil { + if r := core.FirstNonNil(checker.GetRootSymbols(propertySymbol), cb); r != nil { + return r + } } return recur(propertyType.Symbol()) }) diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 2ff9158560..dfbae10884 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -350,6 +350,9 @@ func (s *Server) handleInitialize(req *lsproto.RequestMessage) error { DefinitionProvider: &lsproto.BooleanOrDefinitionOptions{ Boolean: ptrTo(true), }, + ReferencesProvider: &lsproto.BooleanOrReferenceOptions{ + Boolean: ptrTo(true), + }, DiagnosticProvider: &lsproto.DiagnosticOptionsOrDiagnosticRegistrationOptions{ DiagnosticOptions: &lsproto.DiagnosticOptions{ InterFileDependencies: true, From 9a25ad00a07fa761d34add4352307acdbd2a559e Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Tue, 20 May 2025 00:12:53 -0700 Subject: [PATCH 03/13] update to cancellation structure --- internal/ast/utilities.go | 4 +-- internal/binder/binder.go | 2 +- internal/checker/checker.go | 2 +- internal/ls/findallreferences.go | 26 +++++++++-------- internal/ls/findallreferences_test.go | 31 ++++++--------------- internal/ls/findallreferencesexport_test.go | 23 +++++++++++---- internal/lsp/server.go | 17 ++++++----- internal/parser/reparser.go | 4 +-- 8 files changed, 56 insertions(+), 53 deletions(-) diff --git a/internal/ast/utilities.go b/internal/ast/utilities.go index 1a9f4b509c..393828e194 100644 --- a/internal/ast/utilities.go +++ b/internal/ast/utilities.go @@ -1416,7 +1416,7 @@ func GetNonAssignedNameOfDeclaration(declaration *Node) *Node { switch declaration.Kind { case KindBinaryExpression: bin := declaration.AsBinaryExpression() - kind := GetAssignmentDeclarationKind(bin) + kind := GetJSDocAssignmentDeclarationKind(bin) if kind == JSDeclarationKindProperty || kind == JSDeclarationKindThisProperty { return GetElementOrPropertyAccessArgumentExpressionOrName(bin.Left) } @@ -1481,7 +1481,7 @@ const ( JSDeclarationKindProperty ) -func GetAssignmentDeclarationKind(bin *BinaryExpression) JSDeclarationKind { +func GetJSDocAssignmentDeclarationKind(bin *BinaryExpression) JSDeclarationKind { if bin.OperatorToken.Kind != KindEqualsToken || !IsAccessExpression(bin.Left) { return JSDeclarationKindNone } diff --git a/internal/binder/binder.go b/internal/binder/binder.go index e767cb20d9..4f5bad8763 100644 --- a/internal/binder/binder.go +++ b/internal/binder/binder.go @@ -622,7 +622,7 @@ func (b *Binder) bind(node *ast.Node) bool { setFlowNode(node, b.currentFlow) } case ast.KindBinaryExpression: - switch ast.GetAssignmentDeclarationKind(node.AsBinaryExpression()) { + switch ast.GetJSDocAssignmentDeclarationKind(node.AsBinaryExpression()) { case ast.JSDeclarationKindProperty: b.bindFunctionPropertyAssignment(node) case ast.JSDeclarationKindThisProperty: diff --git a/internal/checker/checker.go b/internal/checker/checker.go index ff7c34d166..4436b373c6 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -16814,7 +16814,7 @@ func (c *Checker) isConstructorDeclaredThisProperty(symbol *ast.Symbol) (thisAss break } bin := declaration.AsBinaryExpression() - if ast.GetAssignmentDeclarationKind(bin) == ast.JSDeclarationKindThisProperty && + if ast.GetJSDocAssignmentDeclarationKind(bin) == ast.JSDeclarationKindThisProperty && (bin.Left.Kind != ast.KindElementAccessExpression || ast.IsStringOrNumericLiteralLike(bin.Left.AsElementAccessExpression().ArgumentExpression)) { // TODO: if bin.Type() != nil, use bin.Type() if bin.Right.Kind == ast.KindTypeAssertionExpression { diff --git a/internal/ls/findallreferences.go b/internal/ls/findallreferences.go index 03f85220d1..de61fdf4f6 100644 --- a/internal/ls/findallreferences.go +++ b/internal/ls/findallreferences.go @@ -371,13 +371,10 @@ func getSymbolScope(symbol *ast.Symbol) *ast.Node { // === functions on (*ls) === -func (l *LanguageService) ProvideReferences( - fileName string, - position int, - context *lsproto.ReferenceContext, -) []*lsproto.Location { - // does findReferencedSymbols except only computes the conversions needed for reference locations - program, sourceFile := l.getProgramAndFile(fileName) +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} @@ -472,16 +469,19 @@ func (l *LanguageService) getReferencedSymbolsForNode(position int, node *ast.No } if node.Kind == ast.KindSourceFile { - // !!! not implemented resolvedRef := getReferenceAtPosition(node.AsSourceFile(), position, program) if resolvedRef.file == nil { return nil } - if moduleSymbol := program.GetTypeChecker().GetMergedSymbol(resolvedRef.file.Symbol); moduleSymbol != 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 @@ -499,7 +499,9 @@ func (l *LanguageService) getReferencedSymbolsForNode(position int, node *ast.No } } - checker := program.GetTypeChecker() + 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 @@ -558,7 +560,9 @@ func (l *LanguageService) getReferencedSymbolsForModuleIfDeclaredBySourceFile(sy return moduleReferences } // Continue to get references to 'export ='. - checker := program.GetTypeChecker() + 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)) } diff --git a/internal/ls/findallreferences_test.go b/internal/ls/findallreferences_test.go index 3a723d2e41..b7469cf9a7 100644 --- a/internal/ls/findallreferences_test.go +++ b/internal/ls/findallreferences_test.go @@ -7,37 +7,23 @@ import ( "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") - file := testData.Files[0].Filename markerPositions := testData.MarkerPositions - service := createLanguageService(testData.Files[0].Filename, map[string]string{ + ctx := projecttestutil.WithRequestID(t.Context()) + service, done := createLanguageService(ctx, testData.Files[0].Filename, map[string]string{ testData.Files[0].Filename: testData.Files[0].Content, }) - context := &lsproto.ReferenceContext{} - // ptrTrue := ptrTo(true) - // capabilities := &lsproto.SignatureHelpClientCapabilities{ - // SignatureInformation: &lsproto.ClientSignatureInformationOptions{ - // ActiveParameterSupport: ptrTrue, - // ParameterInformation: &lsproto.ClientSignatureParameterInformationOptions{ - // LabelOffsetSupport: ptrTrue, - // }, - // }, - // } - // input { - // expected map[marker][]marker: {name called at: expected ref locations} - // get locations of markers - // check if each returned reference (which is a location) is in the expected map locations - //} - // preferences := &ls.UserPreferences{} - // from expected locations, - // for each expected location, get the range of the marker. put into a set [start loc]{end loc} + 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)] = marker.Name + allExpectedLocations[*service.GetExpectedReferenceFromMarker(marker.Filename, marker.Position)] = marker.Name } for requestMarkerName, expectedSet := range expectedLocations { @@ -45,9 +31,10 @@ func runFindReferencesTest(t *testing.T, input string, expectedLocations map[str if !ok { t.Fatalf("No marker found for '%s'", requestMarkerName) } - referencesResult := service.ProvideReferences(file, marker.Position, context) + 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 diff --git a/internal/ls/findallreferencesexport_test.go b/internal/ls/findallreferencesexport_test.go index c5359d4671..0077a3d7a6 100644 --- a/internal/ls/findallreferencesexport_test.go +++ b/internal/ls/findallreferencesexport_test.go @@ -2,17 +2,30 @@ 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" - "github.com/microsoft/typescript-go/internal/testutil/lstestutil" ) -func (l *LanguageService) GetExpectedReferenceFromMarker(marker *lstestutil.Marker) *lsproto.Location { +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.getProgramAndFile(marker.Filename) - node := astnav.GetTouchingPropertyName(sourceFile, marker.Position) + _, sourceFile := l.tryGetProgramAndFile(fileName) + node := astnav.GetTouchingPropertyName(sourceFile, pos) return &lsproto.Location{ - Uri: FileNameToDocumentURI(marker.Filename), + 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/lsp/server.go b/internal/lsp/server.go index 666c837885..3b7cb1942b 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -402,7 +402,7 @@ func (s *Server) handleRequestOrNotification(ctx context.Context, req *lsproto.R case *lsproto.CompletionParams: return s.handleCompletion(ctx, req) case *lsproto.ReferenceParams: - return s.handleReferences(req) + return s.handleReferences(ctx, req) default: switch req.Method { case lsproto.MethodShutdown: @@ -553,17 +553,16 @@ func (s *Server) handleDefinition(ctx context.Context, req *lsproto.RequestMessa return nil } -func (s *Server) handleReferences(req *lsproto.RequestMessage) error { +func (s *Server) handleReferences(ctx context.Context, req *lsproto.RequestMessage) error { // findAllReferences params := req.Params.(*lsproto.ReferenceParams) - file, project := s.getFileAndProject(params.TextDocument.Uri) - pos, err := s.converters.LineAndCharacterToPositionForFile(params.Position, file.FileName()) - if err != nil { - return s.sendError(req.ID, err) - } - locations := project.LanguageService().ProvideReferences(file.FileName(), pos, params.Context) + project := s.projectService.EnsureDefaultProjectForURI(params.TextDocument.Uri) + languageService, done := project.GetLanguageServiceForRequest(ctx) + defer done() - return s.sendResult(req.ID, locations) + locations := languageService.ProvideReferences(params) + s.sendResult(req.ID, locations) + return nil } func (s *Server) handleCompletion(ctx context.Context, req *lsproto.RequestMessage) error { diff --git a/internal/parser/reparser.go b/internal/parser/reparser.go index 041fd45f76..4c23240938 100644 --- a/internal/parser/reparser.go +++ b/internal/parser/reparser.go @@ -13,7 +13,7 @@ func (p *Parser) reparseCommonJS(node *ast.Node) { return } bin := node.AsExpressionStatement().Expression.AsBinaryExpression() - kind := ast.GetAssignmentDeclarationKind(bin) + kind := ast.GetJSDocAssignmentDeclarationKind(bin) var export *ast.Node switch kind { case ast.JSDeclarationKindModuleExports: @@ -136,7 +136,7 @@ func (p *Parser) reparseTags(parent *ast.Node, jsDoc []*ast.Node) { } else if parent.Kind == ast.KindExpressionStatement && parent.AsExpressionStatement().Expression.Kind == ast.KindBinaryExpression { bin := parent.AsExpressionStatement().Expression.AsBinaryExpression() - if ast.GetAssignmentDeclarationKind(bin) != ast.JSDeclarationKindNone { + if ast.GetJSDocAssignmentDeclarationKind(bin) != ast.JSDeclarationKindNone { bin.Right = p.makeNewTypeAssertion(p.makeNewType(tag.AsJSDocTypeTag().TypeExpression, nil), bin.Right) } } From 632e5fe3f05552059b06e5518fef23e832e390bf Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Tue, 20 May 2025 12:01:10 -0700 Subject: [PATCH 04/13] address comments --- internal/ast/utilities.go | 142 ++++++++++--------------------- internal/binder/binder.go | 2 +- internal/checker/checker.go | 32 +------ internal/checker/services.go | 30 +++++++ internal/checker/utilities.go | 17 ---- internal/ls/findallreferences.go | 37 ++++++-- internal/ls/utilities.go | 41 ++------- internal/parser/reparser.go | 4 +- 8 files changed, 117 insertions(+), 188 deletions(-) diff --git a/internal/ast/utilities.go b/internal/ast/utilities.go index 393828e194..07ec55893f 100644 --- a/internal/ast/utilities.go +++ b/internal/ast/utilities.go @@ -1416,7 +1416,7 @@ func GetNonAssignedNameOfDeclaration(declaration *Node) *Node { switch declaration.Kind { case KindBinaryExpression: bin := declaration.AsBinaryExpression() - kind := GetJSDocAssignmentDeclarationKind(bin) + kind := GetAssignmentDeclarationKind(bin) if kind == JSDeclarationKindProperty || kind == JSDeclarationKindThisProperty { return GetElementOrPropertyAccessArgumentExpressionOrName(bin.Left) } @@ -1479,9 +1479,22 @@ const ( JSDeclarationKindThisProperty /// F.name = expr, F[name] = expr JSDeclarationKindProperty + + // PropertyAccessKinds + // F.prototype = { ... } + JSDeclarationKindPrototype + // Object.defineProperty(x, 'name', { value: any, writable?: boolean (false by default) }); + // Object.defineProperty(x, 'name', { get: Function, set: Function }); + // Object.defineProperty(x, 'name', { get: Function }); + // Object.defineProperty(x, 'name', { set: Function }); + JSDeclarationKindObjectDefinePropertyValue + // Object.defineProperty(exports || module.exports, 'name', ...); + JSDeclarationKindObjectDefinePropertyExports + // Object.defineProperty(Foo.prototype, 'name', ...); + JSDeclarationKindObjectDefinePrototypeProperty ) -func GetJSDocAssignmentDeclarationKind(bin *BinaryExpression) JSDeclarationKind { +func GetAssignmentDeclarationKind(bin *BinaryExpression) JSDeclarationKind { if bin.OperatorToken.Kind != KindEqualsToken || !IsAccessExpression(bin.Left) { return JSDeclarationKindNone } @@ -1506,6 +1519,37 @@ func hasJSBindableName(node *Node) bool { return IsIdentifier(name) || IsStringLiteralLike(name) } +func GetAssignmentDeclarationPropertyAccessKind(lhs *Node) JSDeclarationKind { + if lhs.Expression().Kind == KindThisKeyword { + return JSDeclarationKindThisProperty + } else if IsModuleExportsAccessExpression(lhs) { + // module.exports = expr + return JSDeclarationKindModuleExports + } else if IsBindableStaticNameExpression(lhs.Expression() /*excludeThisKeyword*/, true) { + if IsPrototypeAccess(lhs.Expression()) { + // F.G....prototype.x = expr + return JSDeclarationKindPrototypeProperty + } + + nextToLast := lhs + for nextToLast.Expression().Kind != KindIdentifier { + nextToLast = nextToLast.Expression() + } + idText := nextToLast.Expression().AsIdentifier().Text + if (idText == "exports" || idText == "module" && GetElementOrPropertyAccessName(nextToLast) == "exports") && + // ExportsProperty does not support binding with computed names + IsBindableStaticAccessExpression(lhs, false) { + // exports.name = expr OR module.exports.name = expr OR exports["name"] = expr ... + return JSDeclarationKindExportsProperty + } + if IsBindableStaticNameExpression(lhs /*excludeThisKeyword*/, true) || (IsElementAccessExpression(lhs) && IsDynamicName(lhs)) { + // F.G...x = expr + return JSDeclarationKindProperty + } + } + return JSDeclarationKindNone +} + /** * A declaration has a dynamic name if all of the following are true: * 1. The declaration has a computed property name. @@ -2552,100 +2596,6 @@ func GetDeclarationContainer(node *Node) *Node { }).Parent } -type AssignmentDeclarationKind = int32 - -const ( - AssignmentDeclarationKindNone = AssignmentDeclarationKind(iota) - /// exports.name = expr - /// module.exports.name = expr - AssignmentDeclarationKindExportsProperty - /// module.exports = expr - AssignmentDeclarationKindModuleExports - /// className.prototype.name = expr - AssignmentDeclarationKindPrototypeProperty - /// this.name = expr - AssignmentDeclarationKindThisProperty - // F.name = expr - AssignmentDeclarationKindProperty - // F.prototype = { ... } - AssignmentDeclarationKindPrototype - // Object.defineProperty(x, 'name', { value: any, writable?: boolean (false by default) }); - // Object.defineProperty(x, 'name', { get: Function, set: Function }); - // Object.defineProperty(x, 'name', { get: Function }); - // Object.defineProperty(x, 'name', { set: Function }); - AssignmentDeclarationKindObjectDefinePropertyValue - // Object.defineProperty(exports || module.exports, 'name', ...); - AssignmentDeclarationKindObjectDefinePropertyExports - // Object.defineProperty(Foo.prototype, 'name', ...); - AssignmentDeclarationKindObjectDefinePrototypeProperty -) - -// / Given a BinaryExpression, returns SpecialPropertyAssignmentKind for the various kinds of property -// / assignments we treat as special in the binder -func GetAssignmentDeclarationKind(expr *Expression /*BinaryExpression | CallExpression*/) AssignmentDeclarationKind { - var special AssignmentDeclarationKind - switch expr.Kind { - case KindCallExpression: - if !IsBindableObjectDefinePropertyCall(expr) { - special = AssignmentDeclarationKindNone - } else { - entityName := expr.Arguments()[0] - if IsExportsIdentifier(entityName) || IsModuleExportsAccessExpression(entityName) { - special = AssignmentDeclarationKindObjectDefinePropertyExports - } else if IsBindableStaticAccessExpression(entityName, false) && GetElementOrPropertyAccessName(entityName) == "prototype" { - special = AssignmentDeclarationKindObjectDefinePrototypeProperty - } else { - special = AssignmentDeclarationKindObjectDefinePropertyValue - } - } - default: // KindBinaryExpression - binExpr := expr.AsBinaryExpression() - if binExpr.OperatorToken.Kind != KindEqualsToken || !IsAccessExpression(binExpr.Left) || isVoidZero(GetRightMostAssignedExpression(expr)) { - special = AssignmentDeclarationKindNone - } else if IsBindableStaticNameExpression(binExpr.Left.Expression() /*excludeThisKeyword*/, true) && - GetElementOrPropertyAccessName(binExpr.Left) == "prototype" && - IsObjectLiteralExpression(GetInitializerOfBinaryExpression(binExpr)) { - // F.prototype = { ... } - special = AssignmentDeclarationKindPrototype - } else { - special = GetAssignmentDeclarationPropertyAccessKind(binExpr.Left) - } - } - return core.IfElse(special == AssignmentDeclarationKindProperty || IsInJSFile(expr), special, AssignmentDeclarationKindNone) -} - -func GetAssignmentDeclarationPropertyAccessKind(lhs *Node) AssignmentDeclarationKind { - if lhs.Expression().Kind == KindThisKeyword { - return AssignmentDeclarationKindThisProperty - } else if IsModuleExportsAccessExpression(lhs) { - // module.exports = expr - return AssignmentDeclarationKindModuleExports - } else if IsBindableStaticNameExpression(lhs.Expression() /*excludeThisKeyword*/, true) { - if IsPrototypeAccess(lhs.Expression()) { - // F.G....prototype.x = expr - return AssignmentDeclarationKindPrototypeProperty - } - - nextToLast := lhs - for nextToLast.Expression().Kind != KindIdentifier { - nextToLast = nextToLast.Expression() - } - idText := nextToLast.Expression().AsIdentifier().Text - if (idText == "exports" || idText == "module" && GetElementOrPropertyAccessName(nextToLast) == "exports") && - // ExportsProperty does not support binding with computed names - IsBindableStaticAccessExpression(lhs, false) { - // exports.name = expr OR module.exports.name = expr OR exports["name"] = expr ... - return AssignmentDeclarationKindExportsProperty - } - if IsBindableStaticNameExpression(lhs /*excludeThisKeyword*/, true) || (IsElementAccessExpression(lhs) && IsDynamicName(lhs)) { - // F.G...x = expr - return AssignmentDeclarationKindProperty - } - } - - return AssignmentDeclarationKindNone -} - func IsPrototypeAccess(node *Node) bool { return IsBindableStaticAccessExpression(node, false) && GetElementOrPropertyAccessName(node) == "prototype" } diff --git a/internal/binder/binder.go b/internal/binder/binder.go index 4f5bad8763..e767cb20d9 100644 --- a/internal/binder/binder.go +++ b/internal/binder/binder.go @@ -622,7 +622,7 @@ func (b *Binder) bind(node *ast.Node) bool { setFlowNode(node, b.currentFlow) } case ast.KindBinaryExpression: - switch ast.GetJSDocAssignmentDeclarationKind(node.AsBinaryExpression()) { + switch ast.GetAssignmentDeclarationKind(node.AsBinaryExpression()) { case ast.JSDeclarationKindProperty: b.bindFunctionPropertyAssignment(node) case ast.JSDeclarationKindThisProperty: diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 4436b373c6..fa6623e3d5 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -13588,36 +13588,6 @@ func (c *Checker) getExportSymbolOfValueSymbolIfExported(symbol *ast.Symbol) *as return c.getMergedSymbol(symbol) } -func (c *Checker) GetExportSpecifierLocalTargetSymbol(node *ast.Node) *ast.Symbol { - switch node.Kind { - case ast.KindExportSpecifier: - if node.Parent.Parent.AsExportDeclaration().ModuleSpecifier != nil { - return c.getExternalModuleMember(node.Parent.Parent, node, false /*dontResolveAlias*/) - } - name := node.AsExportSpecifier().PropertyName - if name == nil { - name = node.Name() - } - if name.Kind == ast.KindStringLiteral { - // Skip for invalid syntax like this: export { "x" } - return nil - } - // fall through - case ast.KindIdentifier: - // fall through - default: - panic("Unhandled case in getExportSpecifierLocalTargetSymbol") - } - 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 -} - func (c *Checker) getSymbolOfDeclaration(node *ast.Node) *ast.Symbol { symbol := node.Symbol() if symbol != nil { @@ -16814,7 +16784,7 @@ func (c *Checker) isConstructorDeclaredThisProperty(symbol *ast.Symbol) (thisAss break } bin := declaration.AsBinaryExpression() - if ast.GetJSDocAssignmentDeclarationKind(bin) == ast.JSDeclarationKindThisProperty && + if ast.GetAssignmentDeclarationKind(bin) == ast.JSDeclarationKindThisProperty && (bin.Left.Kind != ast.KindElementAccessExpression || ast.IsStringOrNumericLiteralLike(bin.Left.AsElementAccessExpression().ArgumentExpression)) { // TODO: if bin.Type() != nil, use bin.Type() if bin.Right.Kind == ast.KindTypeAssertionExpression { diff --git a/internal/checker/services.go b/internal/checker/services.go index 30ed0ebbac..95775eb6b3 100644 --- a/internal/checker/services.go +++ b/internal/checker/services.go @@ -400,6 +400,36 @@ 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.AsExportSpecifier().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 diff --git a/internal/checker/utilities.go b/internal/checker/utilities.go index cb634094ba..b5936ac464 100644 --- a/internal/checker/utilities.go +++ b/internal/checker/utilities.go @@ -1007,23 +1007,6 @@ func getPropertyNameFromType(t *Type) string { panic("Unhandled case in getPropertyNameFromType") } -func GetEffectiveImplementsTypeNodes(node *ast.Node) []*ast.TypeNode { - // compiler utilities - if ast.IsInJSFile(node) { - // not implmented jsdoc - // return getJSDocImplementsTags(node).map(n => n.class); - } else if heritageClause := ast.GetHeritageClause(node, ast.KindImplementsKeyword); heritageClause != nil { - return heritageClause.AsHeritageClause().Types.Nodes - } - return nil -} - -func GetEffectiveBaseTypeNode(node *ast.Node) *ast.TypeNode { - baseType := ast.GetExtendsHeritageClauseElement(node) - // !!! not implemented jsdoc cases - return baseType -} - func isNumericLiteralName(name string) bool { // The intent of numeric names is that // - they are names with text in a numeric form, and that diff --git a/internal/ls/findallreferences.go b/internal/ls/findallreferences.go index de61fdf4f6..b5931fdb7a 100644 --- a/internal/ls/findallreferences.go +++ b/internal/ls/findallreferences.go @@ -2,6 +2,7 @@ package ls import ( "cmp" + "fmt" "slices" "strings" @@ -151,7 +152,7 @@ func getContextNodeForNodeEntry(node *ast.Node) *ast.Node { 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) != ast.AssignmentDeclarationKindNone { + if binaryExpression != nil && ast.GetAssignmentDeclarationKind(binaryExpression.AsBinaryExpression()) != ast.JSDeclarationKindNone { return getContextNode(binaryExpression) } } @@ -293,6 +294,31 @@ func isForRenameWithPrefixAndSuffixText(options refOptions) bool { return options.use == referenceUseRename && options.providePrefixAndSuffixTextForRename } +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. @@ -646,16 +672,14 @@ func getReferencesForThisKeyword(thisOrSuperKeyword *ast.Node, sourceFiles []*as return node.Kind == ast.KindIdentifier && node.Parent.Kind == ast.KindParameter && node.Parent.Name() == node } - // !!! check fall throughs switch searchSpaceNode.Kind { - case ast.KindMethodDeclaration, ast.KindMethodSignature: - if ast.IsObjectLiteralMethod(searchSpaceNode) { + 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 } - // falls through - case ast.KindPropertyDeclaration, ast.KindPropertySignature, ast.KindConstructor, ast.KindGetAccessor, ast.KindSetAccessor: staticFlag &= searchSpaceNode.ModifierFlags() searchSpaceNode = searchSpaceNode.Parent // re-assign to be the owning class break @@ -663,7 +687,6 @@ func getReferencesForThisKeyword(thisOrSuperKeyword *ast.Node, sourceFiles []*as if ast.IsExternalModule(searchSpaceNode.AsSourceFile()) || isParameterName(thisOrSuperKeyword) { return nil } - // falls through case ast.KindFunctionDeclaration, ast.KindFunctionExpression: break // Computed properties in classes are not handled here because references to this are illegal, diff --git a/internal/ls/utilities.go b/internal/ls/utilities.go index 3f807ef69b..9868bce5a0 100644 --- a/internal/ls/utilities.go +++ b/internal/ls/utilities.go @@ -14,21 +14,21 @@ import ( ) // p.Compare(other) == cmp.Compare(p, other) -func ComparePositions(p, other lsproto.Position) int { - if lineComp := cmp.Compare(p.Line, other.Line); lineComp != 0 { +func ComparePositions(pos, other lsproto.Position) int { + if lineComp := cmp.Compare(pos.Line, other.Line); lineComp != 0 { return lineComp } - return cmp.Compare(p.Line, other.Line) + return cmp.Compare(pos.Line, other.Line) } // t.Compare(other) == cmp.Compare(t, other) // // compares Range.Start and then Range.End -func CompareRanges(t, other *lsproto.Range) int { - if startComp := ComparePositions(t.Start, other.Start); startComp != 0 { +func CompareRanges(lsRange, other *lsproto.Range) int { + if startComp := ComparePositions(lsRange.Start, other.Start); startComp != 0 { return startComp } - return ComparePositions(t.End, other.End) + return ComparePositions(lsRange.End, other.End) } var quoteReplacer = strings.NewReplacer("'", `\'`, `\"`, `"`) @@ -38,31 +38,6 @@ func isInString(file *ast.SourceFile, position int, previousToken *ast.Node) boo return false } -func skipPastExportOrImportSpecifierOrUnion(originalSymbol *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(), originalSymbol, 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(originalSymbol.Declarations, func(decl *ast.Node) *ast.Symbol { - if decl.Parent == nil { - // Ignore UMD module and global merge - if originalSymbol.Flags&ast.SymbolFlagsTransient != 0 { - return nil - } - // Assertions for GH#21814. We should be handling SourceFile symbols in `getReferencedSymbolsForModule` instead of getting here. - panic(`Unexpected symbol at ${Debug.formatast.Kind(node.Kind)}: ${Debug.formatSymbol(symbol)}`) - } - if decl.Parent.Kind == ast.KindTypeLiteral && decl.Parent.Parent.Kind == ast.KindUnionType { - return checker.GetPropertyOfType(checker.GetTypeFromTypeNode(decl.Parent.Parent), originalSymbol.Name) - } - return nil - }) -} - func tryGetImportFromModuleSpecifier(node *ast.StringLiteralLike) *ast.Node { switch node.Parent.Kind { case ast.KindImportDeclaration, ast.KindJSImportDeclaration, ast.KindExportDeclaration: @@ -87,8 +62,6 @@ func tryGetImportFromModuleSpecifier(node *ast.StringLiteralLike) *ast.Node { } func isModuleSpecifierLike(node *ast.Node) bool { - // return node.Kind == ast.KindStringLiteral || node.Kind == ast.KindNoSubstitutionTemplateLiteral || - // node.Kind == ast.KindTemplateHead || node.Kind == ast.KindTemplateMiddle || node.Kind == ast.KindTemplateTail if !ast.IsStringLiteralLike(node) { return false } @@ -1632,7 +1605,7 @@ func getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol *ast.Symb } func getSourceOfDefaultedAssignment(node *ast.Node) *ast.Node { - if node.Kind == ast.KindExpressionStatement && node.Expression().Kind == ast.KindBinaryExpression && ast.GetAssignmentDeclarationKind(node.Expression()) != ast.AssignmentDeclarationKindNone { + if node.Kind == ast.KindExpressionStatement && node.Expression().Kind == ast.KindBinaryExpression && ast.GetAssignmentDeclarationKind(node.Expression().AsBinaryExpression()) != ast.JSDeclarationKindNone { binExprRight := node.Expression().AsBinaryExpression().Right if binExprRight.Kind == ast.KindBinaryExpression { binExprDefault := binExprRight.AsBinaryExpression() diff --git a/internal/parser/reparser.go b/internal/parser/reparser.go index 4c23240938..041fd45f76 100644 --- a/internal/parser/reparser.go +++ b/internal/parser/reparser.go @@ -13,7 +13,7 @@ func (p *Parser) reparseCommonJS(node *ast.Node) { return } bin := node.AsExpressionStatement().Expression.AsBinaryExpression() - kind := ast.GetJSDocAssignmentDeclarationKind(bin) + kind := ast.GetAssignmentDeclarationKind(bin) var export *ast.Node switch kind { case ast.JSDeclarationKindModuleExports: @@ -136,7 +136,7 @@ func (p *Parser) reparseTags(parent *ast.Node, jsDoc []*ast.Node) { } else if parent.Kind == ast.KindExpressionStatement && parent.AsExpressionStatement().Expression.Kind == ast.KindBinaryExpression { bin := parent.AsExpressionStatement().Expression.AsBinaryExpression() - if ast.GetJSDocAssignmentDeclarationKind(bin) != ast.JSDeclarationKindNone { + if ast.GetAssignmentDeclarationKind(bin) != ast.JSDeclarationKindNone { bin.Right = p.makeNewTypeAssertion(p.makeNewType(tag.AsJSDocTypeTag().TypeExpression, nil), bin.Right) } } From bb63fd64bbceb9e90edc3c9b8455cc59c7a86494 Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Tue, 20 May 2025 13:23:59 -0700 Subject: [PATCH 05/13] use set as node tracker --- internal/core/set.go | 11 +++++++++++ internal/ls/findallreferences.go | 32 ++++++-------------------------- internal/ls/utilities.go | 4 ++-- 3 files changed, 19 insertions(+), 28 deletions(-) diff --git a/internal/core/set.go b/internal/core/set.go index 3d094cc2ac..8c776266f0 100644 --- a/internal/core/set.go +++ b/internal/core/set.go @@ -48,6 +48,17 @@ func (s *Set[T]) AddIfAbsent(key T) bool { return true } +// Returns true if the key is arleady in the set. Adds the key and returns false otherwise. +// +// "marks" keys for in "seen" sets in services +func (s *Set[T]) HasAndAdd(key T) bool { + if s.Has(key) { + return true + } + s.Add(key) + return false +} + func NewSetFromItems[T comparable](items ...T) *Set[T] { s := &Set[T]{} for _, item := range items { diff --git a/internal/ls/findallreferences.go b/internal/ls/findallreferences.go index b5931fdb7a..c89f50f3e5 100644 --- a/internal/ls/findallreferences.go +++ b/internal/ls/findallreferences.go @@ -1005,26 +1005,6 @@ type refSearch struct { // } // ) -type seenTracker[T comparable] struct { - m core.Set[T] -} - -func (t *seenTracker[T]) mark(key T) bool { - if t.m.Has(key) { - return true - } - t.m.Add(key) - return false -} - -func (t *seenTracker[T]) addToSeen(key T) bool { - if t.m.Has(key) { - return false - } - t.m.Add(key) - return true -} - type inheritKey struct { symbol, parent ast.SymbolId } @@ -1040,9 +1020,9 @@ type refState struct { result []*SymbolAndEntries inheritsFromCache map[inheritKey]bool - seenContainingTypeReferences seenTracker[*ast.Node] // node seen tracker - // seenReExportRHS seenTracker[*ast.Node] // node seen tracker - // importTracker ImportTracker + 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] } @@ -1057,8 +1037,8 @@ func newState(sourceFiles []*ast.SourceFile, sourceFilesSet *core.Set[string], n options: options, result: []*SymbolAndEntries{}, inheritsFromCache: map[inheritKey]bool{}, - seenContainingTypeReferences: seenTracker[*ast.Node]{}, - // seenReExportRHS: seenTracker[*ast.Node]{}, + seenContainingTypeReferences: &core.Set[*ast.Node]{}, + // seenReExportRHS: &core.Set[*ast.Node]{}, symbolIdToReferences: map[ast.SymbolId]*SymbolAndEntries{}, sourceFileToSeenSymbols: map[ast.NodeId]*core.Set[ast.SymbolId]{}, } @@ -1158,7 +1138,7 @@ func (state *refState) addImplementationReferences(refNode *ast.Node, addRef fun } typeHavingNode := typeNode.Parent - if typeHavingNode.Type() == typeNode && state.seenContainingTypeReferences.mark(typeHavingNode) { + if typeHavingNode.Type() == typeNode && state.seenContainingTypeReferences.HasAndAdd(typeHavingNode) { addIfImplementation := func(e *ast.Expression) { if isImplementationExpression(e) { addRef(e) diff --git a/internal/ls/utilities.go b/internal/ls/utilities.go index 9868bce5a0..f559fe6842 100644 --- a/internal/ls/utilities.go +++ b/internal/ls/utilities.go @@ -1558,14 +1558,14 @@ func getParentSymbolsOfPropertyAccess(location *ast.Node, symbol *ast.Symbol, ch * 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 := seenTracker[*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.addToSeen(symbol) { + if symbol.Flags&(ast.SymbolFlagsClass|ast.SymbolFlagsInterface) == 0 || !seen.AddIfAbsent(symbol) { return nil } From 362255d267b6169b43cad189e4ecda21696edf1b Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Thu, 29 May 2025 12:25:14 -0700 Subject: [PATCH 06/13] fix errors --- internal/ast/utilities.go | 11 +- internal/checker/checker.go | 10 +- internal/checker/exports.go | 5 +- internal/checker/printer.go | 541 -------------------------- internal/checker/utilities.go | 9 - internal/compiler/program.go | 8 +- internal/ls/findallreferences.go | 41 +- internal/ls/findallreferences_test.go | 2 +- internal/ls/utilities.go | 2 +- 9 files changed, 38 insertions(+), 591 deletions(-) diff --git a/internal/ast/utilities.go b/internal/ast/utilities.go index 1176ecb748..fa3ddf98fe 100644 --- a/internal/ast/utilities.go +++ b/internal/ast/utilities.go @@ -1540,7 +1540,7 @@ func GetAssignmentDeclarationPropertyAccessKind(lhs *Node) JSDeclarationKind { nextToLast = nextToLast.Expression() } idText := nextToLast.Expression().AsIdentifier().Text - if (idText == "exports" || idText == "module" && GetElementOrPropertyAccessName(nextToLast) == "exports") && + if (idText == "exports" || idText == "module" && getElementOrPropertyAccessNameText(nextToLast) == "exports") && // ExportsProperty does not support binding with computed names IsBindableStaticAccessExpression(lhs, false) { // exports.name = expr OR module.exports.name = expr OR exports["name"] = expr ... @@ -1554,6 +1554,13 @@ func GetAssignmentDeclarationPropertyAccessKind(lhs *Node) JSDeclarationKind { return JSDeclarationKindNone } +func getElementOrPropertyAccessNameText(node *Node) string { + if name := GetElementOrPropertyAccessName(node); name != nil { + return name.Text() + } + return "" +} + /** * A declaration has a dynamic name if all of the following are true: * 1. The declaration has a computed property name. @@ -2601,7 +2608,7 @@ func GetDeclarationContainer(node *Node) *Node { } func IsPrototypeAccess(node *Node) bool { - return IsBindableStaticAccessExpression(node, false) && GetElementOrPropertyAccessName(node) == "prototype" + return IsBindableStaticAccessExpression(node, false) && getElementOrPropertyAccessNameText(node) == "prototype" } // Indicates that a symbol is an alias that does not merge with a local declaration. diff --git a/internal/checker/checker.go b/internal/checker/checker.go index ca807caf40..ef68324e04 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -13661,11 +13661,7 @@ func (c *Checker) getTargetOfImportEqualsDeclaration(node *ast.Node, dontResolve if ast.IsVariableDeclaration(node) || node.AsImportEqualsDeclaration().ModuleReference.Kind == ast.KindExternalModuleReference { moduleReference := getExternalModuleRequireArgument(node) if moduleReference == nil { -<<<<<<< HEAD - moduleReference = GetExternalModuleImportEqualsDeclarationExpression(node) -======= moduleReference = ast.GetExternalModuleImportEqualsDeclarationExpression(node) ->>>>>>> 360255e646e7e1e0b8930bff7f611fd67d04e9d8 } immediate := c.resolveExternalModuleName(node, moduleReference, false /*ignoreErrors*/) resolved := c.resolveExternalModuleSymbol(immediate, false /*dontResolveAlias*/) @@ -15205,7 +15201,7 @@ func (c *Checker) GetTypeOfSymbolAtLocation(symbol *ast.Symbol, location *ast.No // of the expression (which will reflect control flow analysis). If the expression indeed // resolved to the given symbol, return the narrowed type. if ast.IsIdentifier(location) || ast.IsPrivateIdentifier(location) { - if isRightSideOfQualifiedNameOrPropertyAccess(location) { + if IsRightSideOfQualifiedNameOrPropertyAccess(location) { location = location.Parent } if ast.IsExpressionNode(location) && (!ast.IsAssignmentTarget(location) || isWriteAccess(location)) { @@ -29585,11 +29581,7 @@ func (c *Checker) getSymbolAtLocation(node *ast.Node, ignoreErrors bool) *ast.Sy // 2). External module name in an import declaration // 3). Require in Javascript // 4). type A = import("./f/*gotToDefinitionHere*/oo") -<<<<<<< HEAD - if (ast.IsExternalModuleImportEqualsDeclaration(grandParent) && GetExternalModuleImportEqualsDeclarationExpression(grandParent) == node) || -======= if (ast.IsExternalModuleImportEqualsDeclaration(grandParent) && ast.GetExternalModuleImportEqualsDeclarationExpression(grandParent) == node) || ->>>>>>> 360255e646e7e1e0b8930bff7f611fd67d04e9d8 ((parent.Kind == ast.KindImportDeclaration || parent.Kind == ast.KindJSImportDeclaration || parent.Kind == ast.KindExportDeclaration) && parent.AsImportDeclaration().ModuleSpecifier == node) || ast.IsVariableDeclarationInitializedToRequire(grandParent) || (ast.IsLiteralTypeNode(parent) && ast.IsLiteralImportTypeNode(grandParent) && grandParent.AsImportTypeNode().Argument == parent) { diff --git a/internal/checker/exports.go b/internal/checker/exports.go index 09a0705300..fea700c55a 100644 --- a/internal/checker/exports.go +++ b/internal/checker/exports.go @@ -6,13 +6,12 @@ import ( "github.com/microsoft/typescript-go/internal/diagnostics" ) -<<<<<<< HEAD func (c *Checker) GetStringType() *Type { return c.stringType -======= +} + func (c *Checker) GetUnknownSymbol() *ast.Symbol { return c.unknownSymbol ->>>>>>> 360255e646e7e1e0b8930bff7f611fd67d04e9d8 } func (c *Checker) GetUnionType(types []*Type) *Type { diff --git a/internal/checker/printer.go b/internal/checker/printer.go index 8d7e0b1698..e9a452c8b6 100644 --- a/internal/checker/printer.go +++ b/internal/checker/printer.go @@ -355,549 +355,8 @@ func (c *Checker) WriteSignature(s *Signature, enclosingDeclaration *ast.Node, f return c.signatureToStringEx(s, enclosingDeclaration, flags, writer) } -<<<<<<< HEAD -func (p *Printer) print(s string) { - p.sb.WriteString(s) -} - -func (p *Printer) printName(symbol *ast.Symbol) { - p.print(p.c.symbolToString(symbol)) -} - -func (p *Printer) printQualifiedName(symbol *ast.Symbol) { - if p.flags&TypeFormatFlagsUseFullyQualifiedType != 0 && symbol.Parent != nil { - p.printQualifiedName(symbol.Parent) - p.print(".") - } - if symbol.Flags&ast.SymbolFlagsModule != 0 && strings.HasPrefix(symbol.Name, "\"") { - p.print("import(") - p.print(symbol.Name) - p.print(")") - return - } - p.printName(symbol) -} - -func (p *Printer) printTypeEx(t *Type, precedence ast.TypePrecedence) { - if p.c.getTypePrecedence(t) < precedence { - p.print("(") - p.printType(t) - p.print(")") - } else { - p.printType(t) - } -} - -func (p *Printer) printType(t *Type) { - if p.sb.Len() > 1_000_000 { - p.print("...") - return - } - - if t.alias != nil && (p.flags&TypeFormatFlagsInTypeAlias == 0 || p.depth > 0) { - p.printQualifiedName(t.alias.symbol) - p.printTypeArguments(t.alias.typeArguments) - } else { - p.printTypeNoAlias(t) - } -} - -func (p *Printer) printTypeNoAlias(t *Type) { - p.depth++ - switch { - case t.flags&TypeFlagsIntrinsic != 0: - p.print(t.AsIntrinsicType().intrinsicName) - case t.flags&(TypeFlagsLiteral|TypeFlagsEnum) != 0: - p.printLiteralType(t) - case t.flags&TypeFlagsUniqueESSymbol != 0: - p.printUniqueESSymbolType(t) - case t.flags&TypeFlagsUnion != 0: - p.printUnionType(t) - case t.flags&TypeFlagsIntersection != 0: - p.printIntersectionType(t) - case t.flags&TypeFlagsTypeParameter != 0: - p.printTypeParameter(t) - case t.flags&TypeFlagsObject != 0: - p.printRecursive(t, (*Printer).printObjectType) - case t.flags&TypeFlagsIndex != 0: - p.printRecursive(t, (*Printer).printIndexType) - case t.flags&TypeFlagsIndexedAccess != 0: - p.printRecursive(t, (*Printer).printIndexedAccessType) - case t.flags&TypeFlagsConditional != 0: - p.printRecursive(t, (*Printer).printConditionalType) - case t.flags&TypeFlagsTemplateLiteral != 0: - p.printTemplateLiteralType(t) - case t.flags&TypeFlagsStringMapping != 0: - p.printStringMappingType(t) - case t.flags&TypeFlagsSubstitution != 0: - if p.c.isNoInferType(t) { - if noInferSymbol := p.c.getGlobalNoInferSymbolOrNil(); noInferSymbol != nil { - p.printQualifiedName(noInferSymbol) - p.printTypeArguments([]*Type{t.AsSubstitutionType().baseType}) - break - } - } - p.printType(t.AsSubstitutionType().baseType) - } - p.depth-- -} - -func (p *Printer) printRecursive(t *Type, f func(*Printer, *Type)) { - if !p.printing.Has(t) && p.depth < 10 { - p.printing.Add(t) - f(p, t) - p.printing.Delete(t) - } else { - p.print("???") - } -} - -func (p *Printer) printLiteralType(t *Type) { - if t.flags&(TypeFlagsEnumLiteral|TypeFlagsEnum) != 0 { - p.printEnumLiteral(t) - } else { - p.printValue(t.AsLiteralType().value) - } -} - -func (p *Printer) printValue(value any) { - switch value := value.(type) { - case string: - p.printStringLiteral(value) - case jsnum.Number: - p.printNumberLiteral(value) - case bool: - p.printBooleanLiteral(value) - case jsnum.PseudoBigInt: - p.printBigIntLiteral(value) - } -} - -func (p *Printer) printStringLiteral(s string) { - p.print("\"") - p.print(printer.EscapeString(s, '"')) - p.print("\"") -} - -func (p *Printer) printNumberLiteral(f jsnum.Number) { - p.print(f.String()) -} - -func (p *Printer) printBooleanLiteral(b bool) { - p.print(core.IfElse(b, "true", "false")) -} - -func (p *Printer) printBigIntLiteral(b jsnum.PseudoBigInt) { - p.print(b.String() + "n") -} - -func (p *Printer) printUniqueESSymbolType(t *Type) { - p.print("unique symbol") -} - -func (p *Printer) printTemplateLiteralType(t *Type) { - texts := t.AsTemplateLiteralType().texts - types := t.AsTemplateLiteralType().types - p.print("`") - p.print(texts[0]) - for i, t := range types { - p.print("${") - p.printType(t) - p.print("}") - p.print(texts[i+1]) - } - p.print("`") -} - -func (p *Printer) printStringMappingType(t *Type) { - p.printName(t.symbol) - p.print("<") - p.printType(t.AsStringMappingType().target) - p.print(">") -} - -func (p *Printer) printEnumLiteral(t *Type) { - if parent := p.c.getParentOfSymbol(t.symbol); parent != nil { - p.printQualifiedName(parent) - if p.c.getDeclaredTypeOfSymbol(parent) != t { - p.print(".") - p.printName(t.symbol) - } - return - } - p.printQualifiedName(t.symbol) -} - -func (p *Printer) printObjectType(t *Type) { - switch { - case t.objectFlags&ObjectFlagsReference != 0: - p.printParameterizedType(t) - case t.objectFlags&ObjectFlagsClassOrInterface != 0: - p.printQualifiedName(t.symbol) - case p.c.isGenericMappedType(t) || t.objectFlags&ObjectFlagsMapped != 0 && t.AsMappedType().containsError: - p.printMappedType(t) - default: - p.printAnonymousType(t) - } -} - -func (p *Printer) printParameterizedType(t *Type) { - switch { - case p.c.isArrayType(t) && p.flags&TypeFormatFlagsWriteArrayAsGenericType == 0: - p.printArrayType(t) - case isTupleType(t): - p.printTupleType(t) - default: - p.printTypeReference(t) - } -} - -func (p *Printer) printTypeReference(t *Type) { - p.printQualifiedName(t.symbol) - p.printTypeArguments(p.c.getTypeArguments(t)[:p.c.getTypeReferenceArity(t)]) -} - -func (p *Printer) printTypeArguments(typeArguments []*Type) { - if len(typeArguments) != 0 { - p.print("<") - var tail bool - for _, t := range typeArguments { - if tail { - p.print(", ") - } - p.printType(t) - tail = true - } - p.print(">") - } -} - -func (p *Printer) printTypeParameters(typeParameters []*Type) { - if len(typeParameters) != 0 { - p.print("<") - var tail bool - for _, tp := range typeParameters { - if tail { - p.print(", ") - } - p.printTypeParameterAndConstraint(tp) - tail = true - } - p.print(">") - } -} - -func (p *Printer) printArrayType(t *Type) { - d := t.AsTypeReference() - if d.target != p.c.globalArrayType { - p.print("readonly ") - } - p.printTypeEx(p.c.getTypeArguments(t)[0], ast.TypePrecedencePostfix) - p.print("[]") -} - -func (p *Printer) printTupleType(t *Type) { - if t.TargetTupleType().readonly { - p.print("readonly ") - } - p.print("[") - elementInfos := t.TargetTupleType().elementInfos - typeArguments := p.c.getTypeArguments(t) - var tail bool - for i, info := range elementInfos { - t := typeArguments[i] - if tail { - p.print(", ") - } - if info.flags&ElementFlagsVariable != 0 { - p.print("...") - } - if info.labeledDeclaration != nil { - p.print(info.labeledDeclaration.Name().Text()) - if info.flags&ElementFlagsOptional != 0 { - p.print("?: ") - p.printType(p.c.removeMissingType(t, true)) - } else { - p.print(": ") - if info.flags&ElementFlagsRest != 0 { - p.printTypeEx(t, ast.TypePrecedencePostfix) - p.print("[]") - } else { - p.printType(t) - } - } - } else { - if info.flags&ElementFlagsOptional != 0 { - p.printTypeEx(p.c.removeMissingType(t, true), ast.TypePrecedencePostfix) - p.print("?") - } else if info.flags&ElementFlagsRest != 0 { - p.printTypeEx(t, ast.TypePrecedencePostfix) - p.print("[]") - } else { - p.printType(t) - } - } - tail = true - } - p.print("]") -} - -func (p *Printer) printAnonymousType(t *Type) { - if t.symbol != nil && len(t.symbol.Name) != 0 { - if t.symbol.Flags&(ast.SymbolFlagsClass|ast.SymbolFlagsEnum|ast.SymbolFlagsValueModule) != 0 { - if t == p.c.getTypeOfSymbol(t.symbol) { - p.print("typeof ") - p.printQualifiedName(t.symbol) - return - } - } - } - props := p.c.getPropertiesOfObjectType(t) - callSignatures := p.c.getSignaturesOfType(t, SignatureKindCall) - constructSignatures := p.c.getSignaturesOfType(t, SignatureKindConstruct) - if len(props) == 0 { - if len(callSignatures) == 1 && len(constructSignatures) == 0 { - p.printSignature(callSignatures[0], " => ") - return - } - if len(callSignatures) == 0 && len(constructSignatures) == 1 { - p.print("new ") - p.printSignature(constructSignatures[0], " => ") - return - } - } - p.print("{") - hasMembers := false - for _, sig := range callSignatures { - p.print(" ") - p.printSignature(sig, ": ") - p.print(";") - hasMembers = true - } - for _, sig := range constructSignatures { - p.print(" new ") - p.printSignature(sig, ": ") - p.print(";") - hasMembers = true - } - for _, info := range p.c.getIndexInfosOfType(t) { - if info.isReadonly { - p.print(" readonly") - } - p.print(" [") - p.print(getNameFromIndexInfo(info)) - p.print(": ") - p.printType(info.keyType) - p.print("]: ") - p.printType(info.valueType) - p.print(";") - hasMembers = true - } - for _, prop := range props { - if p.c.isReadonlySymbol(prop) { - p.print(" readonly") - } - p.print(" ") - p.printName(prop) - if prop.Flags&ast.SymbolFlagsOptional != 0 { - p.print("?") - } - p.print(": ") - p.printType(p.c.getNonMissingTypeOfSymbol(prop)) - p.print(";") - hasMembers = true - } - if hasMembers { - p.print(" ") - } - p.print("}") -} - -func (p *Printer) printSignature(sig *Signature, returnSeparator string) { - p.printTypeParameters(sig.typeParameters) - p.print("(") - var tail bool - if sig.thisParameter != nil { - p.print("this: ") - p.printType(p.c.getTypeOfSymbol(sig.thisParameter)) - tail = true - } - expandedParameters := p.c.GetExpandedParameters(sig) - // If the expanded parameter list had a variadic in a non-trailing position, don't expand it - parameters := core.IfElse(core.Some(expandedParameters, func(s *ast.Symbol) bool { - return s != expandedParameters[len(expandedParameters)-1] && s.CheckFlags&ast.CheckFlagsRestParameter != 0 - }), sig.parameters, expandedParameters) - for i, param := range parameters { - if tail { - p.print(", ") - } - if param.ValueDeclaration != nil && isRestParameter(param.ValueDeclaration) || param.CheckFlags&ast.CheckFlagsRestParameter != 0 { - p.print("...") - p.printName(param) - } else { - p.printName(param) - if i >= p.c.getMinArgumentCountEx(sig, MinArgumentCountFlagsVoidIsNonOptional) { - p.print("?") - } - } - p.print(": ") - p.printType(p.c.getTypeOfSymbol(param)) - tail = true - } - p.print(")") - p.print(returnSeparator) - if pred := p.c.getTypePredicateOfSignature(sig); pred != nil { - p.printTypePredicate(pred) - } else { - p.printType(p.c.getReturnTypeOfSignature(sig)) - } -} - -func (p *Printer) printTypePredicate(pred *TypePredicate) { - if pred.kind == TypePredicateKindAssertsThis || pred.kind == TypePredicateKindAssertsIdentifier { - p.print("asserts ") - } - if pred.kind == TypePredicateKindThis || pred.kind == TypePredicateKindAssertsThis { - p.print("this") - } else { - p.print(pred.parameterName) - } - if pred.t != nil { - p.print(" is ") - p.printType(pred.t) - } -} - -func (p *Printer) printTypeParameter(t *Type) { - switch { - case t.AsTypeParameter().isThisType: - p.print("this") - case p.extendsTypeDepth > 0 && isInferTypeParameter(t): - p.print("infer ") - p.printTypeParameterAndConstraint(t) - case t.symbol != nil: - p.printName(t.symbol) - default: - p.print("???") - } -} - -func (p *Printer) printTypeParameterAndConstraint(t *Type) { - p.printName(t.symbol) - if constraint := p.c.getConstraintOfTypeParameter(t); constraint != nil { - p.print(" extends ") - p.printType(constraint) - } -} - -func (p *Printer) printUnionType(t *Type) { - switch { - case t.flags&TypeFlagsBoolean != 0: - p.print("boolean") - case t.flags&TypeFlagsEnumLiteral != 0: - p.printQualifiedName(t.symbol) - default: - u := t.AsUnionType() - if u.origin != nil { - p.printType(u.origin) - } else { - var tail bool - for _, t := range p.c.formatUnionTypes(u.types) { - if tail { - p.print(" | ") - } - p.printTypeEx(t, ast.TypePrecedenceUnion) - tail = true - } - } - } -} - -func (p *Printer) printIntersectionType(t *Type) { - var tail bool - for _, t := range t.AsIntersectionType().types { - if tail { - p.print(" & ") - } - p.printTypeEx(t, ast.TypePrecedenceIntersection) - tail = true - } -} - -func (p *Printer) printIndexType(t *Type) { - p.print("keyof ") - p.printTypeEx(t.AsIndexType().target, ast.TypePrecedenceTypeOperator) -} - -func (p *Printer) printIndexedAccessType(t *Type) { - p.printType(t.AsIndexedAccessType().objectType) - p.print("[") - p.printType(t.AsIndexedAccessType().indexType) - p.print("]") -} - -func (p *Printer) printConditionalType(t *Type) { - p.printType(t.AsConditionalType().checkType) - p.print(" extends ") - p.extendsTypeDepth++ - p.printType(t.AsConditionalType().extendsType) - p.extendsTypeDepth-- - p.print(" ? ") - p.printType(p.c.getTrueTypeFromConditionalType(t)) - p.print(" : ") - p.printType(p.c.getFalseTypeFromConditionalType(t)) -} - -func (p *Printer) printMappedType(t *Type) { - d := t.AsMappedType().declaration - p.print("{ ") - if d.ReadonlyToken != nil { - if d.ReadonlyToken.Kind != ast.KindReadonlyKeyword { - p.print(scanner.TokenToString(d.ReadonlyToken.Kind)) - } - p.print("readonly ") - } - p.print("[") - p.printName(p.c.getTypeParameterFromMappedType(t).symbol) - p.print(" in ") - p.printType(p.c.getConstraintTypeFromMappedType(t)) - nameType := p.c.getNameTypeFromMappedType(t) - if nameType != nil { - p.print(" as ") - p.printType(nameType) - } - p.print("]") - if d.QuestionToken != nil { - if d.QuestionToken.Kind != ast.KindQuestionToken { - p.print(scanner.TokenToString(d.QuestionToken.Kind)) - } - p.print("?") - } - p.print(": ") - p.printType(p.c.getTemplateTypeFromMappedType(t)) - p.print("; }") -} - -func (c *Checker) getTextAndTypeOfNode(node *ast.Node) (string, *Type, bool) { - if ast.IsDeclarationNode(node) { - symbol := node.Symbol() - if symbol != nil && !isReservedMemberName(symbol.Name) { - if symbol.Flags&ast.SymbolFlagsValue != 0 { - return c.symbolToString(symbol), c.getTypeOfSymbol(symbol), true - } - if symbol.Flags&ast.SymbolFlagsTypeAlias != 0 { - return c.symbolToString(symbol), c.getDeclaredTypeOfTypeAlias(symbol), true - } - } - } - if ast.IsExpressionNode(node) && !IsRightSideOfQualifiedNameOrPropertyAccess(node) { - return scanner.GetTextOfNode(node), c.getTypeOfExpression(node), false - } - return "", nil, false -======= func (c *Checker) WriteTypePredicate(p *TypePredicate, enclosingDeclaration *ast.Node, flags TypeFormatFlags, writer printer.EmitTextWriter) string { return c.typePredicateToStringEx(p, enclosingDeclaration, flags, writer) ->>>>>>> 360255e646e7e1e0b8930bff7f611fd67d04e9d8 } func (c *Checker) formatUnionTypes(types []*Type) []*Type { diff --git a/internal/checker/utilities.go b/internal/checker/utilities.go index fa6dff2fdb..982c539e55 100644 --- a/internal/checker/utilities.go +++ b/internal/checker/utilities.go @@ -345,16 +345,7 @@ func getExternalModuleRequireArgument(node *ast.Node) *ast.Node { return nil } -<<<<<<< HEAD -func GetExternalModuleImportEqualsDeclarationExpression(node *ast.Node) *ast.Node { - // Debug.assert(isExternalModuleImportEqualsDeclaration(node)) - return node.AsImportEqualsDeclaration().ModuleReference.AsExternalModuleReference().Expression -} - func IsRightSideOfQualifiedNameOrPropertyAccess(node *ast.Node) bool { -======= -func isRightSideOfQualifiedNameOrPropertyAccess(node *ast.Node) bool { ->>>>>>> 360255e646e7e1e0b8930bff7f611fd67d04e9d8 parent := node.Parent switch parent.Kind { case ast.KindQualifiedName: diff --git a/internal/compiler/program.go b/internal/compiler/program.go index 6390308ea5..a9b29c452e 100644 --- a/internal/compiler/program.go +++ b/internal/compiler/program.go @@ -958,10 +958,10 @@ func (p *Program) getModeForTypeReferenceDirectiveInFile(ref *ast.FileReference, } /** This should have similar behavior to 'processSourceFile' without diagnostics or mutation. */ -func (p *Program) GetSourceFileFromReference(referencingFile *ast.SourceFile, ref *ast.FileReference) *ast.SourceFile { - return p.getSourceFileFromReferenceWorker(tspath.ResolveTripleslashReference(ref.FileName, referencingFile.FileName()), p.GetSourceFile, nil /*fail*/) -} - +// func (p *Program) GetSourceFileFromReference(referencingFile *ast.SourceFile, ref *ast.FileReference) *ast.SourceFile { +// return p.getSourceFileFromReferenceWorker(tspath.ResolveTripleslashReference(ref.FileName, referencingFile.FileName()), p.GetSourceFile, nil /*fail*/) +// } +// !!! todo: check against new `getSourceFileFromReference` func (p *Program) getSourceFileFromReferenceWorker( fileName string, getSourceFile func(fileName string) *ast.SourceFile, diff --git a/internal/ls/findallreferences.go b/internal/ls/findallreferences.go index c89f50f3e5..dd3eaa177f 100644 --- a/internal/ls/findallreferences.go +++ b/internal/ls/findallreferences.go @@ -14,6 +14,9 @@ import ( "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/lsp/lsproto" "github.com/microsoft/typescript-go/internal/scanner" + + "unicode/utf8" + "github.com/microsoft/typescript-go/internal/tspath" ) @@ -144,9 +147,10 @@ func getContextNodeForNodeEntry(node *ast.Node) *ast.Node { return nil } - if !ast.IsDeclaration(node.Parent) && node.Parent.Kind != ast.KindExportAssignment { + 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, @@ -187,6 +191,7 @@ func getContextNodeForNodeEntry(node *ast.Node) *ast.Node { 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 @@ -377,11 +382,6 @@ func getSymbolScope(symbol *ast.Symbol) *ast.Node { } scope = container - if scope.Kind == ast.KindFunctionExpression { - for next := getNextJSDocCommentLocation(scope); next != nil; next = getNextJSDocCommentLocation(scope) { - scope = next - } - } } // If symbol.parent, this means we are in an export of an external module. (Otherwise we would have returned `undefined` above.) @@ -580,7 +580,7 @@ func (l *LanguageService) getReferencedSymbolsForModuleIfDeclaredBySourceFile(sy return nil } exportEquals := symbol.Exports[ast.InternalSymbolNameExportEquals] - // If !!exportEquals, we're about to add references to `import("mod")` anyway, so don't double-count them. + // 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 @@ -893,7 +893,7 @@ func getReferenceAtPosition(sourceFile *ast.SourceFile, position int, program *c return nil } - if len(sourceFile.Imports) == 0 && len(sourceFile.ModuleAugmentations) == 0 { + if len(sourceFile.Imports()) == 0 && len(sourceFile.ModuleAugmentations) == 0 { return nil } @@ -942,7 +942,7 @@ func getReferencedSymbolsForSymbol(originalSymbol *ast.Symbol, node *ast.Node, s // !!! 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.providePrefixAndSuffixTextForRename, !!options.implementations)) + search := state.createSearch(node, symbol, comingFromUnknown /*comingFrom*/, "", state.populateSearchSymbolSet(symbol, node, options.use == referenceUseRename, options.providePrefixAndSuffixTextForRename, options.implementations)) state.getReferencesInContainerOrFiles(symbol, search) } @@ -1059,7 +1059,15 @@ func (state *refState) createSearch(location *ast.Node, symbol *ast.Symbol, comi symbolToSearchFor = symbol } } - text = core.StripQuotes(ast.SymbolName(symbolToSearchFor)) + 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} @@ -1261,14 +1269,6 @@ func (state *refState) getReferencesAtLocation(sourceFile *ast.SourceFile, posit return } - // !!! not implemented - // if isJSDocPropertyLikeTag(parent) && parent.isNameFirst && - // parent.TypeExpression && isJSDocTypeLiteral(parent.TypeExpression.Type()) && - // parent.TypeExpression.Type().jsDocPropertyTags && length(parent.TypeExpression.Type().jsDocPropertyTags) { - // getReferencesAtJSDocTypeLiteral(parent.TypeExpression.Type().jsDocPropertyTags, referenceLocation, search, state); - // return; - // } - relatedSymbol, relatedSymbolKind := state.getRelatedSymbol(search, referenceSymbol, referenceLocation) if relatedSymbol == nil { state.getReferenceForShorthandProperty(referenceSymbol, search) @@ -1291,7 +1291,6 @@ func (state *refState) getReferencesAtLocation(sourceFile *ast.SourceFile, posit // 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) { - // !!! when findAllReferences has been fully implemented, check if the behavior is the same since isVariableDeclarationInitializedToBareOrAccessedRequire has been removed 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. @@ -1354,7 +1353,7 @@ func (state *refState) getRelatedSymbol(search *refSearch, referenceSymbol *ast. referenceSymbol, referenceLocation, false, /*isForRenamePopulateSearchSymbolSet*/ - state.options.use != referenceUseRename || !!state.options.providePrefixAndSuffixTextForRename, /*onlyIncludeBindingElementAtReferenceLocation*/ + state.options.use != referenceUseRename || state.options.providePrefixAndSuffixTextForRename, /*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 { @@ -1438,7 +1437,7 @@ func (state *refState) forEachRelatedSymbol( 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] + // 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") } diff --git a/internal/ls/findallreferences_test.go b/internal/ls/findallreferences_test.go index b7469cf9a7..bdddb69034 100644 --- a/internal/ls/findallreferences_test.go +++ b/internal/ls/findallreferences_test.go @@ -15,7 +15,7 @@ func runFindReferencesTest(t *testing.T, input string, expectedLocations map[str 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]string{ + service, done := createLanguageService(ctx, testData.Files[0].Filename, map[string]any{ testData.Files[0].Filename: testData.Files[0].Content, }) defer done() diff --git a/internal/ls/utilities.go b/internal/ls/utilities.go index 4310fd525f..f4215e8311 100644 --- a/internal/ls/utilities.go +++ b/internal/ls/utilities.go @@ -351,7 +351,7 @@ func isNameOfModuleDeclaration(node *ast.Node) bool { } func isExpressionOfExternalModuleImportEqualsDeclaration(node *ast.Node) bool { - return ast.IsExternalModuleImportEqualsDeclaration(node.Parent.Parent) && checker.GetExternalModuleImportEqualsDeclarationExpression(node.Parent.Parent) == node + return ast.IsExternalModuleImportEqualsDeclaration(node.Parent.Parent) && ast.GetExternalModuleImportEqualsDeclarationExpression(node.Parent.Parent) == node } func isNamespaceReference(node *ast.Node) bool { From 0d00772669ce9c261b38f3bf71c5f299088763ef Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Fri, 30 May 2025 09:09:34 -0700 Subject: [PATCH 07/13] address comments --- internal/ast/utilities.go | 10 +-- internal/core/set.go | 2 +- internal/ls/findallreferences.go | 47 +++++----- internal/ls/utilities.go | 147 +++++++------------------------ 4 files changed, 55 insertions(+), 151 deletions(-) diff --git a/internal/ast/utilities.go b/internal/ast/utilities.go index fa3ddf98fe..bf15cce15b 100644 --- a/internal/ast/utilities.go +++ b/internal/ast/utilities.go @@ -1041,9 +1041,8 @@ func CanHaveSymbol(node *Node) bool { KindClassDeclaration, KindClassExpression, KindClassStaticBlockDeclaration, KindConstructor, KindConstructorType, KindConstructSignature, KindElementAccessExpression, KindEnumDeclaration, KindEnumMember, KindExportAssignment, KindExportDeclaration, KindExportSpecifier, KindFunctionDeclaration, KindFunctionExpression, KindFunctionType, - KindGetAccessor, KindIdentifier, KindImportClause, KindImportEqualsDeclaration, KindImportSpecifier, - KindIndexSignature, KindInterfaceDeclaration, KindJSDocCallbackTag, - KindJSDocParameterTag, KindJSDocPropertyTag, KindJSDocSignature, KindJSDocTypedefTag, KindJSDocTypeLiteral, + KindGetAccessor, KindImportClause, KindImportEqualsDeclaration, KindImportSpecifier, KindIndexSignature, + KindInterfaceDeclaration, KindJSExportAssignment, KindJSTypeAliasDeclaration, KindCommonJSExport, KindJsxAttribute, KindJsxAttributes, KindJsxSpreadAttribute, KindMappedType, KindMethodDeclaration, KindMethodSignature, KindModuleDeclaration, KindNamedTupleMember, KindNamespaceExport, KindNamespaceExportDeclaration, KindNamespaceImport, KindNewExpression, KindNoSubstitutionTemplateLiteral, KindNumericLiteral, KindObjectLiteralExpression, @@ -1518,11 +1517,6 @@ func GetAssignmentDeclarationKind(bin *BinaryExpression) JSDeclarationKind { return JSDeclarationKindNone } -func hasJSBindableName(node *Node) bool { - name := GetElementOrPropertyAccessName(node) - return IsIdentifier(name) || IsStringLiteralLike(name) -} - func GetAssignmentDeclarationPropertyAccessKind(lhs *Node) JSDeclarationKind { if lhs.Expression().Kind == KindThisKeyword { return JSDeclarationKindThisProperty diff --git a/internal/core/set.go b/internal/core/set.go index 8c776266f0..c7f9b546fe 100644 --- a/internal/core/set.go +++ b/internal/core/set.go @@ -48,7 +48,7 @@ func (s *Set[T]) AddIfAbsent(key T) bool { return true } -// Returns true if the key is arleady in the set. Adds the key and returns false otherwise. +// Returns true if the key is already in the set. Adds the key and returns false otherwise. // // "marks" keys for in "seen" sets in services func (s *Set[T]) HasAndAdd(key T) bool { diff --git a/internal/ls/findallreferences.go b/internal/ls/findallreferences.go index dd3eaa177f..86e8b88afd 100644 --- a/internal/ls/findallreferences.go +++ b/internal/ls/findallreferences.go @@ -5,6 +5,7 @@ import ( "fmt" "slices" "strings" + "unicode/utf8" "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/astnav" @@ -15,8 +16,6 @@ import ( "github.com/microsoft/typescript-go/internal/lsp/lsproto" "github.com/microsoft/typescript-go/internal/scanner" - "unicode/utf8" - "github.com/microsoft/typescript-go/internal/tspath" ) @@ -920,7 +919,7 @@ func getReferenceAtPosition(sourceFile *ast.SourceFile, position int, program *c // -- 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. */ + // Core find-all-references algorithm for a normal symbol. symbol := core.Coalesce(skipPastExportOrImportSpecifierOrUnion(originalSymbol, node, checker /*useLocalSymbolForExportSpecifier*/, !isForRenameWithPrefixAndSuffixText(options)), originalSymbol) @@ -970,26 +969,23 @@ const ( comingFromExport comingFromType = 2 ) -/** -* Symbol that is currently being searched for. -* This will be replaced if we find an alias for the symbol. - */ +// 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). */ + // 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. */ + + // 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`. - */ + // 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 } @@ -1044,7 +1040,7 @@ func newState(sourceFiles []*ast.SourceFile, sourceFilesSet *core.Set[string], n } } -/** @param allSearchSymbols set of additional symbols for use by `includes`. */ +// @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 }`. @@ -1193,11 +1189,9 @@ func (state *refState) getReferencesInSourceFile(sourceFile *ast.SourceFile, sea } 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 - */ + // 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 } @@ -1309,13 +1303,12 @@ func (state *refState) getReferenceForShorthandProperty(referenceSymbol *ast.Sym } 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. - */ + + // 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) } @@ -1484,7 +1477,7 @@ func (state *refState) forEachRelatedSymbol( return nil, entryKindNone } -/** Search for all occurrences of an identifier in a source file (and filter out the ones that match). */ +// 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/utilities.go b/internal/ls/utilities.go index f4215e8311..8a30abc602 100644 --- a/internal/ls/utilities.go +++ b/internal/ls/utilities.go @@ -16,7 +16,8 @@ import ( "github.com/microsoft/typescript-go/internal/stringutil" ) -// p.Compare(other) == cmp.Compare(p, other) +// 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 @@ -24,9 +25,10 @@ func ComparePositions(pos, other lsproto.Position) int { return cmp.Compare(pos.Line, other.Line) } -// t.Compare(other) == cmp.Compare(t, other) +// Implements a cmp.Compare like function for two *lsproto.Range +// CompareRanges(lsRange, other) == cmp.Compare(lsrange, other) // -// compares Range.Start and then Range.End +// 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 @@ -90,7 +92,7 @@ func isModuleSpecifierLike(node *ast.Node) bool { return node.Parent.Kind == ast.KindExternalModuleReference || node.Parent.Kind == ast.KindImportDeclaration || - node.Parent.Kind == ast.KindJSDocImportTag + node.Parent.Kind == ast.KindJSImportDeclaration } func getNonModuleSymbolOfMergedModuleSymbol(symbol *ast.Symbol) *ast.Symbol { @@ -569,36 +571,6 @@ func nodeIsASICandidate(node *ast.Node, file *ast.SourceFile) bool { return startLine != endLine } -func getNextJSDocCommentLocation(node *ast.Node) *ast.Node { - parent := node.Parent - if parent.Kind == ast.KindPropertyAssignment || - parent.Kind == ast.KindExportAssignment || - parent.Kind == ast.KindPropertyDeclaration || - parent.Kind == ast.KindExpressionStatement && node.Kind == ast.KindPropertyAccessExpression || - parent.Kind == ast.KindReturnStatement || - getNestedModuleDeclaration(parent) != nil || - ast.IsAssignmentExpression(node, false) { - return parent - } - if parent.Parent != nil { - // Try to recognize this pattern when node is initializer of variable declaration and JSDoc comments are on containing variable statement. - // /** - // * @param {number} name - // * @returns {number} - // */ - // var x = function(name) { return name.length; } - if checker.GetSingleVariableOfVariableStatement(parent.Parent) == node || ast.IsAssignmentExpression(parent, false) { - return parent.Parent - } - if parent.Parent.Parent != nil && (checker.GetSingleVariableOfVariableStatement(parent.Parent.Parent) != nil || - getSingleInitializerOfVariableStatementOrPropertyDeclaration(parent.Parent.Parent) == node || - getSourceOfDefaultedAssignment(parent.Parent.Parent) != nil) { - return parent.Parent.Parent - } - } - return nil -} - func isNonContextualKeyword(token ast.Kind) bool { return ast.IsKeywordKind(token) && !ast.IsContextualKeyword(token) } @@ -1039,23 +1011,14 @@ func getContainingNodeIfInHeritageClause(node *ast.Node) *ast.Node { } func getContainerNode(node *ast.Node) *ast.Node { - // if (isJSDocTypeAlias(node)) { - // // This doesn't just apply to the node immediately under the comment, but to everything in its parent's scope. - // // node.Parent = the JSDoc comment, node.Parent.Parent = the node having the comment. - // // Then we get parent again in the loop. - // node = node.Parent.Parent; - // } - - for { - if node = node.Parent; node == nil { - return nil - } - switch node.Kind { + 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 node + return parent } } + return nil } func getAdjustedLocation(node *ast.Node, forRename bool, sourceFile *ast.SourceFile) *ast.Node { @@ -1418,10 +1381,6 @@ func getMeaningFromLocation(node *ast.Node) ast.SemanticMeaning { } else if isNamespaceReference(node) { return ast.SemanticMeaningNamespace } else if parent.Kind == ast.KindTypeParameter { - // todo jsdoc not implemented - // if !ast.IsJSDocTemplateTag(parent.Parent) { - // panic("should be handled by isDeclarationName") - // } 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. @@ -1433,23 +1392,13 @@ func getMeaningFromLocation(node *ast.Node) ast.SemanticMeaning { func getMeaningFromDeclaration(node *ast.Node) ast.SemanticMeaning { switch node.Kind { - case ast.KindVariableDeclaration: - // if ast.IsInJSFile(node) { // !!! && ast.GetJSDocEnumTag(node) { - // return ast.SemanticMeaningAll - // } + case ast.KindVariableDeclaration, ast.KindCommonJSExport: return ast.SemanticMeaningValue case 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.KindTypeLiteral: - return ast.SemanticMeaningType - - case ast.KindJSDocTypedefTag: - // If it has no name node, it shares the name with the value declaration below it. - if node.Name() == nil { - return ast.SemanticMeaningValue | ast.SemanticMeaningType - } + case ast.KindTypeParameter, ast.KindInterfaceDeclaration, ast.KindTypeAliasDeclaration, ast.KindJSTypeAliasDeclaration, ast.KindTypeLiteral: return ast.SemanticMeaningType case ast.KindEnumMember, ast.KindClassDeclaration: @@ -1464,7 +1413,7 @@ func getMeaningFromDeclaration(node *ast.Node) ast.SemanticMeaning { return ast.SemanticMeaningNamespace } - case ast.KindEnumDeclaration, ast.KindNamedImports, ast.KindImportSpecifier, ast.KindImportEqualsDeclaration, ast.KindImportDeclaration, ast.KindExportAssignment, ast.KindExportDeclaration: + case ast.KindEnumDeclaration, ast.KindNamedImports, ast.KindImportSpecifier, ast.KindImportEqualsDeclaration, ast.KindImportDeclaration, ast.KindJSImportDeclaration, ast.KindExportAssignment, ast.KindExportDeclaration: return ast.SemanticMeaningAll // An external module can be a Value @@ -1503,7 +1452,7 @@ func getIntersectingMeaningFromDeclarations(node *ast.Node, symbol *ast.Symbol, for meaning != lastIterationMeaning { // The result is order-sensitive, for instance if initialMeaning == Namespace, and declarations = [class, instantiated module] - // we need to consider both as they initialMeaning intersects with the module in the namespace space, and the 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. @@ -1515,25 +1464,6 @@ func getIntersectingMeaningFromDeclarations(node *ast.Node, symbol *ast.Symbol, return meaning } -func getNestedModuleDeclaration(node *ast.Node) *ast.Node { - if ast.IsModuleDeclaration(node) && node.Body() != nil && node.Body().Kind == ast.KindModuleDeclaration { - return node.Body() - } - return nil -} - -func getSingleInitializerOfVariableStatementOrPropertyDeclaration(node *ast.Node) *ast.Expression { - switch node.Kind { - case ast.KindVariableStatement: - if v := checker.GetSingleVariableOfVariableStatement(node); v != nil { - return v.Initializer() - } - case ast.KindPropertyDeclaration, ast.KindPropertyAssignment: - return node.Initializer() - } - return nil -} - // 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) { @@ -1559,26 +1489,27 @@ func getParentSymbolsOfPropertyAccess(location *ast.Node, symbol *ast.Symbol, ch return nil } - res := core.MapNonNil( - core.IfElse(lhsType.Flags() != 0, lhsType.Types(), core.IfElse(lhsType.Symbol() == symbol.Parent, nil, []*checker.Type{lhsType})), - func(t *checker.Type) *ast.Symbol { - return core.IfElse(t.Symbol() != nil && t.Symbol().Flags&(ast.SymbolFlagsClass|ast.SymbolFlagsInterface) != 0, t.Symbol(), nil) - }, - ) - if len(res) == 0 { - return nil + var possibleSymbols []*checker.Type + if lhsType.Flags() != 0 { + possibleSymbols = lhsType.Types() + } else if lhsType.Symbol() != symbol.Parent { + possibleSymbols = []*checker.Type{lhsType} } - return res + + 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 result an array of symbol of found property symbols -* @param previousIterationSymbolsCache 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. - */ +// 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 @@ -1626,20 +1557,6 @@ func getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol *ast.Symb return nil } -func getSourceOfDefaultedAssignment(node *ast.Node) *ast.Node { - if node.Kind == ast.KindExpressionStatement && node.Expression().Kind == ast.KindBinaryExpression && ast.GetAssignmentDeclarationKind(node.Expression().AsBinaryExpression()) != ast.JSDeclarationKindNone { - binExprRight := node.Expression().AsBinaryExpression().Right - if binExprRight.Kind == ast.KindBinaryExpression { - binExprDefault := binExprRight.AsBinaryExpression() - switch binExprDefault.OperatorToken.Kind { - case ast.KindBarBarToken, ast.KindQuestionQuestionToken: - return binExprDefault.Right - } - } - } - return nil -} - func getTargetLabel(referenceNode *ast.Node, labelName string) *ast.Identifier { // todo: rewrite as `ast.FindAncestor` for referenceNode != nil { From d60caa7087eab29ea012de8dcf54aff0d0eb768a Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Mon, 2 Jun 2025 10:31:53 -0700 Subject: [PATCH 08/13] use `GetAssignmentDeclarationKind` --- internal/ast/utilities.go | 65 -------------------------------- internal/ls/findallreferences.go | 3 +- internal/ls/utilities.go | 17 ++++++--- 3 files changed, 13 insertions(+), 72 deletions(-) diff --git a/internal/ast/utilities.go b/internal/ast/utilities.go index bf15cce15b..e0b75d8fb3 100644 --- a/internal/ast/utilities.go +++ b/internal/ast/utilities.go @@ -1319,16 +1319,6 @@ func IsThisParameter(node *Node) bool { return IsParameter(node) && node.Name() != nil && IsThisIdentifier(node.Name()) } -func IsBindableObjectDefinePropertyCall(expr *Node) bool { - return len(expr.Arguments()) == 3 && - IsPropertyAccessExpression(expr.Expression()) && - IsIdentifier(expr.Expression().Expression()) && - // IdText(expr.Expression().Expression()) == "Object" && - // IdText(expr.Expression().Name()) == "defineProperty" && - IsStringOrNumericLiteralLike(expr.Arguments()[1]) && - IsBindableStaticNameExpression(expr.Arguments()[0] /*excludeThisKeyword*/, true) -} - func IsBindableStaticAccessExpression(node *Node, excludeThisKeyword bool) bool { return IsPropertyAccessExpression(node) && (!excludeThisKeyword && node.Expression().Kind == KindThisKeyword || IsIdentifier(node.Name()) && IsBindableStaticNameExpression(node.Expression() /*excludeThisKeyword*/, true)) || @@ -1482,19 +1472,6 @@ const ( JSDeclarationKindThisProperty /// F.name = expr, F[name] = expr JSDeclarationKindProperty - - // PropertyAccessKinds - // F.prototype = { ... } - JSDeclarationKindPrototype - // Object.defineProperty(x, 'name', { value: any, writable?: boolean (false by default) }); - // Object.defineProperty(x, 'name', { get: Function, set: Function }); - // Object.defineProperty(x, 'name', { get: Function }); - // Object.defineProperty(x, 'name', { set: Function }); - JSDeclarationKindObjectDefinePropertyValue - // Object.defineProperty(exports || module.exports, 'name', ...); - JSDeclarationKindObjectDefinePropertyExports - // Object.defineProperty(Foo.prototype, 'name', ...); - JSDeclarationKindObjectDefinePrototypeProperty ) func GetAssignmentDeclarationKind(bin *BinaryExpression) JSDeclarationKind { @@ -1517,44 +1494,6 @@ func GetAssignmentDeclarationKind(bin *BinaryExpression) JSDeclarationKind { return JSDeclarationKindNone } -func GetAssignmentDeclarationPropertyAccessKind(lhs *Node) JSDeclarationKind { - if lhs.Expression().Kind == KindThisKeyword { - return JSDeclarationKindThisProperty - } else if IsModuleExportsAccessExpression(lhs) { - // module.exports = expr - return JSDeclarationKindModuleExports - } else if IsBindableStaticNameExpression(lhs.Expression() /*excludeThisKeyword*/, true) { - if IsPrototypeAccess(lhs.Expression()) { - // F.G....prototype.x = expr - return JSDeclarationKindPrototypeProperty - } - - nextToLast := lhs - for nextToLast.Expression().Kind != KindIdentifier { - nextToLast = nextToLast.Expression() - } - idText := nextToLast.Expression().AsIdentifier().Text - if (idText == "exports" || idText == "module" && getElementOrPropertyAccessNameText(nextToLast) == "exports") && - // ExportsProperty does not support binding with computed names - IsBindableStaticAccessExpression(lhs, false) { - // exports.name = expr OR module.exports.name = expr OR exports["name"] = expr ... - return JSDeclarationKindExportsProperty - } - if IsBindableStaticNameExpression(lhs /*excludeThisKeyword*/, true) || (IsElementAccessExpression(lhs) && IsDynamicName(lhs)) { - // F.G...x = expr - return JSDeclarationKindProperty - } - } - return JSDeclarationKindNone -} - -func getElementOrPropertyAccessNameText(node *Node) string { - if name := GetElementOrPropertyAccessName(node); name != nil { - return name.Text() - } - return "" -} - /** * A declaration has a dynamic name if all of the following are true: * 1. The declaration has a computed property name. @@ -2601,10 +2540,6 @@ func GetDeclarationContainer(node *Node) *Node { }).Parent } -func IsPrototypeAccess(node *Node) bool { - return IsBindableStaticAccessExpression(node, false) && getElementOrPropertyAccessNameText(node) == "prototype" -} - // Indicates that a symbol is an alias that does not merge with a local declaration. // OR Is a JSContainer which may merge an alias with a local declaration func IsNonLocalAlias(symbol *Symbol, excludes SymbolFlags) bool { diff --git a/internal/ls/findallreferences.go b/internal/ls/findallreferences.go index 86e8b88afd..2b85618d36 100644 --- a/internal/ls/findallreferences.go +++ b/internal/ls/findallreferences.go @@ -284,7 +284,8 @@ func isValidReferencePosition(node *ast.Node, searchSymbolName string) bool { return len(node.Text()) == len(searchSymbolName) && (isLiteralNameOfPropertyDeclarationOrIndexAccess(node) || isNameOfModuleDeclaration(node) || isExpressionOfExternalModuleImportEqualsDeclaration(node) || - (ast.IsCallExpression(node.Parent) && ast.IsBindableObjectDefinePropertyCall(node.Parent) && node.Parent.Arguments()[1] == 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) diff --git a/internal/ls/utilities.go b/internal/ls/utilities.go index 8a30abc602..28ca32d233 100644 --- a/internal/ls/utilities.go +++ b/internal/ls/utilities.go @@ -86,7 +86,7 @@ func isModuleSpecifierLike(node *ast.Node) bool { return false } - if ast.IsRequireCall(node.Parent /*requireStringLiteralLikeArgument*/) || ast.IsImportCall(node.Parent) { + if ast.IsVariableDeclarationInitializedToRequire(node.Parent) || ast.IsImportCall(node.Parent) { return node.Parent.AsCallExpression().Arguments.Nodes[0] == node } @@ -1022,6 +1022,8 @@ func getContainerNode(node *ast.Node) *ast.Node { } 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|] ... @@ -1352,6 +1354,8 @@ func getAdjustedLocationForExportDeclaration(node *ast.ExportDeclaration, forRen } 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 { @@ -1392,10 +1396,10 @@ func getMeaningFromLocation(node *ast.Node) ast.SemanticMeaning { func getMeaningFromDeclaration(node *ast.Node) ast.SemanticMeaning { switch node.Kind { - case ast.KindVariableDeclaration, ast.KindCommonJSExport: - return ast.SemanticMeaningValue - - case 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: + 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: @@ -1413,7 +1417,8 @@ func getMeaningFromDeclaration(node *ast.Node) ast.SemanticMeaning { return ast.SemanticMeaningNamespace } - case ast.KindEnumDeclaration, ast.KindNamedImports, ast.KindImportSpecifier, ast.KindImportEqualsDeclaration, ast.KindImportDeclaration, ast.KindJSImportDeclaration, ast.KindExportAssignment, ast.KindExportDeclaration: + 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 From f2fb3473cb1134e993dc043813101388d3294b3f Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Wed, 4 Jun 2025 14:03:33 -0700 Subject: [PATCH 09/13] comments --- internal/checker/services.go | 2 +- internal/compiler/program.go | 65 -------------------------- internal/core/set.go | 11 ----- internal/ls/findallreferences.go | 78 ++++++++++++++++---------------- 4 files changed, 41 insertions(+), 115 deletions(-) diff --git a/internal/checker/services.go b/internal/checker/services.go index f8df31e13f..54d75d5c7c 100644 --- a/internal/checker/services.go +++ b/internal/checker/services.go @@ -403,7 +403,7 @@ func (c *Checker) GetExportSpecifierLocalTargetSymbol(node *ast.Node) *ast.Symbo if node.Parent.Parent.AsExportDeclaration().ModuleSpecifier != nil { return c.getExternalModuleMember(node.Parent.Parent, node, false /*dontResolveAlias*/) } - name := node.AsExportSpecifier().PropertyName + name := node.PropertyName() if name == nil { name = node.Name() } diff --git a/internal/compiler/program.go b/internal/compiler/program.go index e8c2aaa970..6c73433cc9 100644 --- a/internal/compiler/program.go +++ b/internal/compiler/program.go @@ -4,7 +4,6 @@ import ( "context" "maps" "slices" - "strings" "sync" "github.com/microsoft/typescript-go/internal/ast" @@ -913,70 +912,6 @@ func (p *Program) getModeForTypeReferenceDirectiveInFile(ref *ast.FileReference, // !!! || p.getDefaultResolutionModeForFile(sourceFile) } -/** This should have similar behavior to 'processSourceFile' without diagnostics or mutation. */ -// func (p *Program) GetSourceFileFromReference(referencingFile *ast.SourceFile, ref *ast.FileReference) *ast.SourceFile { -// return p.getSourceFileFromReferenceWorker(tspath.ResolveTripleslashReference(ref.FileName, referencingFile.FileName()), p.GetSourceFile, nil /*fail*/) -// } -// !!! todo: check against new `getSourceFileFromReference` -func (p *Program) getSourceFileFromReferenceWorker( - fileName string, - getSourceFile func(fileName string) *ast.SourceFile, - fail func(diagnostic *diagnostics.Message, argument ...string), - // reason *ReferencedFile, // !!! strada uses FileIncludeReason, but this function only performs extra work for ReferencedFiles -) *ast.SourceFile { - if tspath.HasExtension(fileName) { - canonicalFileName := tspath.GetCanonicalFileName(fileName, p.host.FS().UseCaseSensitiveFileNames()) - if !p.Options().AllowNonTsExtensions.IsTrue() { - if !core.FirstNonNil(p.supportedExtensionsWithJsonIfResolveJsonModule, func(extension string) bool { - return tspath.FileExtensionIs(canonicalFileName, extension) - }) { - if fail != nil { - if tspath.HasJSFileExtension(canonicalFileName) { - fail(diagnostics.File_0_is_a_JavaScript_file_Did_you_mean_to_enable_the_allowJs_option, fileName) - } else { - fail(diagnostics.File_0_has_an_unsupported_extension_The_only_supported_extensions_are_1, fileName, "'"+strings.Join(core.Flatten(p.supportedExtensions), "', '")+"'") - } - } - return nil - } - } - - sourceFile := getSourceFile(fileName) - // todo: fail not use in inputs yet, so leaving this unimplemented for now - // if fail != nil { - // if sourceFile == nil { - // redirect := getProjectReferenceRedirect(fileName) - // if redirect { - // fail(diagnostics.Output_file_0_has_not_been_built_from_source_file_1, redirect, fileName) - // } else { - // fail(diagnostics.File_0_not_found, fileName) - // } - // } else if reason != nil && canonicalFileName == tspath.GetCanonicalFileName(p.GetSourceFileByPath(reason.File).FileName(), p.host.FS().UseCaseSensitiveFileNames()) { - // fail(diagnostics.A_file_cannot_have_a_reference_to_itself) - // } - // } - return sourceFile - } else { - if p.Options().AllowNonTsExtensions.IsTrue() { - if sourceFileNoExtension := getSourceFile(fileName); sourceFileNoExtension != nil { - return sourceFileNoExtension - } - } - - if fail != nil && p.Options().AllowNonTsExtensions.IsTrue() { - fail(diagnostics.File_0_not_found, fileName) - return nil - } - - // Only try adding extensions from the first supported group (which should be .ts/.tsx/.d.ts) - sourceFileWithAddedExtension := core.FirstNonNil(p.supportedExtensions[0], func(extension string) *ast.SourceFile { return getSourceFile(fileName + extension) }) - if fail != nil && sourceFileWithAddedExtension == nil { - fail(diagnostics.Could_not_resolve_the_path_0_with_the_extensions_Colon_1, fileName, "'"+strings.Join(core.Flatten(p.supportedExtensions), "', '")+"'") - } - return sourceFileWithAddedExtension - } -} - type FileIncludeKind int const ( diff --git a/internal/core/set.go b/internal/core/set.go index c7f9b546fe..3d094cc2ac 100644 --- a/internal/core/set.go +++ b/internal/core/set.go @@ -48,17 +48,6 @@ func (s *Set[T]) AddIfAbsent(key T) bool { return true } -// Returns true if the key is already in the set. Adds the key and returns false otherwise. -// -// "marks" keys for in "seen" sets in services -func (s *Set[T]) HasAndAdd(key T) bool { - if s.Has(key) { - return true - } - s.Add(key) - return false -} - func NewSetFromItems[T comparable](items ...T) *Set[T] { s := &Set[T]{} for _, item := range items { diff --git a/internal/ls/findallreferences.go b/internal/ls/findallreferences.go index 2b85618d36..2db0f2ab7d 100644 --- a/internal/ls/findallreferences.go +++ b/internal/ls/findallreferences.go @@ -30,11 +30,11 @@ const ( ) type refOptions struct { - findInStrings bool - findInComments bool - use referenceUse // other, references, rename - implementations bool - providePrefixAndSuffixTextForRename bool + findInStrings bool + findInComments bool + use referenceUse // other, references, rename + implementations bool + useAliasesForRename bool // renamed from providePrefixAndSuffixTextForRename. default: true } // === types for results === @@ -48,10 +48,10 @@ type refInfo struct { type SymbolAndEntries struct { definition *Definition - references []*Entry + references []*referenceEntry } -func NewSymbolAndEntries(kind definitionKind, node *ast.Node, symbol *ast.Symbol, references []*Entry) *SymbolAndEntries { +func NewSymbolAndEntries(kind definitionKind, node *ast.Node, symbol *ast.Symbol, references []*referenceEntry) *SymbolAndEntries { return &SymbolAndEntries{ &Definition{ Kind: kind, @@ -95,7 +95,7 @@ const ( entryKindSearchedPropertyFoundLocal entryKind = 5 ) -type Entry struct { +type referenceEntry struct { kind entryKind node *ast.Node context *ast.Node // !!! ContextWithStartAndEndNode, optional @@ -103,34 +103,35 @@ type Entry struct { textRange *lsproto.Range } -func (l *LanguageService) getRangeOfEntry(entry *Entry) *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) *Entry { - return &Entry{ +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) *Entry { - e := NewNodeEntry(node) +func newNodeEntryWithKind(node *ast.Node, kind entryKind) *referenceEntry { + e := newNodeEntry(node) e.kind = kind return e } -func NewNodeEntry(node *ast.Node) *Entry { +func newNodeEntry(node *ast.Node) *referenceEntry { // creates nodeEntry with `kind == entryKindNode` n := node if node != nil && node.Name() != nil { n = node.Name() } - return &Entry{ + return &referenceEntry{ kind: entryKindNode, node: node, context: getContextNodeForNodeEntry(n), @@ -173,9 +174,10 @@ func getContextNodeForNodeEntry(node *ast.Node) *ast.Node { declOrStatement := ast.FindAncestor(validImport, func(*ast.Node) bool { return ast.IsDeclaration(node) || ast.IsStatement(node) || ast.IsJSDocTag(node) }) - return core.IfElse(ast.IsDeclaration(declOrStatement), - getContextNode(declOrStatement), - declOrStatement) + if ast.IsDeclaration(declOrStatement) { + return getContextNode(declOrStatement) + } + return declOrStatement } } @@ -296,7 +298,7 @@ func isValidReferencePosition(node *ast.Node, searchSymbolName string) bool { } func isForRenameWithPrefixAndSuffixText(options refOptions) bool { - return options.use == referenceUseRename && options.providePrefixAndSuffixTextForRename + return options.use == referenceUseRename && options.useAliasesForRename } func skipPastExportOrImportSpecifierOrUnion(symbol *ast.Symbol, node *ast.Node, checker *checker.Checker, useLocalSymbolForExportSpecifier bool) *ast.Symbol { @@ -429,7 +431,7 @@ func (l *LanguageService) convertSymbolAndEntryToLocation(s *SymbolAndEntries) [ func (l *LanguageService) mergeReferences(program *compiler.Program, referencesToMerge ...[]*SymbolAndEntries) []*SymbolAndEntries { result := []*SymbolAndEntries{} - getSourceFileIndexOfEntry := func(program *compiler.Program, entry *Entry) int { + getSourceFileIndexOfEntry := func(program *compiler.Program, entry *referenceEntry) int { var sourceFile *ast.SourceFile if entry.kind == entryKindRange { sourceFile = program.GetSourceFile(entry.fileName) @@ -465,7 +467,7 @@ func (l *LanguageService) mergeReferences(program *compiler.Program, referencesT reference := result[refIndex] sortedRefs := append(reference.references, entry.references...) - slices.SortStableFunc(sortedRefs, func(entry1, entry2 *Entry) int { + slices.SortStableFunc(sortedRefs, func(entry1, entry2 *referenceEntry) int { entry1File := getSourceFileIndexOfEntry(program, entry1) entry2File := getSourceFileIndexOfEntry(program, entry2) if entry1File != entry2File { @@ -621,7 +623,7 @@ func getReferencedSymbolsSpecial(node *ast.Node, sourceFiles []*ast.SourceFile) } if node.Kind == ast.KindStaticKeyword && node.Parent.Kind == ast.KindClassStaticBlockDeclaration { - return []*SymbolAndEntries{{definition: &Definition{Kind: definitionKindKeyword, node: node}, references: []*Entry{NewNodeEntry(node)}}} + return []*SymbolAndEntries{{definition: &Definition{Kind: definitionKindKeyword, node: node}, references: []*referenceEntry{newNodeEntry(node)}}} } // Labels @@ -653,10 +655,10 @@ func getReferencedSymbolsSpecial(node *ast.Node, sourceFiles []*ast.SourceFile) 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) *Entry { + 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 newNodeEntry(node) } return nil }) @@ -727,10 +729,10 @@ func getReferencesForThisKeyword(thisOrSuperKeyword *ast.Node, sourceFiles []*as return false }) }), - func(n *ast.Node) *Entry { return NewNodeEntry(n) }, + func(n *ast.Node) *referenceEntry { return newNodeEntry(n) }, ) - thisParameter := core.FirstNonNil(references, func(ref *Entry) *ast.Node { + thisParameter := core.FirstNonNil(references, func(ref *referenceEntry) *ast.Node { if ref.node.Parent.Kind == ast.KindParameter { return ref.node } @@ -760,7 +762,7 @@ func getReferencesForSuperKeyword(superKeyword *ast.Node) []*SymbolAndEntries { } sourceFile := ast.GetSourceFileOfNode(searchSpaceNode) - references := core.MapNonNil(getPossibleSymbolReferenceNodes(sourceFile, "super", searchSpaceNode), func(node *ast.Node) *Entry { + references := core.MapNonNil(getPossibleSymbolReferenceNodes(sourceFile, "super", searchSpaceNode), func(node *ast.Node) *referenceEntry { if node.Kind != ast.KindSuperKeyword { return nil } @@ -771,7 +773,7 @@ func getReferencesForSuperKeyword(superKeyword *ast.Node) []*SymbolAndEntries { // 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 newNodeEntry(node) } return nil }) @@ -781,11 +783,11 @@ func getReferencesForSuperKeyword(superKeyword *ast.Node) []*SymbolAndEntries { 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) []*Entry { + 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) *Entry { + 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 newNodeEntry(referenceLocation) } return nil }) @@ -847,9 +849,9 @@ func getPossibleSymbolReferencePositions(sourceFile *ast.SourceFile, symbolName return positions } -func getReferencesForNonModule(referencedFile *ast.SourceFile, program *compiler.Program) []*Entry { +func getReferencesForNonModule(referencedFile *ast.SourceFile, program *compiler.Program) []*referenceEntry { // !!! not implemented - return []*Entry{} + return []*referenceEntry{} } func getMergedAliasedSymbolOfNamespaceExportDeclaration(node *ast.Node, symbol *ast.Symbol, checker *checker.Checker) *ast.Symbol { @@ -942,7 +944,7 @@ func getReferencedSymbolsForSymbol(originalSymbol *ast.Symbol, node *ast.Node, s // !!! 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.providePrefixAndSuffixTextForRename, options.implementations)) + search := state.createSearch(node, symbol, comingFromUnknown /*comingFrom*/, "", state.populateSearchSymbolSet(symbol, node, options.use == referenceUseRename, options.useAliasesForRename, options.implementations)) state.getReferencesInContainerOrFiles(symbol, search) } @@ -1084,12 +1086,12 @@ func (state *refState) referenceAdder(searchSymbol *ast.Symbol) func(*ast.Node, symbolId := ast.GetSymbolId(searchSymbol) symbolAndEntry := state.symbolIdToReferences[symbolId] if symbolAndEntry == nil { - state.symbolIdToReferences[symbolId] = NewSymbolAndEntries(definitionKindSymbol, nil, searchSymbol, []*Entry{}) + 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)) + symbolAndEntry.references = append(symbolAndEntry.references, newNodeEntryWithKind(node, kind)) } } @@ -1143,7 +1145,7 @@ func (state *refState) addImplementationReferences(refNode *ast.Node, addRef fun } typeHavingNode := typeNode.Parent - if typeHavingNode.Type() == typeNode && state.seenContainingTypeReferences.HasAndAdd(typeHavingNode) { + if typeHavingNode.Type() == typeNode && !state.seenContainingTypeReferences.AddIfAbsent(typeHavingNode) { addIfImplementation := func(e *ast.Expression) { if isImplementationExpression(e) { addRef(e) @@ -1347,7 +1349,7 @@ func (state *refState) getRelatedSymbol(search *refSearch, referenceSymbol *ast. referenceSymbol, referenceLocation, false, /*isForRenamePopulateSearchSymbolSet*/ - state.options.use != referenceUseRename || state.options.providePrefixAndSuffixTextForRename, /*onlyIncludeBindingElementAtReferenceLocation*/ + 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 { From bd32a3458e576e2d8c743106e1176f0871645c18 Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Wed, 4 Jun 2025 14:44:00 -0700 Subject: [PATCH 10/13] merge conflicts --- internal/ast/utilities.go | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/internal/ast/utilities.go b/internal/ast/utilities.go index fe3ff6c464..12413cb2f4 100644 --- a/internal/ast/utilities.go +++ b/internal/ast/utilities.go @@ -3516,25 +3516,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 { From 2c3ceba82290f2236aebae51d9240f18f1e19a77 Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Wed, 4 Jun 2025 14:54:43 -0700 Subject: [PATCH 11/13] format (again) --- internal/compiler/program.go | 7 ++++--- internal/ls/findallreferences.go | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/internal/compiler/program.go b/internal/compiler/program.go index e1aa6cbda9..561cfc9993 100644 --- a/internal/compiler/program.go +++ b/internal/compiler/program.go @@ -888,9 +888,10 @@ func (p *Program) GetResolvedTypeReferenceDirectiveFromTypeReferenceDirective(ty } func (p *Program) getModeForTypeReferenceDirectiveInFile(ref *ast.FileReference, sourceFile *ast.SourceFile) core.ResolutionMode { - return ref.ResolutionMode - // TODO - // !!! || p.getDefaultResolutionModeForFile(sourceFile) + if ref.ResolutionMode != core.ResolutionModeNone { + return ref.ResolutionMode + } + return p.GetDefaultResolutionModeForFile(sourceFile) } type FileIncludeKind int diff --git a/internal/ls/findallreferences.go b/internal/ls/findallreferences.go index 862ef67717..f3cf678e14 100644 --- a/internal/ls/findallreferences.go +++ b/internal/ls/findallreferences.go @@ -539,7 +539,7 @@ func (l *LanguageService) getReferencedSymbolsForNode(position int, node *ast.No if isModuleSpecifierLike(node) { // !!! fileIncludeReasons := program.GetFileIncludeReasons() if referencedFile := program.GetResolvedModuleFromModuleSpecifier(node, nil /*sourceFile*/); referencedFile != nil { - // !!! not implemented + // !!! not implemented // return []*SymbolAndEntries{{ // definition: &Definition{Kind: definitionKindString, node: node}, // references: getReferencesForNonModule(referencedFile, program /*fileIncludeReasons,*/), From 2a5ee10d1b385599da24d6f4ab5a0e3a4d60ee85 Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Wed, 4 Jun 2025 16:41:54 -0700 Subject: [PATCH 12/13] add panic handling --- internal/checker/checker.go | 8 ++++---- internal/checker/utilities.go | 7 ------- internal/ls/findallreferences.go | 2 +- internal/lsp/server.go | 8 ++++++++ 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 606111cf92..c2ed93ef7c 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -1830,12 +1830,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) @@ -5631,7 +5631,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 @@ -29926,7 +29926,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/utilities.go b/internal/checker/utilities.go index 3cf61604db..c09c9d99e7 100644 --- a/internal/checker/utilities.go +++ b/internal/checker/utilities.go @@ -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/ls/findallreferences.go b/internal/ls/findallreferences.go index f3cf678e14..ab28853a0d 100644 --- a/internal/ls/findallreferences.go +++ b/internal/ls/findallreferences.go @@ -345,7 +345,7 @@ func getSymbolScope(symbol *ast.Symbol) *ast.Node { return checker.HasModifier(d, ast.ModifierFlagsPrivate) || ast.IsPrivateIdentifierClassElementDeclaration(d) }) if privateDeclaration != nil { - return checker.GetAncestor(privateDeclaration, ast.KindClassDeclaration) + return ast.FindAncestorKind(privateDeclaration, ast.KindClassDeclaration) } // Else this is a public property and could be accessed from anywhere. return nil diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 78a8d01f01..7d0ec50dc3 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -594,6 +594,14 @@ func (s *Server) handleReferences(ctx context.Context, req *lsproto.RequestMessa 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) From fcfc50aa476e227dc4bf9229d43669f939369003 Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Thu, 5 Jun 2025 11:38:43 -0700 Subject: [PATCH 13/13] merge conflicts (again) --- internal/compiler/program.go | 9 --------- internal/ls/findallreferences.go | 18 +++++++++--------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/internal/compiler/program.go b/internal/compiler/program.go index c47ffcbb80..9e6ee60fd7 100644 --- a/internal/compiler/program.go +++ b/internal/compiler/program.go @@ -382,15 +382,6 @@ func (p *Program) findSourceFile(candidate string, reason FileIncludeReason) *as return p.filesByPath[path] } -func (p *Program) GetResolvedModuleFromModuleSpecifier(moduleSpecifier *ast.Node /*!!! stringliterallike*/, sourceFile *ast.SourceFile) *module.ResolvedModule { - if sourceFile == nil { - if sourceFile = ast.GetSourceFileOfNode(moduleSpecifier); sourceFile == nil { - panic("`moduleSpecifier` must have a `SourceFile` ancestor. Use `program.getResolvedModule` instead to provide the containing file and resolution mode.") - } - } - return p.GetResolvedModule(sourceFile, moduleSpecifier.Text(), p.GetModeForUsageLocation(sourceFile, moduleSpecifier)) -} - func (p *Program) GetSyntacticDiagnostics(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic { return p.getDiagnosticsHelper(ctx, sourceFile, false /*ensureBound*/, false /*ensureChecked*/, p.getSyntacticDiagnosticsForFile) } diff --git a/internal/ls/findallreferences.go b/internal/ls/findallreferences.go index ab28853a0d..16c435d74c 100644 --- a/internal/ls/findallreferences.go +++ b/internal/ls/findallreferences.go @@ -537,14 +537,14 @@ func (l *LanguageService) getReferencedSymbolsForNode(position int, node *ast.No // 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) { - // !!! fileIncludeReasons := program.GetFileIncludeReasons() - if referencedFile := program.GetResolvedModuleFromModuleSpecifier(node, nil /*sourceFile*/); referencedFile != nil { - // !!! not implemented - // return []*SymbolAndEntries{{ - // definition: &Definition{Kind: definitionKindString, node: node}, - // references: getReferencesForNonModule(referencedFile, program /*fileIncludeReasons,*/), - // }} - } + // !!! 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). @@ -904,7 +904,7 @@ func getReferenceAtPosition(sourceFile *ast.SourceFile, position int, program *c if !isModuleSpecifierLike(node) || !tspath.IsExternalModuleNameRelative(node.Text()) { return nil } - if resolution := program.GetResolvedModuleFromModuleSpecifier(node, sourceFile); resolution != nil { + if resolution := program.GetResolvedModuleFromModuleSpecifier(sourceFile, node); resolution != nil { verifiedFileName := resolution.ResolvedFileName fileName := resolution.ResolvedFileName if fileName == "" {