Skip to content
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
17 changes: 1 addition & 16 deletions src/server/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -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.
Expand Down
141 changes: 131 additions & 10 deletions src/testRunner/unittests/tsserverProjectSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 textSpanFromSubstring(str: string, substring: string): TextSpan {
function protocolLocationFromSubstring(str: string, substring: string) {
const start = str.indexOf(substring);
Debug.assert(start !== -1);
return createTextSpan(start, substring.length);
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);
const lineStarts = computeLineStarts(str);
const toLocation = (pos: number) => lineAndCharacterToLocation(computeLineAndCharacterOfPosition(lineStarts, pos));
return { start: toLocation(start), end: toLocation(start + substring.length) };
return createTextSpan(start, substring.length);
}

function lineAndCharacterToLocation(lc: LineAndCharacter): protocol.Location {
return { line: lc.line + 1, offset: lc.character + 1 };
function protocolFileLocationFromSubstring(file: File, substring: string): protocol.FileLocationRequestArgs {
return { file: file.path, ...protocolLocationFromSubstring(file.content, substring) };
}
function protocolFileSpanFromSubstring(file: File, substring: string): protocol.FileSpan {
return { file: file.path, ...protocolTextSpanFromSubstring(file.content, substring) };
}

/**
Expand Down Expand Up @@ -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[];
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a reminder because navTo doesn't apply sourcemaps yet, right?

Copy link
Author

@ghost ghost Jun 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, working on it. #25283

{
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);
}
}
}
5 changes: 1 addition & 4 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13109,15 +13109,12 @@ declare namespace ts.server.protocol {
command: CommandTypes.Navto;
arguments: NavtoRequestArgs;
}
interface NavtoItem {
interface NavtoItem extends FileSpan {
name: string;
kind: ScriptElementKind;
matchKind?: string;
isCaseSensitive?: boolean;
kindModifiers?: string;
file: string;
start: Location;
end: Location;
containerName?: string;
containerKind?: ScriptElementKind;
}
Expand Down