Skip to content

Commit d571a09

Browse files
authored
Navto covers all projects (#38027)
* Remove needless structure/destructuring Just pass multiple arguments! Sheesh! * Basic working prototype * Cleaned up version 1. Add test 2. Change protocol. navto-all only happens when filename is undefined or missing. 3. Change location to earlier code path. This change was largely type-guided and resulted in some duplicated code, but I think it's less fault-prone. * remove temp notes * Single-project hits if projectFileName is provided and file is not * use original code as fallback
1 parent 892427a commit d571a09

File tree

4 files changed

+116
-35
lines changed

4 files changed

+116
-35
lines changed

Diff for: src/server/protocol.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -2784,7 +2784,7 @@ namespace ts.server.protocol {
27842784
/**
27852785
* Arguments for navto request message.
27862786
*/
2787-
export interface NavtoRequestArgs extends FileRequestArgs {
2787+
export interface NavtoRequestArgs {
27882788
/**
27892789
* Search term to navigate to from current location; term can
27902790
* be '.*' or an identifier prefix.
@@ -2794,6 +2794,10 @@ namespace ts.server.protocol {
27942794
* Optional limit on the number of items to return.
27952795
*/
27962796
maxResultCount?: number;
2797+
/**
2798+
* The file for the request (absolute pathname required).
2799+
*/
2800+
file?: string;
27972801
/**
27982802
* Optional flag to indicate we want results for just the current file
27992803
* or the entire project.
@@ -2809,7 +2813,7 @@ namespace ts.server.protocol {
28092813
* match the search term given in argument 'searchTerm'. The
28102814
* context for the search is given by the named file.
28112815
*/
2812-
export interface NavtoRequest extends FileRequest {
2816+
export interface NavtoRequest extends Request {
28132817
command: CommandTypes.Navto;
28142818
arguments: NavtoRequestArgs;
28152819
}

Diff for: src/server/session.ts

+40-27
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ namespace ts.server {
304304
projects,
305305
defaultProject,
306306
/*initialLocation*/ undefined,
307-
({ project }, tryAddToTodo) => {
307+
(project, _, tryAddToTodo) => {
308308
for (const output of action(project)) {
309309
if (!contains(outputs, output, resultsEqual) && !tryAddToTodo(project, getLocation(output))) {
310310
outputs.push(output);
@@ -329,7 +329,7 @@ namespace ts.server {
329329
projects,
330330
defaultProject,
331331
initialLocation,
332-
({ project, location }, tryAddToTodo) => {
332+
(project, location, tryAddToTodo) => {
333333
for (const output of project.getLanguageService().findRenameLocations(location.fileName, location.pos, findInStrings, findInComments, hostPreferences.providePrefixAndSuffixTextForRename) || emptyArray) {
334334
if (!contains(outputs, output, documentSpansEqual) && !tryAddToTodo(project, documentSpanLocation(output))) {
335335
outputs.push(output);
@@ -358,7 +358,7 @@ namespace ts.server {
358358
projects,
359359
defaultProject,
360360
initialLocation,
361-
({ project, location }, getMappedLocation) => {
361+
(project, location, getMappedLocation) => {
362362
for (const outputReferencedSymbol of project.getLanguageService().findReferences(location.fileName, location.pos) || emptyArray) {
363363
const mappedDefinitionFile = getMappedLocation(project, documentSpanLocation(outputReferencedSymbol.definition));
364364
const definition: ReferencedSymbolDefinitionInfo = mappedDefinitionFile === undefined ?
@@ -408,7 +408,8 @@ namespace ts.server {
408408
}
409409

410410
type CombineProjectOutputCallback<TLocation extends DocumentPosition | undefined> = (
411-
where: ProjectAndLocation<TLocation>,
411+
project: Project,
412+
location: TLocation,
412413
getMappedLocation: (project: Project, location: DocumentPosition) => DocumentPosition | undefined,
413414
) => void;
414415

@@ -422,9 +423,9 @@ namespace ts.server {
422423
let toDo: ProjectAndLocation<TLocation>[] | undefined;
423424
const seenProjects = createMap<true>();
424425
forEachProjectInProjects(projects, initialLocation && initialLocation.fileName, (project, path) => {
425-
// TLocation shoud be either `DocumentPosition` or `undefined`. Since `initialLocation` is `TLocation` this cast should be valid.
426+
// TLocation should be either `DocumentPosition` or `undefined`. Since `initialLocation` is `TLocation` this cast should be valid.
426427
const location = (initialLocation ? { fileName: path, pos: initialLocation.pos } : undefined) as TLocation;
427-
toDo = callbackProjectAndLocation({ project, location }, projectService, toDo, seenProjects, cb);
428+
toDo = callbackProjectAndLocation(project, location, projectService, toDo, seenProjects, cb);
428429
});
429430

430431
// After initial references are collected, go over every other project and see if it has a reference for the symbol definition.
@@ -442,14 +443,16 @@ namespace ts.server {
442443
if (!addToSeen(seenProjects, project)) return;
443444
const definition = mapDefinitionInProject(defaultDefinition, project, getGeneratedDefinition, getSourceDefinition);
444445
if (definition) {
445-
toDo = callbackProjectAndLocation<TLocation>({ project, location: definition as TLocation }, projectService, toDo, seenProjects, cb);
446+
toDo = callbackProjectAndLocation<TLocation>(project, definition as TLocation, projectService, toDo, seenProjects, cb);
446447
}
447448
});
448449
}
449450
}
450451

451452
while (toDo && toDo.length) {
452-
toDo = callbackProjectAndLocation(Debug.checkDefined(toDo.pop()), projectService, toDo, seenProjects, cb);
453+
const next = toDo.pop();
454+
Debug.assertIsDefined(next);
455+
toDo = callbackProjectAndLocation(next.project, next.location, projectService, toDo, seenProjects, cb);
453456
}
454457
}
455458

@@ -487,40 +490,42 @@ namespace ts.server {
487490
}
488491

489492
function callbackProjectAndLocation<TLocation extends DocumentPosition | undefined>(
490-
projectAndLocation: ProjectAndLocation<TLocation>,
493+
project: Project,
494+
location: TLocation,
491495
projectService: ProjectService,
492496
toDo: ProjectAndLocation<TLocation>[] | undefined,
493497
seenProjects: Map<true>,
494498
cb: CombineProjectOutputCallback<TLocation>,
495499
): ProjectAndLocation<TLocation>[] | undefined {
496-
const { project, location } = projectAndLocation;
497500
if (project.getCancellationToken().isCancellationRequested()) return undefined; // Skip rest of toDo if cancelled
498501
// If this is not the file we were actually looking, return rest of the toDo
499502
if (isLocationProjectReferenceRedirect(project, location)) return toDo;
500-
cb(projectAndLocation, (project, location) => {
501-
addToSeen(seenProjects, projectAndLocation.project);
502-
const originalLocation = projectService.getOriginalLocationEnsuringConfiguredProject(project, location);
503+
cb(project, location, (innerProject, location) => {
504+
addToSeen(seenProjects, project);
505+
const originalLocation = projectService.getOriginalLocationEnsuringConfiguredProject(innerProject, location);
503506
if (!originalLocation) return undefined;
504507

505508
const originalScriptInfo = projectService.getScriptInfo(originalLocation.fileName)!;
506509
toDo = toDo || [];
507510

508511
for (const project of originalScriptInfo.containingProjects) {
509-
addToTodo({ project, location: originalLocation as TLocation }, toDo, seenProjects);
512+
addToTodo(project, originalLocation as TLocation, toDo, seenProjects);
510513
}
511514
const symlinkedProjectsMap = projectService.getSymlinkedProjects(originalScriptInfo);
512515
if (symlinkedProjectsMap) {
513516
symlinkedProjectsMap.forEach((symlinkedProjects, symlinkedPath) => {
514-
for (const symlinkedProject of symlinkedProjects) addToTodo({ project: symlinkedProject, location: { fileName: symlinkedPath, pos: originalLocation.pos } as TLocation }, toDo!, seenProjects);
517+
for (const symlinkedProject of symlinkedProjects) {
518+
addToTodo(symlinkedProject, { fileName: symlinkedPath, pos: originalLocation.pos } as TLocation, toDo!, seenProjects);
519+
}
515520
});
516521
}
517522
return originalLocation === location ? undefined : originalLocation;
518523
});
519524
return toDo;
520525
}
521526

522-
function addToTodo<TLocation extends DocumentPosition | undefined>(projectAndLocation: ProjectAndLocation<TLocation>, toDo: Push<ProjectAndLocation<TLocation>>, seenProjects: Map<true>): void {
523-
if (addToSeen(seenProjects, projectAndLocation.project)) toDo.push(projectAndLocation);
527+
function addToTodo<TLocation extends DocumentPosition | undefined>(project: Project, location: TLocation, toDo: Push<ProjectAndLocation<TLocation>>, seenProjects: Map<true>): void {
528+
if (addToSeen(seenProjects, project)) toDo.push({ project, location });
524529
}
525530

526531
function addToSeen(seenProjects: Map<true>, project: Project) {
@@ -1323,7 +1328,7 @@ namespace ts.server {
13231328
// filter handles case when 'projects' is undefined
13241329
projects = filter(projects, p => p.languageServiceEnabled && !p.isOrphan());
13251330
if (!ignoreNoProjectError && (!projects || !projects.length) && !symLinkedProjects) {
1326-
this.projectService.logErrorForScriptInfoNotFound(args.file);
1331+
this.projectService.logErrorForScriptInfoNotFound(args.file ?? args.projectFileName);
13271332
return Errors.ThrowNoProject();
13281333
}
13291334
return symLinkedProjects ? { projects: projects!, symLinkedProjects } : projects!; // TODO: GH#18217
@@ -1335,6 +1340,9 @@ namespace ts.server {
13351340
if (project) {
13361341
return project;
13371342
}
1343+
if (!args.file) {
1344+
return Errors.ThrowNoProject();
1345+
}
13381346
}
13391347
const info = this.projectService.getScriptInfo(args.file)!;
13401348
return info.getDefaultProject();
@@ -1894,20 +1902,25 @@ namespace ts.server {
18941902
}
18951903

18961904
private getFullNavigateToItems(args: protocol.NavtoRequestArgs): readonly NavigateToItem[] {
1897-
const { currentFileOnly, searchValue, maxResultCount } = args;
1905+
const { currentFileOnly, searchValue, maxResultCount, projectFileName } = args;
18981906
if (currentFileOnly) {
1899-
const { file, project } = this.getFileAndProject(args);
1907+
Debug.assertDefined(args.file);
1908+
const { file, project } = this.getFileAndProject(args as protocol.FileRequestArgs);
19001909
return project.getLanguageService().getNavigateToItems(searchValue, maxResultCount, file);
19011910
}
1902-
else {
1903-
return combineProjectOutputWhileOpeningReferencedProjects<NavigateToItem>(
1904-
this.getProjects(args),
1905-
this.getDefaultProject(args),
1906-
project =>
1907-
project.getLanguageService().getNavigateToItems(searchValue, maxResultCount, /*fileName*/ undefined, /*excludeDts*/ project.isNonTsProject()),
1908-
documentSpanLocation,
1911+
else if (!args.file && !projectFileName) {
1912+
return combineProjectOutputFromEveryProject(
1913+
this.projectService,
1914+
project => project.getLanguageService().getNavigateToItems(searchValue, maxResultCount, /*filename*/ undefined, /*excludeDts*/ project.isNonTsProject()),
19091915
navigateToItemIsEqualTo);
19101916
}
1917+
const fileArgs = args as protocol.FileRequestArgs;
1918+
return combineProjectOutputWhileOpeningReferencedProjects<NavigateToItem>(
1919+
this.getProjects(fileArgs),
1920+
this.getDefaultProject(fileArgs),
1921+
project => project.getLanguageService().getNavigateToItems(searchValue, maxResultCount, /*fileName*/ undefined, /*excludeDts*/ project.isNonTsProject()),
1922+
documentSpanLocation,
1923+
navigateToItemIsEqualTo);
19111924

19121925
function navigateToItemIsEqualTo(a: NavigateToItem, b: NavigateToItem): boolean {
19131926
if (a === b) {

Diff for: src/testRunner/unittests/tsserver/declarationFileMaps.ts

+64-4
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ namespace ts.projectSystem {
6363
};
6464
const aDts: File = {
6565
path: "/a/bin/a.d.ts",
66-
// Need to mangle the sourceMappingURL part or it breaks the build
66+
// ${""} is needed to mangle the sourceMappingURL part or it breaks the build
6767
content: `export declare function fnA(): void;\nexport interface IfaceA {\n}\nexport declare const instanceA: IfaceA;\n//# source${""}MappingURL=a.d.ts.map`,
6868
};
6969

@@ -86,7 +86,7 @@ namespace ts.projectSystem {
8686
content: JSON.stringify(bDtsMapContent),
8787
};
8888
const bDts: File = {
89-
// Need to mangle the sourceMappingURL part or it breaks the build
89+
// ${""} is need to mangle the sourceMappingURL part so it doesn't break the build
9090
path: "/b/bin/b.d.ts",
9191
content: `export declare function fnB(): void;\n//# source${""}MappingURL=b.d.ts.map`,
9292
};
@@ -114,15 +114,17 @@ namespace ts.projectSystem {
114114
})
115115
};
116116

117-
function makeSampleProjects(addUserTsConfig?: boolean) {
117+
function makeSampleProjects(addUserTsConfig?: boolean, keepAllFiles?: boolean) {
118118
const host = createServerHost([aTs, aTsconfig, aDtsMap, aDts, bTsconfig, bTs, bDtsMap, bDts, ...(addUserTsConfig ? [userTsForConfigProject, userTsconfig] : [userTs]), dummyFile]);
119119
const session = createSession(host);
120120

121121
checkDeclarationFiles(aTs, session, [aDtsMap, aDts]);
122122
checkDeclarationFiles(bTs, session, [bDtsMap, bDts]);
123123

124124
// Testing what happens if we delete the original sources.
125-
host.deleteFile(bTs.path);
125+
if (!keepAllFiles) {
126+
host.deleteFile(bTs.path);
127+
}
126128

127129
openFilesForSession([userTs], session);
128130
const service = session.getProjectService();
@@ -322,6 +324,64 @@ namespace ts.projectSystem {
322324
verifyATsConfigOriginalProject(session);
323325
});
324326

327+
it("navigateToAll -- when neither file nor project is specified", () => {
328+
const session = makeSampleProjects(/*addUserTsConfig*/ true, /*keepAllFiles*/ true);
329+
const response = executeSessionRequest<protocol.NavtoRequest, protocol.NavtoResponse>(session, CommandNames.Navto, { file: undefined, searchValue: "fn" });
330+
assert.deepEqual<readonly protocol.NavtoItem[] | undefined>(response, [
331+
{
332+
...protocolFileSpanFromSubstring({
333+
file: bTs,
334+
text: "export function fnB() {}"
335+
}),
336+
name: "fnB",
337+
matchKind: "prefix",
338+
isCaseSensitive: true,
339+
kind: ScriptElementKind.functionElement,
340+
kindModifiers: "export",
341+
},
342+
{
343+
...protocolFileSpanFromSubstring({
344+
file: aTs,
345+
text: "export function fnA() {}"
346+
}),
347+
name: "fnA",
348+
matchKind: "prefix",
349+
isCaseSensitive: true,
350+
kind: ScriptElementKind.functionElement,
351+
kindModifiers: "export",
352+
},
353+
{
354+
...protocolFileSpanFromSubstring({
355+
file: userTs,
356+
text: "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }"
357+
}),
358+
name: "fnUser",
359+
matchKind: "prefix",
360+
isCaseSensitive: true,
361+
kind: ScriptElementKind.functionElement,
362+
kindModifiers: "export",
363+
}
364+
]);
365+
});
366+
367+
it("navigateToAll -- when file is not specified but project is", () => {
368+
const session = makeSampleProjects(/*addUserTsConfig*/ true, /*keepAllFiles*/ true);
369+
const response = executeSessionRequest<protocol.NavtoRequest, protocol.NavtoResponse>(session, CommandNames.Navto, { projectFileName: bTsconfig.path, file: undefined, searchValue: "fn" });
370+
assert.deepEqual<readonly protocol.NavtoItem[] | undefined>(response, [
371+
{
372+
...protocolFileSpanFromSubstring({
373+
file: bTs,
374+
text: "export function fnB() {}"
375+
}),
376+
name: "fnB",
377+
matchKind: "prefix",
378+
isCaseSensitive: true,
379+
kind: ScriptElementKind.functionElement,
380+
kindModifiers: "export",
381+
}
382+
]);
383+
});
384+
325385
const referenceATs = (aTs: File): protocol.ReferencesResponseItem => makeReferenceItem({
326386
file: aTs,
327387
isDefinition: true,

Diff for: tests/baselines/reference/api/tsserverlibrary.d.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -8319,7 +8319,7 @@ declare namespace ts.server.protocol {
83198319
/**
83208320
* Arguments for navto request message.
83218321
*/
8322-
interface NavtoRequestArgs extends FileRequestArgs {
8322+
interface NavtoRequestArgs {
83238323
/**
83248324
* Search term to navigate to from current location; term can
83258325
* be '.*' or an identifier prefix.
@@ -8329,6 +8329,10 @@ declare namespace ts.server.protocol {
83298329
* Optional limit on the number of items to return.
83308330
*/
83318331
maxResultCount?: number;
8332+
/**
8333+
* The file for the request (absolute pathname required).
8334+
*/
8335+
file?: string;
83328336
/**
83338337
* Optional flag to indicate we want results for just the current file
83348338
* or the entire project.
@@ -8342,7 +8346,7 @@ declare namespace ts.server.protocol {
83428346
* match the search term given in argument 'searchTerm'. The
83438347
* context for the search is given by the named file.
83448348
*/
8345-
interface NavtoRequest extends FileRequest {
8349+
interface NavtoRequest extends Request {
83468350
command: CommandTypes.Navto;
83478351
arguments: NavtoRequestArgs;
83488352
}

0 commit comments

Comments
 (0)