Skip to content

Commit 16f045b

Browse files
author
Andy
authored
Add test for goto-definition with project references (microsoft#24867)
* Add test for goto-definition with project references * Assert that the declaration file is what we emit
1 parent 4d43a3a commit 16f045b

3 files changed

Lines changed: 133 additions & 30 deletions

File tree

src/server/protocol.ts

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2439,7 +2439,7 @@ namespace ts.server.protocol {
24392439
/**
24402440
* An item found in a navto response.
24412441
*/
2442-
export interface NavtoItem {
2442+
export interface NavtoItem extends FileSpan {
24432443
/**
24442444
* The symbol's name.
24452445
*/
@@ -2465,21 +2465,6 @@ namespace ts.server.protocol {
24652465
*/
24662466
kindModifiers?: string;
24672467

2468-
/**
2469-
* The file in which the symbol is found.
2470-
*/
2471-
file: string;
2472-
2473-
/**
2474-
* The location within file at which the symbol is found.
2475-
*/
2476-
start: Location;
2477-
2478-
/**
2479-
* One past the last character of the symbol.
2480-
*/
2481-
end: Location;
2482-
24832468
/**
24842469
* Name of symbol's container symbol (if any); for example,
24852470
* the class name if symbol is a class member.

src/testRunner/unittests/tsserverProjectSystem.ts

Lines changed: 131 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -415,22 +415,33 @@ namespace ts.projectSystem {
415415
checkArray("Open files", arrayFrom(projectService.openFiles.keys(), path => projectService.getScriptInfoForPath(path as Path)!.fileName), expectedFiles.map(file => file.path));
416416
}
417417

418-
function textSpanFromSubstring(str: string, substring: string): TextSpan {
418+
function protocolLocationFromSubstring(str: string, substring: string) {
419419
const start = str.indexOf(substring);
420420
Debug.assert(start !== -1);
421-
return createTextSpan(start, substring.length);
421+
return protocolToLocation(str)(start);
422+
}
423+
function protocolToLocation(text: string): (pos: number) => protocol.Location {
424+
const lineStarts = computeLineStarts(text);
425+
return pos => {
426+
const x = computeLineAndCharacterOfPosition(lineStarts, pos);
427+
return { line: x.line + 1, offset: x.character + 1 };
428+
};
422429
}
423-
424430
function protocolTextSpanFromSubstring(str: string, substring: string): protocol.TextSpan {
431+
const span = textSpanFromSubstring(str, substring);
432+
const toLocation = protocolToLocation(str);
433+
return { start: toLocation(span.start), end: toLocation(span.start + span.length) };
434+
}
435+
function textSpanFromSubstring(str: string, substring: string): TextSpan {
425436
const start = str.indexOf(substring);
426437
Debug.assert(start !== -1);
427-
const lineStarts = computeLineStarts(str);
428-
const toLocation = (pos: number) => lineAndCharacterToLocation(computeLineAndCharacterOfPosition(lineStarts, pos));
429-
return { start: toLocation(start), end: toLocation(start + substring.length) };
438+
return createTextSpan(start, substring.length);
430439
}
431-
432-
function lineAndCharacterToLocation(lc: LineAndCharacter): protocol.Location {
433-
return { line: lc.line + 1, offset: lc.character + 1 };
440+
function protocolFileLocationFromSubstring(file: File, substring: string): protocol.FileLocationRequestArgs {
441+
return { file: file.path, ...protocolLocationFromSubstring(file.content, substring) };
442+
}
443+
function protocolFileSpanFromSubstring(file: File, substring: string): protocol.FileSpan {
444+
return { file: file.path, ...protocolTextSpanFromSubstring(file.content, substring) };
434445
}
435446

436447
/**
@@ -485,13 +496,19 @@ namespace ts.projectSystem {
485496
};
486497
}
487498

488-
export function openFilesForSession(files: ReadonlyArray<File>, session: server.Session) {
499+
export function openFilesForSession(files: ReadonlyArray<File>, session: server.Session): void {
489500
for (const file of files) {
490501
const request = makeSessionRequest<protocol.OpenRequestArgs>(CommandNames.Open, { file: file.path });
491502
session.executeCommand(request);
492503
}
493504
}
494505

506+
export function closeFilesForSession(files: ReadonlyArray<File>, session: server.Session): void {
507+
for (const file of files) {
508+
session.executeCommand(makeSessionRequest<protocol.FileRequestArgs>(CommandNames.Close, { file: file.path }));
509+
}
510+
}
511+
495512
interface ErrorInformation {
496513
diagnosticMessage: DiagnosticMessage;
497514
errorTextArguments?: string[];
@@ -8830,4 +8847,108 @@ export const x = 10;`
88308847
assert.equal(moduleInfo.cacheSourceFile.sourceFile.text, updatedModuleContent);
88318848
});
88328849
});
8850+
8851+
function makeSampleProjects() {
8852+
const aTs: File = {
8853+
path: "/a/a.ts",
8854+
content: "export function fnA() {}",
8855+
};
8856+
const aTsconfig: File = {
8857+
path: "/a/tsconfig.json",
8858+
content: `{
8859+
"compilerOptions": {
8860+
"outDir": "bin",
8861+
"declaration": true,
8862+
"declarationMap": true,
8863+
"composite": true,
8864+
}
8865+
}`,
8866+
};
8867+
8868+
const bTs: File = {
8869+
path: "/b/b.ts",
8870+
content: 'import { fnA } from "../a/a";\nexport function fnB() { fnA(); }',
8871+
};
8872+
const bTsconfig: File = {
8873+
path: "/b/tsconfig.json",
8874+
content: `{
8875+
"compilerOptions": {
8876+
"outDir": "bin",
8877+
},
8878+
"references": [
8879+
{ "path": "../a" }
8880+
]
8881+
}`,
8882+
};
8883+
8884+
const host = createServerHost([aTs, aTsconfig, bTs, bTsconfig]);
8885+
const session = createSession(host);
8886+
8887+
writeDeclarationFiles(aTs, host, session, [
8888+
{ name: "/a/bin/a.d.ts.map", text: '{"version":3,"file":"a.d.ts","sourceRoot":"","sources":["../a.ts"],"names":[],"mappings":"AAAA,wBAAgB,GAAG,SAAK"}' },
8889+
// Need to mangle the sourceMappingURL part or it breaks the build
8890+
{ name: "/a/bin/a.d.ts", text: `export declare function fnA(): void;\n//# source${""}MappingURL=a.d.ts.map` },
8891+
]);
8892+
8893+
return { session, aTs, bTs };
8894+
}
8895+
8896+
describe("tsserverProjectSystem project references", () => {
8897+
it("goToDefinition", () => {
8898+
const { session, aTs, bTs } = makeSampleProjects();
8899+
8900+
openFilesForSession([bTs], session);
8901+
8902+
const definitionRequest = makeSessionRequest<protocol.FileLocationRequestArgs>(CommandNames.Definition, protocolFileLocationFromSubstring(bTs, "fnA()"));
8903+
const definitionResponse = session.executeCommand(definitionRequest).response as protocol.DefinitionResponse["body"];
8904+
8905+
assert.deepEqual(definitionResponse, [protocolFileSpanFromSubstring(aTs, "fnA")]);
8906+
});
8907+
8908+
it("navigateTo", () => {
8909+
const { session, bTs } = makeSampleProjects();
8910+
8911+
openFilesForSession([bTs], session);
8912+
8913+
const navtoRequest = makeSessionRequest<protocol.NavtoRequestArgs>(CommandNames.Navto, { file: bTs.path, searchValue: "fn" });
8914+
const navtoResponse = session.executeCommand(navtoRequest).response as protocol.NavtoResponse["body"];
8915+
8916+
assert.deepEqual(navtoResponse, [
8917+
// TODO: First result should be from a.ts, not a.d.ts
8918+
{
8919+
file: "/a/bin/a.d.ts",
8920+
start: { line: 1, offset: 1 },
8921+
end: { line: 1, offset: 37 },
8922+
name: "fnA",
8923+
matchKind: "prefix",
8924+
kind: ScriptElementKind.functionElement,
8925+
kindModifiers: "export,declare",
8926+
},
8927+
{
8928+
...protocolFileSpanFromSubstring(bTs, "export function fnB() { fnA(); }"),
8929+
name: "fnB",
8930+
matchKind: "prefix",
8931+
kind: ScriptElementKind.functionElement,
8932+
kindModifiers: "export",
8933+
}
8934+
]);
8935+
});
8936+
});
8937+
8938+
function writeDeclarationFiles(file: File, host: TestServerHost, session: TestSession, expectedFiles: ReadonlyArray<{ readonly name: string, readonly text: string }>): void {
8939+
openFilesForSession([file], session);
8940+
const project = Debug.assertDefined(session.getProjectService().getDefaultProjectForFile(file.path as server.NormalizedPath, /*ensureProject*/ false));
8941+
const program = project.getCurrentProgram();
8942+
const output = getFileEmitOutput(program, Debug.assertDefined(program.getSourceFile(file.path)), /*emitOnlyDtsFiles*/ true);
8943+
closeFilesForSession([file], session);
8944+
8945+
Debug.assert(!output.emitSkipped);
8946+
assert.deepEqual(output.outputFiles, expectedFiles.map(e => ({ ...e, writeByteOrderMark: false })));
8947+
8948+
for (const { name, text } of output.outputFiles) {
8949+
const directory: Folder = { path: getDirectoryPath(name) };
8950+
host.ensureFileOrFolder(directory);
8951+
host.writeFile(name, text);
8952+
}
8953+
}
88338954
}

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13119,15 +13119,12 @@ declare namespace ts.server.protocol {
1311913119
command: CommandTypes.Navto;
1312013120
arguments: NavtoRequestArgs;
1312113121
}
13122-
interface NavtoItem {
13122+
interface NavtoItem extends FileSpan {
1312313123
name: string;
1312413124
kind: ScriptElementKind;
1312513125
matchKind?: string;
1312613126
isCaseSensitive?: boolean;
1312713127
kindModifiers?: string;
13128-
file: string;
13129-
start: Location;
13130-
end: Location;
1313113128
containerName?: string;
1313213129
containerKind?: ScriptElementKind;
1313313130
}

0 commit comments

Comments
 (0)