Find references of a module by filename (#41805)

* Naive implementation enough to build and write a test

* Add simple test

* Add project references test

* Add deduplication test, accept baselines

* Add test for referencing a script (doesn’t do anything)

* Update API baselines

* Use refFileMap for non-module references

* Fix find-all-refs on module specifier

* Remove unused util

* Don’t store text range on ts.RefFile

* Ensure string literal could itself be a file reference

* Remove unused utilities

* Improve baseline format

* Preserve old behavior of falling back to string literal references

* Update baselines from master

* Fix old RefFileMap code after merge

* Add test for additional response info

* Undo test change
This commit is contained in:
Andrew Branch 2020-12-11 12:37:02 -08:00 committed by GitHub
parent 1c1cd9b08d
commit 9dfbf07d8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 810 additions and 45 deletions

View File

@ -362,6 +362,18 @@ namespace ts.server {
}));
}
getFileReferences(fileName: string): ReferenceEntry[] {
const request = this.processRequest<protocol.FileReferencesRequest>(CommandNames.FileReferences, { file: fileName });
const response = this.processResponse<protocol.FileReferencesResponse>(request);
return response.body!.refs.map(entry => ({ // TODO: GH#18217
fileName: entry.file,
textSpan: this.decodeSpan(entry),
isWriteAccess: entry.isWriteAccess,
isDefinition: entry.isDefinition,
}));
}
getEmitOutput(file: string): EmitOutput {
const request = this.processRequest<protocol.EmitOutputRequest>(protocol.CommandTypes.EmitOutput, { file });
const response = this.processResponse<protocol.EmitOutputResponse>(request);

View File

@ -1114,38 +1114,77 @@ namespace FourSlash {
}
}
public verifyBaselineFindAllReferences(markerName: string) {
const marker = this.getMarkerByName(markerName);
const references = this.languageService.findReferences(marker.fileName, marker.position);
public verifyBaselineFindAllReferences(...markerNames: string[]) {
const baseline = markerNames.map(markerName => {
const marker = this.getMarkerByName(markerName);
const references = this.languageService.findReferences(marker.fileName, marker.position);
const refsByFile = references
? ts.group(ts.sort(ts.flatMap(references, r => r.references), (a, b) => a.textSpan.start - b.textSpan.start), ref => ref.fileName)
: ts.emptyArray;
// Write input files
const baselineContent = this.getBaselineContentForGroupedReferences(refsByFile, markerName);
// Write response JSON
return baselineContent + JSON.stringify(references, undefined, 2);
}).join("\n\n");
Harness.Baseline.runBaseline(this.getBaselineFileNameForContainingTestFile(".baseline.jsonc"), baseline);
}
public verifyBaselineGetFileReferences(fileName: string) {
const references = this.languageService.getFileReferences(fileName);
const refsByFile = references
? ts.group(ts.sort(ts.flatMap(references, r => r.references), (a, b) => a.textSpan.start - b.textSpan.start), ref => ref.fileName)
? ts.group(ts.sort(references, (a, b) => a.textSpan.start - b.textSpan.start), ref => ref.fileName)
: ts.emptyArray;
// Write input files
let baselineContent = this.getBaselineContentForGroupedReferences(refsByFile);
// Write response JSON
baselineContent += JSON.stringify(references, undefined, 2);
Harness.Baseline.runBaseline(this.getBaselineFileNameForContainingTestFile(".baseline.jsonc"), baselineContent);
}
private getBaselineContentForGroupedReferences(refsByFile: readonly (readonly ts.ReferenceEntry[])[], markerName?: string) {
const marker = markerName !== undefined ? this.getMarkerByName(markerName) : undefined;
let baselineContent = "";
for (const group of refsByFile) {
baselineContent += getBaselineContentForFile(group[0].fileName, this.getFileContent(group[0].fileName));
baselineContent += "\n\n";
}
// Write response JSON
baselineContent += JSON.stringify(references, undefined, 2);
Harness.Baseline.runBaseline(this.getBaselineFileNameForContainingTestFile(".baseline.jsonc"), baselineContent);
return baselineContent;
function getBaselineContentForFile(fileName: string, content: string) {
let newContent = `=== ${fileName} ===\n`;
let pos = 0;
for (const { textSpan } of refsByFile.find(refs => refs[0].fileName === fileName) ?? ts.emptyArray) {
if (fileName === marker.fileName && ts.textSpanContainsPosition(textSpan, marker.position)) {
newContent += "/*FIND ALL REFS*/";
}
const end = textSpan.start + textSpan.length;
newContent += content.slice(pos, textSpan.start);
newContent += "[|";
newContent += content.slice(textSpan.start, end);
pos = textSpan.start;
// It's easier to read if the /*FIND ALL REFS*/ comment is outside the range markers, which makes
// this code a bit more verbose than it would be if I were less picky about the baseline format.
if (fileName === marker?.fileName && marker.position === textSpan.start) {
newContent += "/*FIND ALL REFS*/";
newContent += "[|";
}
else if (fileName === marker?.fileName && ts.textSpanContainsPosition(textSpan, marker.position)) {
newContent += "[|";
newContent += content.slice(pos, marker.position);
newContent += "/*FIND ALL REFS*/";
pos = marker.position;
}
else {
newContent += "[|";
}
newContent += content.slice(pos, end);
newContent += "|]";
pos = end;
}
if (marker?.fileName === fileName && marker.position >= pos) {
newContent += content.slice(pos, marker.position);
newContent += "/*FIND ALL REFS*/";
pos = marker.position;
}
newContent += content.slice(pos);
return newContent.split(/\r?\n/).map(l => "// " + l).join("\n");
}

View File

@ -332,8 +332,12 @@ namespace FourSlashInterface {
this.state.verifyTypeOfSymbolAtLocation(range, symbol, expected);
}
public baselineFindAllReferences(markerName: string) {
this.state.verifyBaselineFindAllReferences(markerName);
public baselineFindAllReferences(...markerNames: string[]) {
this.state.verifyBaselineFindAllReferences(...markerNames);
}
public baselineGetFileReferences(fileName: string) {
this.state.verifyBaselineGetFileReferences(fileName);
}
public referenceGroups(starts: ArrayOrSingle<string> | ArrayOrSingle<FourSlash.Range>, parts: ReferenceGroup[]) {

View File

@ -519,6 +519,9 @@ namespace Harness.LanguageService {
findReferences(fileName: string, position: number): ts.ReferencedSymbol[] {
return unwrapJSONCallResult(this.shim.findReferences(fileName, position));
}
getFileReferences(fileName: string): ts.ReferenceEntry[] {
return unwrapJSONCallResult(this.shim.getFileReferences(fileName));
}
getOccurrencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] {
return unwrapJSONCallResult(this.shim.getOccurrencesAtPosition(fileName, position));
}

View File

@ -37,6 +37,9 @@ namespace ts.server.protocol {
/* @internal */
EmitOutput = "emit-output",
Exit = "exit",
FileReferences = "fileReferences",
/* @internal */
FileReferencesFull = "fileReferences-full",
Format = "format",
Formatonkey = "formatonkey",
/* @internal */
@ -1152,6 +1155,25 @@ namespace ts.server.protocol {
body?: ReferencesResponseBody;
}
export interface FileReferencesRequest extends FileRequest {
command: CommandTypes.FileReferences;
}
export interface FileReferencesResponseBody {
/**
* The file locations referencing the symbol.
*/
refs: readonly ReferencesResponseItem[];
/**
* The name of the symbol.
*/
symbolName: string;
}
export interface FileReferencesResponse extends Response {
body?: FileReferencesResponseBody;
}
/**
* Argument for RenameRequest request.
*/

View File

@ -409,6 +409,29 @@ namespace ts.server {
return outputs.filter(o => o.references.length !== 0);
}
function combineProjectOutputForFileReferences(
projects: Projects,
defaultProject: Project,
fileName: string
): readonly ReferenceEntry[] {
const outputs: ReferenceEntry[] = [];
combineProjectOutputWorker(
projects,
defaultProject,
/*initialLocation*/ undefined,
project => {
for (const referenceEntry of project.getLanguageService().getFileReferences(fileName) || emptyArray) {
if (!contains(outputs, referenceEntry, documentSpansEqual)) {
outputs.push(referenceEntry);
}
}
},
);
return outputs;
}
interface ProjectAndLocation<TLocation extends DocumentPosition | undefined> {
readonly project: Project;
readonly location: TLocation;
@ -1509,7 +1532,7 @@ namespace ts.server {
return arrayFrom(map.values());
}
private getReferences(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.ReferencesResponseBody | undefined | readonly ReferencedSymbol[] {
private getReferences(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.ReferencesResponseBody | readonly ReferencedSymbol[] {
const file = toNormalizedPath(args.file);
const projects = this.getProjects(args);
const position = this.getPositionInFile(args, file);
@ -1528,22 +1551,28 @@ namespace ts.server {
const nameSpan = nameInfo && nameInfo.textSpan;
const symbolStartOffset = nameSpan ? scriptInfo.positionToLineOffset(nameSpan.start).offset : 0;
const symbolName = nameSpan ? scriptInfo.getSnapshot().getText(nameSpan.start, textSpanEnd(nameSpan)) : "";
const refs: readonly protocol.ReferencesResponseItem[] = flatMap(references, referencedSymbol =>
referencedSymbol.references.map(({ fileName, textSpan, contextSpan, isWriteAccess, isDefinition }): protocol.ReferencesResponseItem => {
const scriptInfo = Debug.checkDefined(this.projectService.getScriptInfo(fileName));
const span = toProtocolTextSpanWithContext(textSpan, contextSpan, scriptInfo);
const lineSpan = scriptInfo.lineToTextSpan(span.start.line - 1);
const lineText = scriptInfo.getSnapshot().getText(lineSpan.start, textSpanEnd(lineSpan)).replace(/\r|\n/g, "");
return {
file: fileName,
...span,
lineText,
isWriteAccess,
isDefinition
};
}));
const refs: readonly protocol.ReferencesResponseItem[] = flatMap(references, referencedSymbol => {
return referencedSymbol.references.map(entry => referenceEntryToReferencesResponseItem(this.projectService, entry));
});
return { refs, symbolName, symbolStartOffset, symbolDisplayString };
}
private getFileReferences(args: protocol.FileRequestArgs, simplifiedResult: boolean): protocol.FileReferencesResponseBody | readonly ReferenceEntry[] {
const projects = this.getProjects(args);
const references = combineProjectOutputForFileReferences(
projects,
this.getDefaultProject(args),
args.file,
);
if (!simplifiedResult) return references;
const refs = references.map(entry => referenceEntryToReferencesResponseItem(this.projectService, entry));
return {
refs,
symbolName: `"${args.file}"`
};
}
/**
* @param fileName is the name of the file to be opened
* @param fileContent is a version of the file content that is known to be more up to date than the one on disk
@ -2639,6 +2668,12 @@ namespace ts.server {
[CommandNames.GetSpanOfEnclosingComment]: (request: protocol.SpanOfEnclosingCommentRequest) => {
return this.requiredResponse(this.getSpanOfEnclosingComment(request.arguments));
},
[CommandNames.FileReferences]: (request: protocol.FileReferencesRequest) => {
return this.requiredResponse(this.getFileReferences(request.arguments, /*simplifiedResult*/ true));
},
[CommandNames.FileReferencesFull]: (request: protocol.FileReferencesRequest) => {
return this.requiredResponse(this.getFileReferences(request.arguments, /*simplifiedResult*/ false));
},
[CommandNames.Format]: (request: protocol.FormatRequest) => {
return this.requiredResponse(this.getFormattingEditsForRange(request.arguments));
},
@ -3068,4 +3103,18 @@ namespace ts.server {
return text;
}
function referenceEntryToReferencesResponseItem(projectService: ProjectService, { fileName, textSpan, contextSpan, isWriteAccess, isDefinition }: ReferenceEntry): protocol.ReferencesResponseItem {
const scriptInfo = Debug.checkDefined(projectService.getScriptInfo(fileName));
const span = toProtocolTextSpanWithContext(textSpan, contextSpan, scriptInfo);
const lineSpan = scriptInfo.lineToTextSpan(span.start.line - 1);
const lineText = scriptInfo.getSnapshot().getText(lineSpan.start, textSpanEnd(lineSpan)).replace(/\r|\n/g, "");
return {
file: fileName,
...span,
lineText,
isWriteAccess,
isDefinition
};
}
}

View File

@ -622,7 +622,21 @@ namespace ts.FindAllReferences {
// Could not find a symbol e.g. unknown identifier
if (!symbol) {
// String literal might be a property (and thus have a symbol), so do this here rather than in getReferencedSymbolsSpecial.
return !options.implementations && isStringLiteralLike(node) ? getReferencesForStringLiteral(node, sourceFiles, checker, cancellationToken) : undefined;
if (!options.implementations && isStringLiteralLike(node)) {
if (isRequireCall(node.parent, /*requireStringLiteralLikeArgument*/ true) || isExternalModuleReference(node.parent) || isImportDeclaration(node.parent) || isImportCall(node.parent)) {
const fileIncludeReasons = program.getFileIncludeReasons();
const referencedFileName = node.getSourceFile().resolvedModules?.get(node.text)?.resolvedFileName;
const referencedFile = referencedFileName ? program.getSourceFile(referencedFileName) : undefined;
if (referencedFile) {
return [{ definition: { type: DefinitionKind.String, node }, references: getReferencesForNonModule(referencedFile, fileIncludeReasons, program) || emptyArray }];
}
// 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).
}
return getReferencesForStringLiteral(node, sourceFiles, checker, cancellationToken);
}
return undefined;
}
if (symbol.escapedName === InternalSymbolName.ExportEquals) {
@ -642,6 +656,35 @@ namespace ts.FindAllReferences {
return mergeReferences(program, moduleReferences, references, moduleReferencesOfExportTarget);
}
export function getReferencesForFileName(fileName: string, program: Program, sourceFiles: readonly SourceFile[], sourceFilesSet: ReadonlySet<string> = new Set(sourceFiles.map(f => f.fileName))): readonly Entry[] {
const moduleSymbol = program.getSourceFile(fileName)?.symbol;
if (moduleSymbol) {
return getReferencedSymbolsForModule(program, moduleSymbol, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet)[0]?.references || emptyArray;
}
const fileIncludeReasons = program.getFileIncludeReasons();
const referencedFile = program.getSourceFile(fileName);
return referencedFile && fileIncludeReasons && getReferencesForNonModule(referencedFile, fileIncludeReasons, program) || emptyArray;
}
function getReferencesForNonModule(referencedFile: SourceFile, refFileMap: MultiMap<Path, FileIncludeReason>, program: Program): readonly SpanEntry[] | undefined {
let entries: SpanEntry[] | undefined;
const references = refFileMap.get(referencedFile.path) || emptyArray;
for (const ref of references) {
if (isReferencedFile(ref)) {
const referencingFile = program.getSourceFileByPath(ref.file)!;
const location = getReferencedFileLocation(program.getSourceFileByPath, ref);
if (isReferenceFileLocation(location)) {
entries = append(entries, {
kind: EntryKind.Span,
fileName: referencingFile.fileName,
textSpan: createTextSpanFromRange(location)
});
}
}
}
return entries;
}
function getMergedAliasedSymbolOfNamespaceExportDeclaration(node: Node, symbol: Symbol, checker: TypeChecker) {
if (node.parent && isNamespaceExportDeclaration(node.parent)) {
const aliasedSymbol = checker.getAliasedSymbol(symbol);

View File

@ -1728,6 +1728,11 @@ namespace ts {
return FindAllReferences.findReferencedSymbols(program, cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position);
}
function getFileReferences(fileName: string): ReferenceEntry[] {
synchronizeHostData();
return FindAllReferences.Core.getReferencesForFileName(fileName, program, program.getSourceFiles()).map(FindAllReferences.toReferenceEntry);
}
function getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles = false): NavigateToItem[] {
synchronizeHostData();
const sourceFiles = fileName ? [getValidSourceFile(fileName)] : program.getSourceFiles();
@ -2526,6 +2531,7 @@ namespace ts {
getTypeDefinitionAtPosition,
getReferencesAtPosition,
findReferences,
getFileReferences,
getOccurrencesAtPosition,
getDocumentHighlights,
getNameOrDottedNameSpan,

View File

@ -208,6 +208,12 @@ namespace ts {
*/
findReferences(fileName: string, position: number): string;
/**
* Returns a JSON-encoded value of the type:
* { fileName: string; textSpan: { start: number; length: number}; isWriteAccess: boolean, isDefinition?: boolean }[]
*/
getFileReferences(fileName: string): string;
/**
* @deprecated
* Returns a JSON-encoded value of the type:
@ -916,6 +922,13 @@ namespace ts {
);
}
public getFileReferences(fileName: string) {
return this.forwardJSONCall(
`getFileReferences('${fileName})`,
() => this.languageService.getFileReferences(fileName)
);
}
public getOccurrencesAtPosition(fileName: string, position: number): string {
return this.forwardJSONCall(
`getOccurrencesAtPosition('${fileName}', ${position})`,

View File

@ -468,6 +468,7 @@ namespace ts {
getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined;
findReferences(fileName: string, position: number): ReferencedSymbol[] | undefined;
getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): DocumentHighlights[] | undefined;
getFileReferences(fileName: string): ReferenceEntry[];
/** @deprecated */
getOccurrencesAtPosition(fileName: string, position: number): readonly ReferenceEntry[] | undefined;

View File

@ -171,6 +171,7 @@
"unittests/tsserver/getApplicableRefactors.ts",
"unittests/tsserver/getEditsForFileRename.ts",
"unittests/tsserver/getExportReferences.ts",
"unittests/tsserver/getFileReferences.ts",
"unittests/tsserver/importHelpers.ts",
"unittests/tsserver/importSuggestionsCache.ts",
"unittests/tsserver/inferredProjects.ts",

View File

@ -0,0 +1,58 @@
namespace ts.projectSystem {
describe("unittests:: tsserver:: getFileReferences", () => {
const importA = `import "./a";`;
const importCurlyFromA = `import {} from "./a";`;
const importAFromA = `import { a } from "/project/a";`;
const typeofImportA = `type T = typeof import("./a").a;`;
const aTs: File = {
path: "/project/a.ts",
content: "export const a = {};",
};
const bTs: File = {
path: "/project/b.ts",
content: importA,
};
const cTs: File = {
path: "/project/c.ts",
content: importCurlyFromA
};
const dTs: File = {
path: "/project/d.ts",
content: [importAFromA, typeofImportA].join("\n")
};
const tsconfig: File = {
path: "/project/tsconfig.json",
content: "{}",
};
function makeSampleSession() {
const host = createServerHost([aTs, bTs, cTs, dTs, tsconfig]);
const session = createSession(host);
openFilesForSession([aTs, bTs, cTs, dTs], session);
return session;
}
it("should get file references", () => {
const session = makeSampleSession();
const response = executeSessionRequest<protocol.FileReferencesRequest, protocol.FileReferencesResponse>(
session,
protocol.CommandTypes.FileReferences,
{ file: aTs.path },
);
const expectResponse: protocol.FileReferencesResponseBody = {
refs: [
makeReferenceItem({ file: bTs, text: "./a", lineText: importA, contextText: importA, isDefinition: false, isWriteAccess: false }),
makeReferenceItem({ file: cTs, text: "./a", lineText: importCurlyFromA, contextText: importCurlyFromA, isDefinition: false, isWriteAccess: false }),
makeReferenceItem({ file: dTs, text: "/project/a", lineText: importAFromA, contextText: importAFromA, isDefinition: false, isWriteAccess: false }),
makeReferenceItem({ file: dTs, text: "./a", lineText: typeofImportA, contextText: typeofImportA, isDefinition: false, isWriteAccess: false }),
],
symbolName: `"${aTs.path}"`,
};
assert.deepEqual(response, expectResponse);
});
});
}

View File

@ -207,6 +207,8 @@ namespace ts.server {
CommandNames.Implementation,
CommandNames.ImplementationFull,
CommandNames.Exit,
CommandNames.FileReferences,
CommandNames.FileReferencesFull,
CommandNames.Format,
CommandNames.Formatonkey,
CommandNames.FormatFull,

View File

@ -5531,6 +5531,7 @@ declare namespace ts {
getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined;
findReferences(fileName: string, position: number): ReferencedSymbol[] | undefined;
getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): DocumentHighlights[] | undefined;
getFileReferences(fileName: string): ReferenceEntry[];
/** @deprecated */
getOccurrencesAtPosition(fileName: string, position: number): readonly ReferenceEntry[] | undefined;
getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles?: boolean): NavigateToItem[];
@ -6536,6 +6537,7 @@ declare namespace ts.server.protocol {
DefinitionAndBoundSpan = "definitionAndBoundSpan",
Implementation = "implementation",
Exit = "exit",
FileReferences = "fileReferences",
Format = "format",
Formatonkey = "formatonkey",
Geterr = "geterr",
@ -7349,6 +7351,22 @@ declare namespace ts.server.protocol {
interface ReferencesResponse extends Response {
body?: ReferencesResponseBody;
}
interface FileReferencesRequest extends FileRequest {
command: CommandTypes.FileReferences;
}
interface FileReferencesResponseBody {
/**
* The file locations referencing the symbol.
*/
refs: readonly ReferencesResponseItem[];
/**
* The name of the symbol.
*/
symbolName: string;
}
interface FileReferencesResponse extends Response {
body?: FileReferencesResponseBody;
}
/**
* Argument for RenameRequest request.
*/
@ -9980,6 +9998,7 @@ declare namespace ts.server {
private mapRenameInfo;
private toSpanGroups;
private getReferences;
private getFileReferences;
/**
* @param fileName is the name of the file to be opened
* @param fileContent is a version of the file content that is known to be more up to date than the one on disk

View File

@ -5531,6 +5531,7 @@ declare namespace ts {
getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined;
findReferences(fileName: string, position: number): ReferencedSymbol[] | undefined;
getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): DocumentHighlights[] | undefined;
getFileReferences(fileName: string): ReferenceEntry[];
/** @deprecated */
getOccurrencesAtPosition(fileName: string, position: number): readonly ReferenceEntry[] | undefined;
getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles?: boolean): NavigateToItem[];

View File

@ -1,6 +1,6 @@
// === /b.js ===
// const { [|f|]/*FIND ALL REFS*/ } = require('./a')
// [|f|]
// const { [|f|] } = require('./a')
// /*FIND ALL REFS*/[|f|]
// === /a.js ===
// function [|f|]() { }

View File

@ -1,6 +1,6 @@
// === /b.js ===
// const { [|f|]/*FIND ALL REFS*/ } = require('./a')
// [|f|]
// const { [|f|] } = require('./a')
// /*FIND ALL REFS*/[|f|]
// === /a.js ===
// function [|f|]() { }

View File

@ -1,6 +1,6 @@
// === /b.js ===
// const { [|f|]/*FIND ALL REFS*/ } = require('./a')
// [|f|]
// const { [|f|] } = require('./a')
// /*FIND ALL REFS*/[|f|]
// === /a.js ===
// function [|f|]() { }

View File

@ -1,5 +1,5 @@
// === /app.ts ===
// /*FIND ALL REFS*/export function [|hello|]() {};
// export function [|he/*FIND ALL REFS*/llo|]() {};
// === /indirect-use.ts ===
// import("./re-export").then(mod => mod.services.app.[|hello|]());

View File

@ -1,5 +1,5 @@
// === /app.ts ===
// /*FIND ALL REFS*/export function [|hello|]() {};
// export function [|he/*FIND ALL REFS*/llo|]() {};
// === /indirect-use.ts ===
// import type { app } from "./re-export";

View File

@ -0,0 +1,186 @@
// === /import.ts ===
// import [|"./script/*FIND ALL REFS*/"|];
// === /require.js ===
// require([|"./script"|]);
// console.log("./script");
// === /tripleSlash.ts ===
// /// <reference path="[|script.ts|]" />
[
{
"definition": {
"containerKind": "",
"containerName": "",
"fileName": "/import.ts",
"kind": "var",
"name": "./script",
"textSpan": {
"start": 8,
"length": 8
},
"displayParts": [
{
"text": "\"./script\"",
"kind": "stringLiteral"
}
]
},
"references": [
{
"textSpan": {
"start": 7,
"length": 10
},
"fileName": "/import.ts",
"isWriteAccess": false,
"isDefinition": false
},
{
"textSpan": {
"start": 8,
"length": 10
},
"fileName": "/require.js",
"isWriteAccess": false,
"isDefinition": false
},
{
"textSpan": {
"start": 21,
"length": 9
},
"fileName": "/tripleSlash.ts",
"isWriteAccess": false,
"isDefinition": false
}
]
}
]
// === /import.ts ===
// import [|"./script"|];
// === /require.js ===
// require([|"./script/*FIND ALL REFS*/"|]);
// console.log("./script");
// === /tripleSlash.ts ===
// /// <reference path="[|script.ts|]" />
[
{
"definition": {
"containerKind": "",
"containerName": "",
"fileName": "/require.js",
"kind": "var",
"name": "./script",
"textSpan": {
"start": 9,
"length": 8
},
"displayParts": [
{
"text": "\"./script\"",
"kind": "stringLiteral"
}
]
},
"references": [
{
"textSpan": {
"start": 7,
"length": 10
},
"fileName": "/import.ts",
"isWriteAccess": false,
"isDefinition": false
},
{
"textSpan": {
"start": 8,
"length": 10
},
"fileName": "/require.js",
"isWriteAccess": false,
"isDefinition": false
},
{
"textSpan": {
"start": 21,
"length": 9
},
"fileName": "/tripleSlash.ts",
"isWriteAccess": false,
"isDefinition": false
}
]
}
]
// === /require.js ===
// require("[|./script|]");
// console.log("[|./script|]/*FIND ALL REFS*/");
// === /stringLiteral.ts ===
// console.log("[|./script|]");
[
{
"definition": {
"containerKind": "",
"containerName": "",
"fileName": "/require.js",
"kind": "var",
"name": "./script",
"textSpan": {
"start": 34,
"length": 8
},
"displayParts": [
{
"text": "\"./script\"",
"kind": "stringLiteral"
}
]
},
"references": [
{
"textSpan": {
"start": 9,
"length": 8
},
"fileName": "/require.js",
"contextSpan": {
"start": 0,
"length": 20
},
"isWriteAccess": false,
"isDefinition": false,
"isInString": true
},
{
"textSpan": {
"start": 34,
"length": 8
},
"fileName": "/require.js",
"isWriteAccess": false,
"isDefinition": false,
"isInString": true
},
{
"textSpan": {
"start": 13,
"length": 8
},
"fileName": "/stringLiteral.ts",
"isWriteAccess": false,
"isDefinition": false,
"isInString": true
}
]
}
]

View File

@ -5,7 +5,7 @@
// const y = 0;
// === /a.js ===
// /*FIND ALL REFS*/module.exports = class [|C|] {};
// module.exports = class /*FIND ALL REFS*/[|C|] {};
// module.exports.D = class D {};
[

View File

@ -1,6 +1,6 @@
// === /a.js ===
// /*FIND ALL REFS*/module.exports = class C {};
// module.exports.[|D|] = class D {};
// module.exports = class C {};
// module.exports./*FIND ALL REFS*/[|D|] = class D {};
// === /b.js ===
// /** @type {import("./a")} */

View File

@ -1,6 +1,6 @@
// === /a.js ===
// /*FIND ALL REFS*/module.exports = class C {};
// module.exports.D = class [|D|] {};
// module.exports = class C {};
// module.exports.D = class /*FIND ALL REFS*/[|D|] {};
// === /b.js ===
// /** @type {import("./a")} */

View File

@ -0,0 +1,64 @@
// === /project/b.ts ===
// import "[|./a|]";
// === /project/c.ts ===
// import {} from "[|./a|]";
// === /project/d.ts ===
// import { a } from "[|/project/a|]";
// type T = typeof import("[|./a|]").a;
[
{
"textSpan": {
"start": 8,
"length": 3
},
"fileName": "/project/b.ts",
"contextSpan": {
"start": 0,
"length": 13
},
"isWriteAccess": false,
"isDefinition": false
},
{
"textSpan": {
"start": 16,
"length": 3
},
"fileName": "/project/c.ts",
"contextSpan": {
"start": 0,
"length": 21
},
"isWriteAccess": false,
"isDefinition": false
},
{
"textSpan": {
"start": 19,
"length": 10
},
"fileName": "/project/d.ts",
"contextSpan": {
"start": 0,
"length": 31
},
"isWriteAccess": false,
"isDefinition": false
},
{
"textSpan": {
"start": 56,
"length": 3
},
"fileName": "/project/d.ts",
"contextSpan": {
"start": 32,
"length": 32
},
"isWriteAccess": false,
"isDefinition": false
}
]

View File

@ -0,0 +1,14 @@
// === /project/b.ts ===
// import [|"./a"|];
[
{
"textSpan": {
"start": 7,
"length": 5
},
"fileName": "/project/b.ts",
"isWriteAccess": false,
"isDefinition": false
}
]

View File

@ -0,0 +1,26 @@
// === /test.ts ===
// import "[|./util|]";
// === /index.ts ===
// export * from "[|./util|]";
[
{
"fileName": "/index.ts",
"textSpan": {
"start": 15,
"length": 6
},
"isWriteAccess": false,
"isDefinition": false
},
{
"fileName": "/test.ts",
"textSpan": {
"start": 8,
"length": 6
},
"isWriteAccess": false,
"isDefinition": false
}
]

View File

@ -0,0 +1,48 @@
// === /project/b.ts ===
// import "[|./a|]";
// === /project/c.ts ===
// import {} from "[|./a|]";
// === /project/d.ts ===
// import { a } from "[|/project/a|]";
// type T = typeof import("[|./a|]").a;
[
{
"fileName": "/project/b.ts",
"textSpan": {
"start": 8,
"length": 3
},
"isWriteAccess": false,
"isDefinition": false
},
{
"fileName": "/project/c.ts",
"textSpan": {
"start": 16,
"length": 3
},
"isWriteAccess": false,
"isDefinition": false
},
{
"fileName": "/project/d.ts",
"textSpan": {
"start": 19,
"length": 10
},
"isWriteAccess": false,
"isDefinition": false
},
{
"fileName": "/project/d.ts",
"textSpan": {
"start": 56,
"length": 3
},
"isWriteAccess": false,
"isDefinition": false
}
]

View File

@ -0,0 +1,38 @@
// === /packages/client/index.ts ===
// import "[|@shared/referenced|]";
// === /packages/server/index.js ===
// const mod = require("[|../shared/src/referenced|]");
// === /packages/server/router.js ===
// const blah = require("[|../shared/dist/referenced|]");
[
{
"fileName": "/packages/server/index.js",
"textSpan": {
"start": 21,
"length": 24
},
"isWriteAccess": false,
"isDefinition": false
},
{
"fileName": "/packages/server/router.js",
"textSpan": {
"start": 22,
"length": 25
},
"isWriteAccess": false,
"isDefinition": false
},
{
"fileName": "/packages/client/index.ts",
"textSpan": {
"start": 8,
"length": 18
},
"isWriteAccess": false,
"isDefinition": false
}
]

View File

@ -0,0 +1,21 @@
/// <reference path="fourslash.ts" />
// @checkJs: true
// @Filename: /script.ts
//// console.log("I'm a script!");
// @Filename: /import.ts
//// import "./script/*1*/";
// @Filename: /require.js
//// require("./script/*2*/");
//// console.log("./script/*3*/");
// @Filename: /tripleSlash.ts
//// /// <reference path="script.ts" />
// @Filename: /stringLiteral.ts
//// console.log("./script");
verify.baselineFindAllReferences("1", "2", "3");

View File

@ -289,7 +289,8 @@ declare namespace FourSlashInterface {
goToType(startMarkerNames: ArrayOrSingle<string>, endMarkerNames: ArrayOrSingle<string>): void;
verifyGetEmitOutputForCurrentFile(expected: string): void;
verifyGetEmitOutputContentsForCurrentFile(expected: ts.OutputFile[]): void;
baselineFindAllReferences(markerName: string): void;
baselineFindAllReferences(...markerNames: string[]): void;
baselineGetFileReferences(fileName: string): void;
noReferences(markerNameOrRange?: string | Range): void;
symbolAtLocation(startRange: Range, ...declarationRanges: Range[]): void;
typeOfSymbolAtLocation(range: Range, symbol: any, expected: string): void;

View File

@ -0,0 +1,16 @@
/// <reference path="fourslash.ts" />
// @Filename: /project/a.ts
//// export const a = 0;
// @Filename: /project/b.ts
//// import "./a";
// @Filename: /project/c.ts
//// import {} from "./a";
// @Filename: /project/d.ts
//// import { a } from "/project/a";
//// type T = typeof import("./a").a;
verify.baselineGetFileReferences("/project/a.ts");

View File

@ -0,0 +1,9 @@
/// <reference path="fourslash.ts" />
// @Filename: /project/a.ts
//// console.log("I'm a script!");
// @Filename: /project/b.ts
//// import "./a";
verify.baselineGetFileReferences("/project/a.ts");

View File

@ -0,0 +1,23 @@
/// <reference path="../fourslash.ts" />
// @Filename: /tsconfig.json
//// { "files": [], "references": [{ "path": "tsconfig.build.json" }, { "path": "tsconfig.test.json" }] }
// @Filename: /tsconfig.build.json
//// { "compilerOptions": { "rootDir": "src", "outDir": "dist/build", "composite": true }, "files": ["index.ts"] }
// @Filename: /index.ts
//// export * from "./util";
// @Filename: /tsconfig.test.json
//// { "compilerOptions": { "rootDir": "src", "outDir": "dist/test", "composite": true }, "files": ["test.ts", "index.ts"] }
// @Filename: /test.ts
//// import "./util";
// @Filename: /util.ts
//// export {}
// util.ts is referenced by index.ts, which is included in tsconfig.build.json and tsconfig.test.json.
// That reference will be returned from both projects' language services. Test ensures it gets deduplicated.
verify.baselineGetFileReferences("/util.ts");

View File

@ -0,0 +1,19 @@
/// <reference path="../fourslash.ts" />
// @Filename: /project/tsconfig.json
//// {}
// @Filename: /project/a.ts
//// export const a = 0;
// @Filename: /project/b.ts
//// import "./a";
// @Filename: /project/c.ts
//// import {} from "./a";
// @Filename: /project/d.ts
//// import { a } from "/project/a";
//// type T = typeof import("./a").a;
verify.baselineGetFileReferences("/project/a.ts");

View File

@ -0,0 +1,27 @@
/// <reference path="../fourslash.ts" />
// @Filename: /tsconfig.json
//// { "files": [], "references": [{ "path": "packages/server" }, { "path": "packages/client" }] }
// @Filename: /packages/shared/tsconfig.json
//// { "compilerOptions": { "rootDir": "src", "outDir": "dist", "composite": true } }
// @Filename: /packages/shared/src/referenced.ts
//// export {};
// @Filename: /packages/server/tsconfig.json
//// { "compilerOptions": { "checkJs": true }, "references": [{ "path": "../shared" }] }
// @Filename: /packages/server/index.js
//// const mod = require("../shared/src/referenced");
// @Filename: /packages/server/router.js
//// const blah = require("../shared/dist/referenced");
// @Filename: /packages/client/tsconfig.json
//// { "compilerOptions": { "paths": { "@shared/*": ["../shared/src/*"] } }, "references": [{ "path": "../shared" }] }
// @Filename: /packages/client/index.ts
//// import "@shared/referenced";
verify.baselineGetFileReferences("/packages/shared/src/referenced.ts");