Skip to content

Make findAllReferences work on triple-slash reference paths that resolve to scripts #41936

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 49 additions & 16 deletions src/services/findAllReferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ namespace ts.FindAllReferences {
readonly references: readonly Entry[];
}

export const enum DefinitionKind { Symbol, Label, Keyword, This, String }
export const enum DefinitionKind { Symbol, Label, Keyword, This, String, TripleSlashReference }
export type Definition =
| { readonly type: DefinitionKind.Symbol; readonly symbol: Symbol }
| { readonly type: DefinitionKind.Label; readonly node: Identifier }
| { readonly type: DefinitionKind.Keyword; readonly node: Node }
| { readonly type: DefinitionKind.This; readonly node: Node }
| { readonly type: DefinitionKind.String; readonly node: StringLiteralLike };
| { readonly type: DefinitionKind.String; readonly node: StringLiteralLike }
| { readonly type: DefinitionKind.TripleSlashReference; readonly reference: FileReference, readonly file: SourceFile };

export const enum EntryKind { Span, Node, StringLiteral, SearchedLocalFoundProperty, SearchedPropertyFoundLocal }
export type NodeEntryKind = EntryKind.Node | EntryKind.StringLiteral | EntryKind.SearchedLocalFoundProperty | EntryKind.SearchedPropertyFoundLocal;
Expand Down Expand Up @@ -298,17 +299,16 @@ namespace ts.FindAllReferences {
}

function definitionToReferencedSymbolDefinitionInfo(def: Definition, checker: TypeChecker, originalNode: Node): ReferencedSymbolDefinitionInfo {
const info = (() => {
const info = ((): { sourceFile: SourceFile, textSpan: TextSpan, name: string, kind: ScriptElementKind, displayParts: SymbolDisplayPart[], context?: Node | ContextWithStartAndEndNode } => {
switch (def.type) {
case DefinitionKind.Symbol: {
const { symbol } = def;
const { displayParts, kind } = getDefinitionKindAndDisplayParts(symbol, checker, originalNode);
const name = displayParts.map(p => p.text).join("");
const declaration = symbol.declarations && firstOrUndefined(symbol.declarations);
const node = declaration ? (getNameOfDeclaration(declaration) || declaration) : originalNode;
return {
node: declaration ?
getNameOfDeclaration(declaration) || declaration :
originalNode,
...getFileAndTextSpanFromNode(node),
name,
kind,
displayParts,
Expand All @@ -317,32 +317,44 @@ namespace ts.FindAllReferences {
}
case DefinitionKind.Label: {
const { node } = def;
return { node, name: node.text, kind: ScriptElementKind.label, displayParts: [displayPart(node.text, SymbolDisplayPartKind.text)] };
return { ...getFileAndTextSpanFromNode(node), name: node.text, kind: ScriptElementKind.label, displayParts: [displayPart(node.text, SymbolDisplayPartKind.text)] };
}
case DefinitionKind.Keyword: {
const { node } = def;
const name = tokenToString(node.kind)!;
return { node, name, kind: ScriptElementKind.keyword, displayParts: [{ text: name, kind: ScriptElementKind.keyword }] };
return { ...getFileAndTextSpanFromNode(node), name, kind: ScriptElementKind.keyword, displayParts: [{ text: name, kind: ScriptElementKind.keyword }] };
}
case DefinitionKind.This: {
const { node } = def;
const symbol = checker.getSymbolAtLocation(node);
const displayParts = symbol && SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(
checker, symbol, node.getSourceFile(), getContainerNode(node), node).displayParts || [textPart("this")];
return { node, name: "this", kind: ScriptElementKind.variableElement, displayParts };
return { ...getFileAndTextSpanFromNode(node), name: "this", kind: ScriptElementKind.variableElement, displayParts };
}
case DefinitionKind.String: {
const { node } = def;
return { node, name: node.text, kind: ScriptElementKind.variableElement, displayParts: [displayPart(getTextOfNode(node), SymbolDisplayPartKind.stringLiteral)] };
return {
...getFileAndTextSpanFromNode(node),
name: node.text,
kind: ScriptElementKind.variableElement,
displayParts: [displayPart(getTextOfNode(node), SymbolDisplayPartKind.stringLiteral)]
};
}
case DefinitionKind.TripleSlashReference: {
return {
textSpan: createTextSpanFromRange(def.reference),
sourceFile: def.file,
name: def.reference.fileName,
kind: ScriptElementKind.string,
displayParts: [displayPart(`"${def.reference.fileName}"`, SymbolDisplayPartKind.stringLiteral)]
};
}
default:
return Debug.assertNever(def);
}
})();

const { node, name, kind, displayParts, context } = info;
const sourceFile = node.getSourceFile();
const textSpan = getTextSpan(isComputedPropertyName(node) ? node.expression : node, sourceFile);
const { sourceFile, textSpan, name, kind, displayParts, context } = info;
return {
containerKind: ScriptElementKind.unknown,
containerName: "",
Expand All @@ -355,6 +367,14 @@ namespace ts.FindAllReferences {
};
}

function getFileAndTextSpanFromNode(node: Node) {
const sourceFile = node.getSourceFile();
return {
sourceFile,
textSpan: getTextSpan(isComputedPropertyName(node) ? node.expression : node, sourceFile)
};
}

function getDefinitionKindAndDisplayParts(symbol: Symbol, checker: TypeChecker, node: Node): { displayParts: SymbolDisplayPart[], kind: ScriptElementKind } {
const meaning = Core.getIntersectingMeaningFromDeclarations(node, symbol);
const enclosingDeclaration = symbol.declarations && firstOrUndefined(symbol.declarations) || node;
Expand Down Expand Up @@ -603,9 +623,22 @@ namespace ts.FindAllReferences {
node = getAdjustedRenameLocation(node);
}
if (isSourceFile(node)) {
const reference = GoToDefinition.getReferenceAtPosition(node, position, program);
const moduleSymbol = reference && program.getTypeChecker().getMergedSymbol(reference.file.symbol);
return moduleSymbol && getReferencedSymbolsForModule(program, moduleSymbol, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet);
const resolvedRef = GoToDefinition.getReferenceAtPosition(node, position, program);
if (!resolvedRef) {
return undefined;
}
const moduleSymbol = program.getTypeChecker().getMergedSymbol(resolvedRef.file.symbol);
if (moduleSymbol) {
return getReferencedSymbolsForModule(program, moduleSymbol, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet);
}
const fileIncludeReasons = program.getFileIncludeReasons();
if (!fileIncludeReasons) {
return undefined;
}
return [{
definition: { type: DefinitionKind.TripleSlashReference, reference: resolvedRef.reference, file: node },
references: getReferencesForNonModule(resolvedRef.file, fileIncludeReasons, program) || emptyArray
}];
}

if (!options.implementations) {
Expand Down
14 changes: 7 additions & 7 deletions src/services/goToDefinition.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/* @internal */
namespace ts.GoToDefinition {
export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile, position: number): readonly DefinitionInfo[] | undefined {
const reference = getReferenceAtPosition(sourceFile, position, program);
if (reference) {
return [getDefinitionInfoForFileReference(reference.fileName, reference.file.fileName)];
const resolvedRef = getReferenceAtPosition(sourceFile, position, program);
if (resolvedRef) {
return [getDefinitionInfoForFileReference(resolvedRef.reference.fileName, resolvedRef.file.fileName)];
}

const node = getTouchingPropertyName(sourceFile, position);
Expand Down Expand Up @@ -108,24 +108,24 @@ namespace ts.GoToDefinition {
|| (!isCallLikeExpression(calledDeclaration.parent) && s === calledDeclaration.parent.symbol);
}

export function getReferenceAtPosition(sourceFile: SourceFile, position: number, program: Program): { fileName: string, file: SourceFile } | undefined {
export function getReferenceAtPosition(sourceFile: SourceFile, position: number, program: Program): { reference: FileReference, file: SourceFile } | undefined {
const referencePath = findReferenceInPosition(sourceFile.referencedFiles, position);
if (referencePath) {
const file = program.getSourceFileFromReference(sourceFile, referencePath);
return file && { fileName: referencePath.fileName, file };
return file && { reference: referencePath, file };
}

const typeReferenceDirective = findReferenceInPosition(sourceFile.typeReferenceDirectives, position);
if (typeReferenceDirective) {
const reference = program.getResolvedTypeReferenceDirectives().get(typeReferenceDirective.fileName);
const file = reference && program.getSourceFile(reference.resolvedFileName!); // TODO:GH#18217
return file && { fileName: typeReferenceDirective.fileName, file };
return file && { reference: typeReferenceDirective, file };
}

const libReferenceDirective = findReferenceInPosition(sourceFile.libReferenceDirectives, position);
if (libReferenceDirective) {
const file = program.getLibFileFromReference(libReferenceDirective);
return file && { fileName: libReferenceDirective.fileName, file };
return file && { reference: libReferenceDirective, file };
}

return undefined;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// === /c.js ===
// require([|"./b"|]);
// require("globals");

// === /a.ts ===
// /// <reference path="[|b.ts|]/*FIND ALL REFS*/" />
// /// <reference types="globals" />

[
{
"definition": {
"containerKind": "",
"containerName": "",
"fileName": "/a.ts",
"kind": "string",
"name": "b.ts",
"textSpan": {
"start": 21,
"length": 4
},
"displayParts": [
{
"text": "\"b.ts\"",
"kind": "stringLiteral"
}
]
},
"references": [
{
"textSpan": {
"start": 21,
"length": 4
},
"fileName": "/a.ts",
"isWriteAccess": false,
"isDefinition": false
},
{
"textSpan": {
"start": 8,
"length": 5
},
"fileName": "/c.js",
"isWriteAccess": false,
"isDefinition": false
}
]
}
]

// === /c.js ===
// require("./b");
// require([|"globals"|]);

// === /a.ts ===
// /// <reference path="b.ts" />
// /// <reference types="[|globals|]/*FIND ALL REFS*/" />

[
{
"definition": {
"containerKind": "",
"containerName": "",
"fileName": "/a.ts",
"kind": "string",
"name": "globals",
"textSpan": {
"start": 52,
"length": 7
},
"displayParts": [
{
"text": "\"globals\"",
"kind": "stringLiteral"
}
]
},
"references": [
{
"textSpan": {
"start": 52,
"length": 7
},
"fileName": "/a.ts",
"isWriteAccess": false,
"isDefinition": false
},
{
"textSpan": {
"start": 24,
"length": 9
},
"fileName": "/c.js",
"isWriteAccess": false,
"isDefinition": false
}
]
}
]
19 changes: 19 additions & 0 deletions tests/cases/fourslash/findAllReferencesTripleSlash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/// <reference path="fourslash.ts" />

// @checkJs: true

// @Filename: /node_modules/@types/globals/index.d.ts
//// declare const someAmbientGlobal: unknown;

// @Filename: /a.ts
//// /// <reference path="b.ts/*1*/" />
//// /// <reference types="globals/*2*/" />

// @Filename: /b.ts
//// console.log("b.ts");

// @Filename: /c.js
//// require("./b");
//// require("globals");

verify.baselineFindAllReferences("1", "2");