-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Allow to find all references for constructors #10540
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3f126a5
c1a291d
f90d8dd
7e6f18d
0dc976d
4514f8f
5977473
efc7e9d
e8e7ec6
3eadbf6
d6d6a4a
ab75365
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1515,5 +1515,4 @@ namespace ts { | |
| ? ((fileName) => fileName) | ||
| : ((fileName) => fileName.toLowerCase()); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2792,18 +2792,42 @@ namespace ts { | |
| return node && node.parent && node.parent.kind === SyntaxKind.PropertyAccessExpression && (<PropertyAccessExpression>node.parent).name === node; | ||
| } | ||
|
|
||
| function climbPastPropertyAccess(node: Node) { | ||
| return isRightSideOfPropertyAccess(node) ? node.parent : node; | ||
| } | ||
|
|
||
| /** Get `C` given `N` if `N` is in the position `class C extends N` or `class C extends foo.N` where `N` is an identifier. */ | ||
| function tryGetClassByExtendingIdentifier(node: Node): ClassLikeDeclaration | undefined { | ||
| return tryGetClassExtendingExpressionWithTypeArguments(climbPastPropertyAccess(node).parent); | ||
| } | ||
|
|
||
| function isCallExpressionTarget(node: Node): boolean { | ||
| if (isRightSideOfPropertyAccess(node)) { | ||
| node = node.parent; | ||
| } | ||
| return node && node.parent && node.parent.kind === SyntaxKind.CallExpression && (<CallExpression>node.parent).expression === node; | ||
| return isCallOrNewExpressionTarget(node, SyntaxKind.CallExpression); | ||
| } | ||
|
|
||
| function isNewExpressionTarget(node: Node): boolean { | ||
| if (isRightSideOfPropertyAccess(node)) { | ||
| node = node.parent; | ||
| } | ||
| return node && node.parent && node.parent.kind === SyntaxKind.NewExpression && (<CallExpression>node.parent).expression === node; | ||
| return isCallOrNewExpressionTarget(node, SyntaxKind.NewExpression); | ||
| } | ||
|
|
||
| function isCallOrNewExpressionTarget(node: Node, kind: SyntaxKind) { | ||
| const target = climbPastPropertyAccess(node); | ||
| return target && target.parent && target.parent.kind === kind && (<CallExpression>target.parent).expression === target; | ||
| } | ||
|
|
||
| function climbPastManyPropertyAccesses(node: Node): Node { | ||
| return isRightSideOfPropertyAccess(node) ? climbPastManyPropertyAccesses(node.parent) : node; | ||
| } | ||
|
|
||
| /** Returns a CallLikeExpression where `node` is the target being invoked. */ | ||
| function getAncestorCallLikeExpression(node: Node): CallLikeExpression | undefined { | ||
| const target = climbPastManyPropertyAccesses(node); | ||
| const callLike = target.parent; | ||
| return callLike && isCallLikeExpression(callLike) && getInvokedExpression(callLike) === target && callLike; | ||
| } | ||
|
|
||
| function tryGetSignatureDeclaration(typeChecker: TypeChecker, node: Node): SignatureDeclaration | undefined { | ||
| const callLike = getAncestorCallLikeExpression(node); | ||
| return callLike && typeChecker.getResolvedSignature(callLike).declaration; | ||
| } | ||
|
|
||
| function isNameOfModuleDeclaration(node: Node) { | ||
|
|
@@ -4609,7 +4633,7 @@ namespace ts { | |
| const symbolFlags = symbol.flags; | ||
| let symbolKind = getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(symbol, symbolFlags, location); | ||
| let hasAddedSymbolInfo: boolean; | ||
| const isThisExpression: boolean = location.kind === SyntaxKind.ThisKeyword && isExpression(location); | ||
| const isThisExpression = location.kind === SyntaxKind.ThisKeyword && isExpression(location); | ||
| let type: Type; | ||
|
|
||
| // Class at constructor site need to be shown as constructor apart from property,method, vars | ||
|
|
@@ -5072,14 +5096,25 @@ namespace ts { | |
| }; | ||
| } | ||
|
|
||
| function getSymbolInfo(typeChecker: TypeChecker, symbol: Symbol, node: Node) { | ||
| return { | ||
| symbolName: typeChecker.symbolToString(symbol), // Do not get scoped name, just the name of the symbol | ||
| symbolKind: getSymbolKind(symbol, node), | ||
| containerName: symbol.parent ? typeChecker.symbolToString(symbol.parent, node) : "" | ||
| }; | ||
| } | ||
|
|
||
| function createDefinitionFromSignatureDeclaration(decl: SignatureDeclaration): DefinitionInfo { | ||
| const typeChecker = program.getTypeChecker(); | ||
| const { symbolName, symbolKind, containerName } = getSymbolInfo(typeChecker, decl.symbol, decl); | ||
| return createDefinitionInfo(decl, symbolKind, symbolName, containerName); | ||
| } | ||
|
|
||
| function getDefinitionFromSymbol(symbol: Symbol, node: Node): DefinitionInfo[] { | ||
| const typeChecker = program.getTypeChecker(); | ||
| const result: DefinitionInfo[] = []; | ||
| const declarations = symbol.getDeclarations(); | ||
| const symbolName = typeChecker.symbolToString(symbol); // Do not get scoped name, just the name of the symbol | ||
| const symbolKind = getSymbolKind(symbol, node); | ||
| const containerSymbol = symbol.parent; | ||
| const containerName = containerSymbol ? typeChecker.symbolToString(containerSymbol, node) : ""; | ||
| const { symbolName, symbolKind, containerName } = getSymbolInfo(typeChecker, symbol, node); | ||
|
|
||
| if (!tryAddConstructSignature(symbol, node, symbolKind, symbolName, containerName, result) && | ||
| !tryAddCallSignature(symbol, node, symbolKind, symbolName, containerName, result)) { | ||
|
|
@@ -5205,6 +5240,12 @@ namespace ts { | |
| } | ||
|
|
||
| const typeChecker = program.getTypeChecker(); | ||
|
|
||
| const calledDeclaration = tryGetSignatureDeclaration(typeChecker, node); | ||
| if (calledDeclaration) { | ||
| return [createDefinitionFromSignatureDeclaration(calledDeclaration)]; | ||
| } | ||
|
|
||
| let symbol = typeChecker.getSymbolAtLocation(node); | ||
|
|
||
| // Could not find a symbol e.g. node is string or number keyword, | ||
|
|
@@ -6012,6 +6053,7 @@ namespace ts { | |
| case SyntaxKind.Identifier: | ||
| case SyntaxKind.ThisKeyword: | ||
| // case SyntaxKind.SuperKeyword: TODO:GH#9268 | ||
| case SyntaxKind.ConstructorKeyword: | ||
| case SyntaxKind.StringLiteral: | ||
| return getReferencedSymbolsForNode(node, program.getSourceFiles(), findInStrings, findInComments); | ||
| } | ||
|
|
@@ -6056,6 +6098,8 @@ namespace ts { | |
| return getReferencesForSuperKeyword(node); | ||
| } | ||
|
|
||
| // `getSymbolAtLocation` normally returns the symbol of the class when given the constructor keyword, | ||
| // so we have to specify that we want the constructor symbol. | ||
| const symbol = typeChecker.getSymbolAtLocation(node); | ||
|
|
||
| if (!symbol && node.kind === SyntaxKind.StringLiteral) { | ||
|
|
@@ -6130,7 +6174,7 @@ namespace ts { | |
| }; | ||
| } | ||
|
|
||
| function getAliasSymbolForPropertyNameSymbol(symbol: Symbol, location: Node): Symbol { | ||
| function getAliasSymbolForPropertyNameSymbol(symbol: Symbol, location: Node): Symbol | undefined { | ||
| if (symbol.flags & SymbolFlags.Alias) { | ||
| // Default import get alias | ||
| const defaultImport = getDeclarationOfKind(symbol, SyntaxKind.ImportClause); | ||
|
|
@@ -6156,6 +6200,10 @@ namespace ts { | |
| return undefined; | ||
| } | ||
|
|
||
| function followAliasIfNecessary(symbol: Symbol, location: Node): Symbol { | ||
| return getAliasSymbolForPropertyNameSymbol(symbol, location) || symbol; | ||
| } | ||
|
|
||
| function getPropertySymbolOfDestructuringAssignment(location: Node) { | ||
| return isArrayLiteralOrObjectLiteralDestructuringPattern(location.parent.parent) && | ||
| typeChecker.getPropertySymbolOfDestructuringAssignment(<Identifier>location); | ||
|
|
@@ -6420,7 +6468,8 @@ namespace ts { | |
| if (referenceSymbol) { | ||
| const referenceSymbolDeclaration = referenceSymbol.valueDeclaration; | ||
| const shorthandValueSymbol = typeChecker.getShorthandAssignmentValueSymbol(referenceSymbolDeclaration); | ||
| const relatedSymbol = getRelatedSymbol(searchSymbols, referenceSymbol, referenceLocation); | ||
| const relatedSymbol = getRelatedSymbol(searchSymbols, referenceSymbol, referenceLocation, | ||
| /*searchLocationIsConstructor*/ searchLocation.kind === SyntaxKind.ConstructorKeyword); | ||
|
|
||
| if (relatedSymbol) { | ||
| const referencedSymbol = getReferencedSymbol(relatedSymbol); | ||
|
|
@@ -6436,12 +6485,94 @@ namespace ts { | |
| const referencedSymbol = getReferencedSymbol(shorthandValueSymbol); | ||
| referencedSymbol.references.push(getReferenceEntryFromNode(referenceSymbolDeclaration.name)); | ||
| } | ||
| else if (searchLocation.kind === SyntaxKind.ConstructorKeyword) { | ||
| findAdditionalConstructorReferences(referenceSymbol, referenceLocation); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| return; | ||
|
|
||
| /** Adds references when a constructor is used with `new this()` in its own class and `super()` calls in subclasses. */ | ||
| function findAdditionalConstructorReferences(referenceSymbol: Symbol, referenceLocation: Node): void { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think a better approach here is to call the main routine
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. come grab me tomorrow if you want to talk about this more. |
||
| Debug.assert(isClassLike(searchSymbol.valueDeclaration)); | ||
|
|
||
| const referenceClass = referenceLocation.parent; | ||
| if (referenceSymbol === searchSymbol && isClassLike(referenceClass)) { | ||
| Debug.assert(referenceClass.name === referenceLocation); | ||
| // This is the class declaration containing the constructor. | ||
| addReferences(findOwnConstructorCalls(searchSymbol)); | ||
| } | ||
| else { | ||
| // If this class appears in `extends C`, then the extending class' "super" calls are references. | ||
| const classExtending = tryGetClassByExtendingIdentifier(referenceLocation); | ||
| if (classExtending && isClassLike(classExtending) && followAliasIfNecessary(referenceSymbol, referenceLocation) === searchSymbol) { | ||
| addReferences(superConstructorAccesses(classExtending)); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| function addReferences(references: Node[]): void { | ||
| if (references.length) { | ||
| const referencedSymbol = getReferencedSymbol(searchSymbol); | ||
| addRange(referencedSymbol.references, map(references, getReferenceEntryFromNode)); | ||
| } | ||
| } | ||
|
|
||
| /** `classSymbol` is the class where the constructor was defined. | ||
| * Reference the constructor and all calls to `new this()`. | ||
| */ | ||
| function findOwnConstructorCalls(classSymbol: Symbol): Node[] { | ||
| const result: Node[] = []; | ||
|
|
||
| for (const decl of classSymbol.members["__constructor"].declarations) { | ||
| Debug.assert(decl.kind === SyntaxKind.Constructor); | ||
| const ctrKeyword = decl.getChildAt(0); | ||
| Debug.assert(ctrKeyword.kind === SyntaxKind.ConstructorKeyword); | ||
| result.push(ctrKeyword); | ||
| } | ||
|
|
||
| forEachProperty(classSymbol.exports, member => { | ||
| const decl = member.valueDeclaration; | ||
| if (decl && decl.kind === SyntaxKind.MethodDeclaration) { | ||
| const body = (<MethodDeclaration>decl).body; | ||
| if (body) { | ||
| forEachDescendantOfKind(body, SyntaxKind.ThisKeyword, thisKeyword => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you should not decent in nested classes or functions, as |
||
| if (isNewExpressionTarget(thisKeyword)) { | ||
| result.push(thisKeyword); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| /** Find references to `super` in the constructor of an extending class. */ | ||
| function superConstructorAccesses(cls: ClassLikeDeclaration): Node[] { | ||
| const symbol = cls.symbol; | ||
| const ctr = symbol.members["__constructor"]; | ||
| if (!ctr) { | ||
| return []; | ||
| } | ||
|
|
||
| const result: Node[] = []; | ||
| for (const decl of ctr.declarations) { | ||
| Debug.assert(decl.kind === SyntaxKind.Constructor); | ||
| const body = (<ConstructorDeclaration>decl).body; | ||
| if (body) { | ||
| forEachDescendantOfKind(body, SyntaxKind.SuperKeyword, node => { | ||
| if (isCallExpressionTarget(node)) { | ||
| result.push(node); | ||
| } | ||
| }); | ||
| } | ||
| }; | ||
| return result; | ||
| } | ||
|
|
||
| function getReferencedSymbol(symbol: Symbol): ReferencedSymbol { | ||
| const symbolId = getSymbolId(symbol); | ||
| let index = symbolToIndex[symbolId]; | ||
|
|
@@ -6822,16 +6953,17 @@ namespace ts { | |
| } | ||
| } | ||
|
|
||
| function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node): Symbol { | ||
| if (searchSymbols.indexOf(referenceSymbol) >= 0) { | ||
| return referenceSymbol; | ||
| function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node, searchLocationIsConstructor: boolean): Symbol | undefined { | ||
| if (contains(searchSymbols, referenceSymbol)) { | ||
| // If we are searching for constructor uses, they must be 'new' expressions. | ||
| return (!searchLocationIsConstructor || isNewExpressionTarget(referenceLocation)) && referenceSymbol; | ||
| } | ||
|
|
||
| // If the reference symbol is an alias, check if what it is aliasing is one of the search | ||
| // symbols but by looking up for related symbol of this alias so it can handle multiple level of indirectness. | ||
| const aliasSymbol = getAliasSymbolForPropertyNameSymbol(referenceSymbol, referenceLocation); | ||
| if (aliasSymbol) { | ||
| return getRelatedSymbol(searchSymbols, aliasSymbol, referenceLocation); | ||
| return getRelatedSymbol(searchSymbols, aliasSymbol, referenceLocation, searchLocationIsConstructor); | ||
| } | ||
|
|
||
| // If the reference location is in an object literal, try to get the contextual type for the | ||
|
|
@@ -8355,6 +8487,15 @@ namespace ts { | |
| }; | ||
| } | ||
|
|
||
| function forEachDescendantOfKind(node: Node, kind: SyntaxKind, action: (node: Node) => void) { | ||
| forEachChild(node, child => { | ||
| if (child.kind === kind) { | ||
| action(child); | ||
| } | ||
| forEachDescendantOfKind(child, kind, action); | ||
| }); | ||
| } | ||
|
|
||
| /* @internal */ | ||
| export function getNameTable(sourceFile: SourceFile): Map<number> { | ||
| if (!sourceFile.nameTable) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
kind: SyntaxKind.CallExpression | SyntaxKind.NewExpressionThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would require us to do a new LKG to get that feature.