Factor out FindAllReferences.Core and use Definition/Entry to allow findReferencedSymbols and getImplementationsAtPosition to return different results

This commit is contained in:
Andy Hanson
2017-03-17 09:00:30 -07:00
parent ec558b8080
commit 94f6839aec
7 changed files with 278 additions and 197 deletions

View File

@@ -1849,7 +1849,6 @@ namespace ts {
}
export interface ExternalModuleReference extends Node {
parent: ImportEqualsDeclaration;
kind: SyntaxKind.ExternalModuleReference;
parent?: ImportEqualsDeclaration;
expression?: Expression;
@@ -1909,7 +1908,6 @@ namespace ts {
}
export interface NamedExports extends Node {
parent: ExportDeclaration;
kind: SyntaxKind.NamedExports;
parent?: ExportDeclaration;
elements: NodeArray<ExportSpecifier>;
@@ -1925,7 +1923,6 @@ namespace ts {
}
export interface ExportSpecifier extends Declaration {
parent: NamedExports;
kind: SyntaxKind.ExportSpecifier;
parent?: NamedExports;
propertyName?: Identifier; // Name preceding "as" keyword (or undefined when "as" is absent)

View File

@@ -992,7 +992,7 @@ namespace FourSlash {
for (const startRange of toArray(startRanges)) {
this.goToRangeStart(startRange);
const fullActual = this.findReferencesAtCaret().map<ReferenceJson>(({ definition, references }) => ({
const fullActual = ts.map<ts.ReferencedSymbol, ReferenceJson>(this.findReferencesAtCaret(), ({ definition, references }) => ({
definition: definition.displayParts.map(d => d.text).join(""),
ranges: references
}));
@@ -2383,7 +2383,7 @@ namespace FourSlash {
else {
if (actual === undefined) {
this.raiseError(`${name} failed - expected the template {newText: "${expected.newText}", caretOffset: "${expected.caretOffset}"} but got nothing instead`);
}
if (actual.newText !== expected.newText) {

View File

@@ -21,19 +21,15 @@ namespace ts.DocumentHighlights {
return referenceEntries && convertReferencedSymbols(referenceEntries);
}
function convertReferencedSymbols(referenceEntries: ReferenceEntry[]): DocumentHighlights[] {
function convertReferencedSymbols(referenceEntries: FindAllReferences.Entry[]): DocumentHighlights[] {
const fileNameToDocumentHighlights = createMap<HighlightSpan[]>();
for (const referenceEntry of referenceEntries) {
const fileName = referenceEntry.fileName;
for (const entry of referenceEntries) {
const { fileName, span } = FindAllReferences.toHighlightSpan(entry);
let highlightSpans = fileNameToDocumentHighlights.get(fileName);
if (!highlightSpans) {
fileNameToDocumentHighlights.set(fileName, highlightSpans = []);
}
highlightSpans.push({
textSpan: referenceEntry.textSpan,
kind: referenceEntry.isWriteAccess ? HighlightSpanKind.writtenReference : HighlightSpanKind.reference
});
highlightSpans.push(span);
}
return arrayFrom(fileNameToDocumentHighlights.entries(), ([fileName, highlightSpans ]) => ({ fileName, highlightSpans }));

View File

@@ -2,6 +2,33 @@
/* @internal */
namespace ts.FindAllReferences {
export interface SymbolAndEntries {
definition: Definition | undefined;
references: Entry[];
}
type Definition =
| { type: "symbol"; symbol: Symbol; node: Node; }
| { type: "label"; node: Identifier; }
| { type: "keyword"; node: ts.Node; }
| { type: "this"; node: ts.Node; }
| { type: "string"; node: ts.StringLiteral };
export type Entry = NodeEntry | SpanEntry;
export interface NodeEntry {
type: "node";
node: Node;
isInString: boolean;
}
interface SpanEntry {
type: "span";
fileName: string;
textSpan: TextSpan;
}
export function nodeEntry(node: ts.Node, isInString = false): NodeEntry {
return { type: "node", node, isInString };
}
export interface Options {
readonly findInStrings?: boolean;
readonly findInComments?: boolean;
@@ -16,29 +43,41 @@ namespace ts.FindAllReferences {
export function findReferencedSymbols(checker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], sourceFile: SourceFile, position: number): ReferencedSymbol[] | undefined {
const referencedSymbols = findAllReferencedSymbols(checker, cancellationToken, sourceFiles, sourceFile, position);
// Only include referenced symbols that have a valid definition.
return filter(referencedSymbols, rs => !!rs.definition);
if (!referencedSymbols || !referencedSymbols.length) {
return undefined;
}
const out: ReferencedSymbol[] = [];
for (const { definition, references } of referencedSymbols) {
// Only include referenced symbols that have a valid definition.
if (definition) {
out.push({ definition: definitionToReferencedSymbolDefinitionInfo(definition, checker), references: references.map(toReferenceEntry) });
}
}
return out;
}
export function getImplementationsAtPosition(checker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], sourceFile: SourceFile, position: number): ImplementationLocation[] {
const node = getTouchingPropertyName(sourceFile, position);
const referenceEntries = getImplementationReferenceEntries(checker, cancellationToken, sourceFiles, node);
return map(referenceEntries, ({ textSpan, fileName }) => ({ textSpan, fileName }));
return map(referenceEntries, entry => toImplementationLocation(entry, checker));
}
function getImplementationReferenceEntries(typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], node: Node): ReferenceEntry[] {
function getImplementationReferenceEntries(typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], node: Node): Entry[] | undefined {
// If invoked directly on a shorthand property assignment, then return
// the declaration of the symbol being assigned (not the symbol being assigned to).
if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) {
const result: ReferenceEntry[] = [];
getReferenceEntriesForShorthandPropertyAssignment(node, typeChecker, node => result.push(getReferenceEntryFromNode(node)));
const result: NodeEntry[] = [];
Core.getReferenceEntriesForShorthandPropertyAssignment(node, typeChecker, node => result.push(nodeEntry(node)));
return result;
}
else if (node.kind === SyntaxKind.SuperKeyword || isSuperProperty(node.parent)) {
// References to and accesses on the super keyword only have one possible implementation, so no
// need to "Find all References"
const symbol = typeChecker.getSymbolAtLocation(node);
return symbol.valueDeclaration && [getReferenceEntryFromNode(symbol.valueDeclaration)];
return symbol.valueDeclaration && [nodeEntry(symbol.valueDeclaration)];
}
else {
// Perform "Find all References" and retrieve only those that are implementations
@@ -47,30 +86,188 @@ namespace ts.FindAllReferences {
}
export function findReferencedEntries(checker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], sourceFile: SourceFile, position: number, options?: Options): ReferenceEntry[] | undefined {
return flattenEntries(findAllReferencedSymbols(checker, cancellationToken, sourceFiles, sourceFile, position, options));
const x = flattenEntries(findAllReferencedSymbols(checker, cancellationToken, sourceFiles, sourceFile, position, options));
return map(x, toReferenceEntry);
}
function findAllReferencedSymbols(checker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], sourceFile: SourceFile, position: number, options?: Options): ReferencedSymbol[] | undefined {
export function getReferenceEntriesForNode(node: Node, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken, options: Options = {}): Entry[] | undefined {
return flattenEntries(Core.getReferencedSymbolsForNode(node, sourceFiles, checker, cancellationToken, options));
}
function findAllReferencedSymbols(checker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], sourceFile: SourceFile, position: number, options?: Options): SymbolAndEntries[] | undefined {
const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true);
return getReferencedSymbolsForNode(node, sourceFiles, checker, cancellationToken, options);
return Core.getReferencedSymbolsForNode(node, sourceFiles, checker, cancellationToken, options);
}
export function getReferenceEntriesForNode(node: Node, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken, options: Options = {}): ReferenceEntry[] | undefined {
return flattenEntries(getReferencedSymbolsForNode(node, sourceFiles, checker, cancellationToken, options));
}
function flattenEntries(referenceSymbols: ReferencedSymbol[]): ReferenceEntry[] {
function flattenEntries(referenceSymbols: SymbolAndEntries[]): Entry[] {
return referenceSymbols && flatMap(referenceSymbols, r => r.references);
}
function definitionToReferencedSymbolDefinitionInfo(def: Definition, checker: TypeChecker): ReferencedSymbolDefinitionInfo | undefined {
const info = (() => {
switch (def.type) {
case "symbol": {
const { symbol, node } = def;
const declarations = symbol.declarations;
if (!declarations || declarations.length === 0) {
return undefined;
}
const { displayParts, kind } = getDefinitionKindAndDisplayParts(symbol, node, checker);
const name = displayParts.map(p => p.text).join("");
return { node, name, kind, displayParts };
}
case "label": {
const { node } = def;
return { node, name: node.text, kind: ScriptElementKind.label, displayParts: [displayPart(node.text, SymbolDisplayPartKind.text)] };
}
case "keyword": {
const { node } = def;
const name = tokenToString(node.kind);
return { node, name, kind: ScriptElementKind.keyword, displayParts: [{ text: name, kind: ScriptElementKind.keyword }] };
}
case "this": {
const { node } = def;
const symbol = checker.getSymbolAtLocation(node);
const displayParts = symbol && SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(
checker, symbol, node.getSourceFile(), getContainerNode(node), node).displayParts;
return { node, name: "this", kind: ScriptElementKind.variableElement, displayParts };
}
case "string": {
const { node } = def;
return { node, name: node.text, kind: ScriptElementKind.variableElement, displayParts: [displayPart(getTextOfNode(node), SymbolDisplayPartKind.stringLiteral)] };
}
}
})();
if (!info) {
return undefined;
}
const { node, name, kind, displayParts } = info;
const sourceFile = node.getSourceFile();
return {
containerKind: "",
containerName: "",
fileName: sourceFile.fileName,
kind,
name,
textSpan: createTextSpanFromNode(node, sourceFile),
displayParts
};
}
function getDefinitionKindAndDisplayParts(symbol: Symbol, node: Node, checker: TypeChecker): { displayParts: SymbolDisplayPart[], kind: string } {
const { displayParts, symbolKind } =
SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, node.getSourceFile(), getContainerNode(node), node);
return { displayParts, kind: symbolKind };
}
function toReferenceEntry(entry: Entry): ReferenceEntry {
if (entry.type === "span") {
return { textSpan: entry.textSpan, fileName: entry.fileName, isWriteAccess: false, isDefinition: false };
}
const { node, isInString } = entry;
return {
fileName: node.getSourceFile().fileName,
textSpan: getTextSpan(node),
isWriteAccess: isWriteAccess(node),
isDefinition: isDeclarationName(node) || isLiteralComputedPropertyDeclarationName(node),
isInString: isInString ? true : undefined
};
}
function toImplementationLocation(entry: Entry, checker: ts.TypeChecker): ImplementationLocation {
if (entry.type === "node") {
const { node } = entry;
return { textSpan: getTextSpan(node), fileName: node.getSourceFile().fileName, ...implementationKindDisplayParts(node, checker) };
} else {
const { textSpan, fileName } = entry;
return { textSpan, fileName, kind: ScriptElementKind.unknown, displayParts: [] };
}
}
function implementationKindDisplayParts(node: ts.Node, checker: ts.TypeChecker): { kind: string, displayParts: SymbolDisplayPart[] } {
const symbol = checker.getSymbolAtLocation(isDeclaration(node) && node.name ? node.name : node);
if (symbol) {
return getDefinitionKindAndDisplayParts(symbol, node, checker);
}
else if (node.kind === SyntaxKind.ObjectLiteralExpression) {
return {
kind: ScriptElementKind.interfaceElement,
displayParts: [punctuationPart(SyntaxKind.OpenParenToken), textPart("object literal"), punctuationPart(SyntaxKind.CloseParenToken)]
};
}
else if (node.kind === SyntaxKind.ClassExpression) {
return {
kind: ScriptElementKind.localClassElement,
displayParts: [punctuationPart(SyntaxKind.OpenParenToken), textPart("anonymous local class"), punctuationPart(SyntaxKind.CloseParenToken)]
};
}
else {
return { kind: getNodeKind(node), displayParts: [] };
}
}
export function toHighlightSpan(entry: FindAllReferences.Entry): { fileName: string, span: HighlightSpan } {
if (entry.type === "span") {
const { fileName, textSpan } = entry;
return { fileName, span: { textSpan, kind: HighlightSpanKind.reference } };
}
const { node, isInString } = entry;
const fileName = entry.node.getSourceFile().fileName;
const writeAccess = isWriteAccess(node);
const span: HighlightSpan = {
textSpan: getTextSpan(node),
kind: writeAccess ? HighlightSpanKind.writtenReference : HighlightSpanKind.reference,
isInString: isInString ? true : undefined
};
return { fileName, span };
}
function getTextSpan(node: Node): TextSpan {
let start = node.getStart();
let end = node.getEnd();
if (node.kind === SyntaxKind.StringLiteral) {
start += 1;
end -= 1;
}
return createTextSpanFromBounds(start, end);
}
/** A node is considered a writeAccess iff it is a name of a declaration or a target of an assignment */
function isWriteAccess(node: Node): boolean {
if (node.kind === SyntaxKind.Identifier && isDeclarationName(node)) {
return true;
}
const parent = node.parent;
if (parent) {
if (parent.kind === SyntaxKind.PostfixUnaryExpression || parent.kind === SyntaxKind.PrefixUnaryExpression) {
return true;
}
else if (parent.kind === SyntaxKind.BinaryExpression && (<BinaryExpression>parent).left === node) {
const operator = (<BinaryExpression>parent).operatorToken.kind;
return SyntaxKind.FirstAssignment <= operator && operator <= SyntaxKind.LastAssignment;
}
}
return false;
}
}
/** Encapsulates the core find-all-references algorithm. */
/* @internal */
namespace ts.FindAllReferences.Core {
/** Core find-all-references algorithm. Handles special cases before delegating to `getReferencedSymbolsForSymbol`. */
function getReferencedSymbolsForNode(node: Node, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken, options: Options = {}): ReferencedSymbol[] | undefined {
export function getReferencedSymbolsForNode(node: Node, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken, options: Options = {}): SymbolAndEntries[] | undefined {
if (node.kind === ts.SyntaxKind.SourceFile) {
return undefined;
}
if (!options.implementations) {
const special = getReferencedSymbolsSpecial(node, sourceFiles, checker, cancellationToken);
const special = getReferencedSymbolsSpecial(node, sourceFiles, cancellationToken);
if (special) {
return special;
}
@@ -97,7 +294,7 @@ namespace ts.FindAllReferences {
}
/** getReferencedSymbols for special node kinds. */
function getReferencedSymbolsSpecial(node: Node, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken): ReferencedSymbol[] | undefined {
function getReferencedSymbolsSpecial(node: Node, sourceFiles: SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] | undefined {
if (isTypeKeyword(node.kind)) {
return getAllReferencesForKeyword(sourceFiles, node.kind, cancellationToken);
}
@@ -117,24 +314,24 @@ namespace ts.FindAllReferences {
}
if (isThis(node)) {
return getReferencesForThisKeyword(node, sourceFiles, checker, cancellationToken);
return getReferencesForThisKeyword(node, sourceFiles, cancellationToken);
}
if (node.kind === SyntaxKind.SuperKeyword) {
return getReferencesForSuperKeyword(node, checker, cancellationToken);
return getReferencesForSuperKeyword(node, cancellationToken);
}
return undefined;
}
/** Core find-all-references algorithm for a normal symbol. */
function getReferencedSymbolsForSymbol(symbol: Symbol, node: Node, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken, options: Options): ReferencedSymbol[] {
function getReferencedSymbolsForSymbol(symbol: Symbol, node: Node, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken, options: Options): SymbolAndEntries[] {
symbol = skipPastExportOrImportSpecifier(symbol, node, checker);
// Compute the meaning from the location and the symbol it references
const searchMeaning = getIntersectingMeaningFromDeclarations(getMeaningFromLocation(node), symbol.declarations);
const result: ReferencedSymbol[] = [];
const result: SymbolAndEntries[] = [];
const state = createState(sourceFiles, node, checker, cancellationToken, searchMeaning, options, result);
const search = state.createSearch(node, symbol, /*comingFrom*/undefined, { allSearchSymbols: populateSearchSymbolSet(symbol, node, checker, options.implementations) });
@@ -248,8 +445,8 @@ namespace ts.FindAllReferences {
markSeenReExportRHS(rhs: Identifier): boolean;
}
function createState(sourceFiles: SourceFile[], originalLocation: Node, checker: TypeChecker, cancellationToken: CancellationToken, searchMeaning: SemanticMeaning, options: Options, result: Push<ReferencedSymbol>): State {
const symbolIdToReferences: ReferenceEntry[][] = [];
function createState(sourceFiles: SourceFile[], originalLocation: Node, checker: TypeChecker, cancellationToken: CancellationToken, searchMeaning: SemanticMeaning, options: Options, result: Push<SymbolAndEntries>): State {
const symbolIdToReferences: Entry[][] = [];
const inheritsFromCache = createMap<boolean>();
// Source file ID → symbol ID → Whether the symbol has been searched for in the source file.
const sourceFileToSeenSymbols: Array<Array<true>> = [];
@@ -284,15 +481,15 @@ namespace ts.FindAllReferences {
let references = symbolIdToReferences[symbolId];
if (!references) {
references = symbolIdToReferences[symbolId] = [];
result.push({ definition: getDefinition(referenceSymbol, searchLocation, checker), references });
result.push({ definition: { type: "symbol", symbol: referenceSymbol, node: searchLocation }, references });
}
return node => references.push(getReferenceEntryFromNode(node));
return node => references.push({ type: "node", node, isInString: false });
}
function addStringOrCommentReference(fileName: string, textSpan: TextSpan): void {
result.push({
definition: undefined,
references: [{ fileName, textSpan, isWriteAccess: false, isDefinition: false }]
references: [{ type: "span", fileName, textSpan }]
});
}
@@ -361,26 +558,6 @@ namespace ts.FindAllReferences {
return getNameTable(sourceFile).get(escapedName) !== undefined;
}
function getDefinition(symbol: Symbol, node: Node, checker: TypeChecker): ReferencedSymbolDefinitionInfo | undefined {
const declarations = symbol.declarations;
if (!declarations || declarations.length === 0) {
return undefined;
}
const { displayParts, symbolKind } =
SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, node.getSourceFile(), getContainerNode(node), node);
const name = displayParts.map(p => p.text).join("");
return {
containerKind: "",
containerName: "",
name,
kind: symbolKind,
fileName: declarations[0].getSourceFile().fileName,
textSpan: createTextSpan(declarations[0].getStart(), 0),
displayParts
};
}
function getPropertySymbolOfDestructuringAssignment(location: Node, checker: TypeChecker): Symbol | undefined {
return isArrayLiteralOrObjectLiteralDestructuringPattern(location.parent.parent) &&
checker.getPropertySymbolOfDestructuringAssignment(<Identifier>location);
@@ -509,8 +686,8 @@ namespace ts.FindAllReferences {
return positions;
}
function getLabelReferencesInNode(container: Node, targetLabel: Identifier, cancellationToken: CancellationToken): ReferencedSymbol[] {
const references: ReferenceEntry[] = [];
function getLabelReferencesInNode(container: Node, targetLabel: Identifier, cancellationToken: CancellationToken): SymbolAndEntries[] {
const references: Entry[] = [];
const sourceFile = container.getSourceFile();
const labelName = targetLabel.text;
const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, labelName, container.getStart(), container.getEnd(), cancellationToken);
@@ -525,21 +702,11 @@ namespace ts.FindAllReferences {
// Only pick labels that are either the target label, or have a target that is the target label
if (node === targetLabel ||
(isJumpStatementTarget(node) && getTargetLabel(node, labelName) === targetLabel)) {
references.push(getReferenceEntryFromNode(node));
references.push(nodeEntry(node));
}
}
const definition: ReferencedSymbolDefinitionInfo = {
containerKind: "",
containerName: "",
fileName: targetLabel.getSourceFile().fileName,
kind: ScriptElementKind.label,
name: labelName,
textSpan: createTextSpanFromNode(targetLabel, sourceFile),
displayParts: [displayPart(labelName, SymbolDisplayPartKind.text)]
};
return [{ definition, references }];
return [{ definition: { type: "label", node: targetLabel }, references }];
}
function isValidReferencePosition(node: Node, searchSymbolName: string): boolean {
@@ -561,41 +728,22 @@ namespace ts.FindAllReferences {
}
}
function getAllReferencesForKeyword(sourceFiles: SourceFile[], keywordKind: ts.SyntaxKind, cancellationToken: CancellationToken): ReferencedSymbol[] {
const name = tokenToString(keywordKind);
const references: ReferenceEntry[] = [];
function getAllReferencesForKeyword(sourceFiles: SourceFile[], keywordKind: ts.SyntaxKind, cancellationToken: CancellationToken): SymbolAndEntries[] {
const references: NodeEntry[] = [];
for (const sourceFile of sourceFiles) {
cancellationToken.throwIfCancellationRequested();
addReferencesForKeywordInFile(sourceFile, keywordKind, name, cancellationToken, references);
addReferencesForKeywordInFile(sourceFile, keywordKind, tokenToString(keywordKind), cancellationToken, references);
}
if (!references.length) return undefined;
const definition: ReferencedSymbolDefinitionInfo = {
containerKind: "",
containerName: "",
fileName: references[0].fileName,
kind: ScriptElementKind.keyword,
name,
textSpan: references[0].textSpan,
displayParts: [{ text: name, kind: ScriptElementKind.keyword }]
};
return [{ definition, references }];
return references.length ? [{ definition: { type: "keyword", node: references[0].node }, references }] : undefined;
}
function addReferencesForKeywordInFile(sourceFile: SourceFile, kind: SyntaxKind, searchText: string, cancellationToken: CancellationToken, references: Push<ReferenceEntry>): void {
function addReferencesForKeywordInFile(sourceFile: SourceFile, kind: SyntaxKind, searchText: string, cancellationToken: CancellationToken, references: Push<NodeEntry>): void {
const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, searchText, sourceFile.getStart(), sourceFile.getEnd(), cancellationToken);
for (const position of possiblePositions) {
cancellationToken.throwIfCancellationRequested();
const referenceLocation = getTouchingPropertyName(sourceFile, position);
if (referenceLocation.kind === kind) {
references.push({
textSpan: createTextSpanFromNode(referenceLocation),
fileName: sourceFile.fileName,
isWriteAccess: false,
isDefinition: false,
});
references.push(nodeEntry(referenceLocation));
}
}
}
@@ -683,7 +831,7 @@ namespace ts.FindAllReferences {
}
if (!propertyName) {
addRef()
addRef();
}
else if (referenceLocation === propertyName) {
// For `export { foo as bar } from "baz"`, "`foo`" will be added from the singleReferences for import searches of the original export.
@@ -1034,7 +1182,7 @@ namespace ts.FindAllReferences {
}
}
function getReferencesForSuperKeyword(superKeyword: Node, checker: TypeChecker, cancellationToken: CancellationToken): ReferencedSymbol[] {
function getReferencesForSuperKeyword(superKeyword: Node, cancellationToken: CancellationToken): SymbolAndEntries[] {
let searchSpaceNode = getSuperContainer(superKeyword, /*stopOnFunctions*/ false);
if (!searchSpaceNode) {
return undefined;
@@ -1057,7 +1205,7 @@ namespace ts.FindAllReferences {
return undefined;
}
const references: ReferenceEntry[] = [];
const references: Entry[] = [];
const sourceFile = searchSpaceNode.getSourceFile();
const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "super", searchSpaceNode.getStart(), searchSpaceNode.getEnd(), cancellationToken);
@@ -1076,15 +1224,14 @@ namespace ts.FindAllReferences {
// 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 && (ModifierFlags.Static & getModifierFlags(container)) === staticFlag && container.parent.symbol === searchSpaceNode.symbol) {
references.push(getReferenceEntryFromNode(node));
references.push(nodeEntry(node));
}
}
const definition = getDefinition(searchSpaceNode.symbol, superKeyword, checker);
return [{ definition, references }];
return [{ definition: { type: "symbol", symbol: searchSpaceNode.symbol, node: superKeyword }, references }];
}
function getReferencesForThisKeyword(thisOrSuperKeyword: Node, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken): ReferencedSymbol[] {
function getReferencesForThisKeyword(thisOrSuperKeyword: Node, sourceFiles: SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] {
let searchSpaceNode = getThisContainer(thisOrSuperKeyword, /* includeArrowFunctions */ false);
// Whether 'this' occurs in a static context within a class.
@@ -1119,7 +1266,7 @@ namespace ts.FindAllReferences {
return undefined;
}
const references: ReferenceEntry[] = [];
const references: Entry[] = [];
let possiblePositions: number[];
if (searchSpaceNode.kind === SyntaxKind.SourceFile) {
@@ -1134,25 +1281,12 @@ namespace ts.FindAllReferences {
getThisReferencesInFile(sourceFile, searchSpaceNode, possiblePositions, references);
}
const thisOrSuperSymbol = checker.getSymbolAtLocation(thisOrSuperKeyword);
const displayParts = thisOrSuperSymbol && SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(
checker, thisOrSuperSymbol, thisOrSuperKeyword.getSourceFile(), getContainerNode(thisOrSuperKeyword), thisOrSuperKeyword).displayParts;
return [{
definition: {
containerKind: "",
containerName: "",
fileName: thisOrSuperKeyword.getSourceFile().fileName,
kind: ScriptElementKind.variableElement,
name: "this",
textSpan: createTextSpanFromNode(thisOrSuperKeyword),
displayParts
},
references: references
definition: { type: "this", node: thisOrSuperKeyword },
references
}];
function getThisReferencesInFile(sourceFile: SourceFile, searchSpaceNode: Node, possiblePositions: number[], result: ReferenceEntry[]): void {
function getThisReferencesInFile(sourceFile: SourceFile, searchSpaceNode: Node, possiblePositions: number[], result: Entry[]): void {
forEach(possiblePositions, position => {
cancellationToken.throwIfCancellationRequested();
@@ -1167,13 +1301,13 @@ namespace ts.FindAllReferences {
case SyntaxKind.FunctionExpression:
case SyntaxKind.FunctionDeclaration:
if (searchSpaceNode.symbol === container.symbol) {
result.push(getReferenceEntryFromNode(node));
result.push(nodeEntry(node));
}
break;
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
if (isObjectLiteralMethod(searchSpaceNode) && searchSpaceNode.symbol === container.symbol) {
result.push(getReferenceEntryFromNode(node));
result.push(nodeEntry(node));
}
break;
case SyntaxKind.ClassExpression:
@@ -1181,12 +1315,12 @@ namespace ts.FindAllReferences {
// Make sure the container belongs to the same class
// and has the appropriate static modifier from the original container.
if (container.parent && searchSpaceNode.symbol === container.parent.symbol && (getModifierFlags(container) & ModifierFlags.Static) === staticFlag) {
result.push(getReferenceEntryFromNode(node));
result.push(nodeEntry(node));
}
break;
case SyntaxKind.SourceFile:
if (container.kind === SyntaxKind.SourceFile && !isExternalModule(<SourceFile>container)) {
result.push(getReferenceEntryFromNode(node));
result.push(nodeEntry(node));
}
break;
}
@@ -1194,7 +1328,7 @@ namespace ts.FindAllReferences {
}
}
function getReferencesForStringLiteral(node: StringLiteral, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken): ReferencedSymbol[] {
function getReferencesForStringLiteral(node: StringLiteral, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken): SymbolAndEntries[] {
const type = getStringLiteralTypeForNode(node, checker);
if (!type) {
@@ -1202,7 +1336,7 @@ namespace ts.FindAllReferences {
return undefined;
}
const references: ReferenceEntry[] = [];
const references: NodeEntry[] = [];
for (const sourceFile of sourceFiles) {
const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, type.text, sourceFile.getStart(), sourceFile.getEnd(), cancellationToken);
@@ -1210,19 +1344,11 @@ namespace ts.FindAllReferences {
}
return [{
definition: {
containerKind: "",
containerName: "",
fileName: node.getSourceFile().fileName,
kind: ScriptElementKind.variableElement,
name: type.text,
textSpan: createTextSpanFromNode(node),
displayParts: [displayPart(getTextOfNode(node), SymbolDisplayPartKind.stringLiteral)]
},
references: references
definition: { type: "string", node },
references
}];
function getReferencesForStringLiteralInFile(sourceFile: SourceFile, searchType: Type, possiblePositions: number[], references: ReferenceEntry[]): void {
function getReferencesForStringLiteralInFile(sourceFile: SourceFile, searchType: Type, possiblePositions: number[], references: Push<NodeEntry>): void {
for (const position of possiblePositions) {
cancellationToken.throwIfCancellationRequested();
@@ -1233,7 +1359,7 @@ namespace ts.FindAllReferences {
const type = getStringLiteralTypeForNode(<StringLiteral>node, checker);
if (type === searchType) {
references.push(getReferenceEntryFromNode(node));
references.push(nodeEntry(node, /*isInString*/true));
}
}
}
@@ -1537,7 +1663,7 @@ namespace ts.FindAllReferences {
}
}
function getReferenceEntriesForShorthandPropertyAssignment(node: Node, checker: TypeChecker, addReference: (node: Node) => void): void {
export function getReferenceEntriesForShorthandPropertyAssignment(node: Node, checker: TypeChecker, addReference: (node: Node) => void): void {
const refSymbol = checker.getSymbolAtLocation(node);
const shorthandSymbol = checker.getShorthandAssignmentValueSymbol(refSymbol.valueDeclaration);
@@ -1550,45 +1676,6 @@ namespace ts.FindAllReferences {
}
}
function getReferenceEntryFromNode(node: Node): ReferenceEntry {
return {
fileName: node.getSourceFile().fileName,
textSpan: getTextSpan(node),
isWriteAccess: isWriteAccess(node),
isDefinition: isDeclarationName(node) || isLiteralComputedPropertyDeclarationName(node)
};
}
function getTextSpan(node: Node): TextSpan {
let start = node.getStart();
let end = node.getEnd();
if (node.kind === SyntaxKind.StringLiteral) {
start += 1;
end -= 1;
}
return createTextSpanFromBounds(start, end);
}
/** A node is considered a writeAccess iff it is a name of a declaration or a target of an assignment */
function isWriteAccess(node: Node): boolean {
if (node.kind === SyntaxKind.Identifier && isDeclarationName(node)) {
return true;
}
const parent = node.parent;
if (parent) {
if (parent.kind === SyntaxKind.PostfixUnaryExpression || parent.kind === SyntaxKind.PrefixUnaryExpression) {
return true;
}
else if (parent.kind === SyntaxKind.BinaryExpression && (<BinaryExpression>parent).left === node) {
const operator = (<BinaryExpression>parent).operatorToken.kind;
return SyntaxKind.FirstAssignment <= operator && operator <= SyntaxKind.LastAssignment;
}
}
return false;
}
function forEachDescendantOfKind(node: Node, kind: SyntaxKind, action: (node: Node) => void): void {
forEachChild(node, child => {
if (child.kind === kind) {
@@ -1639,19 +1726,4 @@ namespace ts.FindAllReferences {
return getSymbolsForClassAndInterfaceComponents(<UnionOrIntersectionType>localParentType);
}
}
/** True if the symbol is for an external module, as opposed to a namespace. */
export function isExternalModuleSymbol(moduleSymbol: Symbol): boolean {
Debug.assert(!!(moduleSymbol.flags & SymbolFlags.Module));
return moduleSymbol.name.charCodeAt(0) === CharacterCodes.doubleQuote;
}
/** Returns `true` the first time it encounters a node and `false` afterwards. */
export function nodeSeenTracker<T extends Node>(): (node: T) => boolean {
const seen: Array<true> = [];
return node => {
const id = getNodeId(node);
return !seen[id] && (seen[id] = true);
};
}
}

View File

@@ -491,7 +491,7 @@ namespace ts.FindAllReferences {
/** If at an export specifier, go to the symbol it refers to. */
function skipExportSpecifierSymbol(symbol: Symbol, checker: TypeChecker): Symbol {
// For `export { foo } from './bar", there's nothing to skip, because it does not create a new alias. But `export { foo } does.
for (const declaration of symbol.declarations) {
if (symbol.declarations) for (const declaration of symbol.declarations) {
if (isExportSpecifier(declaration) && !(declaration as ExportSpecifier).propertyName && !(declaration as ExportSpecifier).parent.parent.moduleSpecifier) {
return checker.getExportSpecifierLocalTargetSymbol(declaration);
}

View File

@@ -479,6 +479,7 @@ namespace ts {
displayParts: SymbolDisplayPart[];
}
//!!! internal implementation details leaked!!!
export interface ReferencedSymbolOf<T extends DocumentSpan> {
definition: ReferencedSymbolDefinitionInfo;
references: T[];

View File

@@ -1132,6 +1132,21 @@ namespace ts {
return false;
}
}
/** True if the symbol is for an external module, as opposed to a namespace. */
export function isExternalModuleSymbol(moduleSymbol: Symbol): boolean {
Debug.assert(!!(moduleSymbol.flags & SymbolFlags.Module));
return moduleSymbol.name.charCodeAt(0) === CharacterCodes.doubleQuote;
}
/** Returns `true` the first time it encounters a node and `false` afterwards. */
export function nodeSeenTracker<T extends Node>(): (node: T) => boolean {
const seen: Array<true> = [];
return node => {
const id = getNodeId(node);
return !seen[id] && (seen[id] = true);
};
}
}
// Display-part writer helpers