Add test for goto-definition with project references (#24867)

* Add test for goto-definition with project references

* Assert that the declaration file is what we emit
This commit is contained in:
Andy
2018-06-27 15:56:04 -07:00
committed by GitHub
parent 4d43a3a3c0
commit 16f045b7f5
3 changed files with 134 additions and 31 deletions

View File

@@ -2439,7 +2439,7 @@ namespace ts.server.protocol {
/**
* An item found in a navto response.
*/
export interface NavtoItem {
export interface NavtoItem extends FileSpan {
/**
* The symbol's name.
*/
@@ -2465,21 +2465,6 @@ namespace ts.server.protocol {
*/
kindModifiers?: string;
/**
* The file in which the symbol is found.
*/
file: string;
/**
* The location within file at which the symbol is found.
*/
start: Location;
/**
* One past the last character of the symbol.
*/
end: Location;
/**
* Name of symbol's container symbol (if any); for example,
* the class name if symbol is a class member.

View File

@@ -415,22 +415,33 @@ namespace ts.projectSystem {
checkArray("Open files", arrayFrom(projectService.openFiles.keys(), path => projectService.getScriptInfoForPath(path as Path)!.fileName), expectedFiles.map(file => file.path));
}
function protocolLocationFromSubstring(str: string, substring: string) {
const start = str.indexOf(substring);
Debug.assert(start !== -1);
return protocolToLocation(str)(start);
}
function protocolToLocation(text: string): (pos: number) => protocol.Location {
const lineStarts = computeLineStarts(text);
return pos => {
const x = computeLineAndCharacterOfPosition(lineStarts, pos);
return { line: x.line + 1, offset: x.character + 1 };
};
}
function protocolTextSpanFromSubstring(str: string, substring: string): protocol.TextSpan {
const span = textSpanFromSubstring(str, substring);
const toLocation = protocolToLocation(str);
return { start: toLocation(span.start), end: toLocation(span.start + span.length) };
}
function textSpanFromSubstring(str: string, substring: string): TextSpan {
const start = str.indexOf(substring);
Debug.assert(start !== -1);
return createTextSpan(start, substring.length);
}
function protocolTextSpanFromSubstring(str: string, substring: string): protocol.TextSpan {
const start = str.indexOf(substring);
Debug.assert(start !== -1);
const lineStarts = computeLineStarts(str);
const toLocation = (pos: number) => lineAndCharacterToLocation(computeLineAndCharacterOfPosition(lineStarts, pos));
return { start: toLocation(start), end: toLocation(start + substring.length) };
function protocolFileLocationFromSubstring(file: File, substring: string): protocol.FileLocationRequestArgs {
return { file: file.path, ...protocolLocationFromSubstring(file.content, substring) };
}
function lineAndCharacterToLocation(lc: LineAndCharacter): protocol.Location {
return { line: lc.line + 1, offset: lc.character + 1 };
function protocolFileSpanFromSubstring(file: File, substring: string): protocol.FileSpan {
return { file: file.path, ...protocolTextSpanFromSubstring(file.content, substring) };
}
/**
@@ -485,13 +496,19 @@ namespace ts.projectSystem {
};
}
export function openFilesForSession(files: ReadonlyArray<File>, session: server.Session) {
export function openFilesForSession(files: ReadonlyArray<File>, session: server.Session): void {
for (const file of files) {
const request = makeSessionRequest<protocol.OpenRequestArgs>(CommandNames.Open, { file: file.path });
session.executeCommand(request);
}
}
export function closeFilesForSession(files: ReadonlyArray<File>, session: server.Session): void {
for (const file of files) {
session.executeCommand(makeSessionRequest<protocol.FileRequestArgs>(CommandNames.Close, { file: file.path }));
}
}
interface ErrorInformation {
diagnosticMessage: DiagnosticMessage;
errorTextArguments?: string[];
@@ -8830,4 +8847,108 @@ export const x = 10;`
assert.equal(moduleInfo.cacheSourceFile.sourceFile.text, updatedModuleContent);
});
});
function makeSampleProjects() {
const aTs: File = {
path: "/a/a.ts",
content: "export function fnA() {}",
};
const aTsconfig: File = {
path: "/a/tsconfig.json",
content: `{
"compilerOptions": {
"outDir": "bin",
"declaration": true,
"declarationMap": true,
"composite": true,
}
}`,
};
const bTs: File = {
path: "/b/b.ts",
content: 'import { fnA } from "../a/a";\nexport function fnB() { fnA(); }',
};
const bTsconfig: File = {
path: "/b/tsconfig.json",
content: `{
"compilerOptions": {
"outDir": "bin",
},
"references": [
{ "path": "../a" }
]
}`,
};
const host = createServerHost([aTs, aTsconfig, bTs, bTsconfig]);
const session = createSession(host);
writeDeclarationFiles(aTs, host, session, [
{ name: "/a/bin/a.d.ts.map", text: '{"version":3,"file":"a.d.ts","sourceRoot":"","sources":["../a.ts"],"names":[],"mappings":"AAAA,wBAAgB,GAAG,SAAK"}' },
// Need to mangle the sourceMappingURL part or it breaks the build
{ name: "/a/bin/a.d.ts", text: `export declare function fnA(): void;\n//# source${""}MappingURL=a.d.ts.map` },
]);
return { session, aTs, bTs };
}
describe("tsserverProjectSystem project references", () => {
it("goToDefinition", () => {
const { session, aTs, bTs } = makeSampleProjects();
openFilesForSession([bTs], session);
const definitionRequest = makeSessionRequest<protocol.FileLocationRequestArgs>(CommandNames.Definition, protocolFileLocationFromSubstring(bTs, "fnA()"));
const definitionResponse = session.executeCommand(definitionRequest).response as protocol.DefinitionResponse["body"];
assert.deepEqual(definitionResponse, [protocolFileSpanFromSubstring(aTs, "fnA")]);
});
it("navigateTo", () => {
const { session, bTs } = makeSampleProjects();
openFilesForSession([bTs], session);
const navtoRequest = makeSessionRequest<protocol.NavtoRequestArgs>(CommandNames.Navto, { file: bTs.path, searchValue: "fn" });
const navtoResponse = session.executeCommand(navtoRequest).response as protocol.NavtoResponse["body"];
assert.deepEqual(navtoResponse, [
// TODO: First result should be from a.ts, not a.d.ts
{
file: "/a/bin/a.d.ts",
start: { line: 1, offset: 1 },
end: { line: 1, offset: 37 },
name: "fnA",
matchKind: "prefix",
kind: ScriptElementKind.functionElement,
kindModifiers: "export,declare",
},
{
...protocolFileSpanFromSubstring(bTs, "export function fnB() { fnA(); }"),
name: "fnB",
matchKind: "prefix",
kind: ScriptElementKind.functionElement,
kindModifiers: "export",
}
]);
});
});
function writeDeclarationFiles(file: File, host: TestServerHost, session: TestSession, expectedFiles: ReadonlyArray<{ readonly name: string, readonly text: string }>): void {
openFilesForSession([file], session);
const project = Debug.assertDefined(session.getProjectService().getDefaultProjectForFile(file.path as server.NormalizedPath, /*ensureProject*/ false));
const program = project.getCurrentProgram();
const output = getFileEmitOutput(program, Debug.assertDefined(program.getSourceFile(file.path)), /*emitOnlyDtsFiles*/ true);
closeFilesForSession([file], session);
Debug.assert(!output.emitSkipped);
assert.deepEqual(output.outputFiles, expectedFiles.map(e => ({ ...e, writeByteOrderMark: false })));
for (const { name, text } of output.outputFiles) {
const directory: Folder = { path: getDirectoryPath(name) };
host.ensureFileOrFolder(directory);
host.writeFile(name, text);
}
}
}