-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Clean up FAR and NavTo aggregation #47938
Conversation
I'm generally okay with the test change (seeing fewer results when a file is specified) both in principal, since not loading projects seems preferable when scoping to the active document, and in practice, since AFAIK only old version of VS Code pass a file with no project.
This change had two goals: 1. Make the code easier to understand, primarily by simplifying the callback structure and minimizing side-effects 2. Improve performance by reducing repeated work, both FAR searches of individual projects and default tsconfig searches Most of the baseline changes just reflect the de-duping of tsconfig searches, a few show fewer and occasionally different FAR invocations, and a couple show response changes. I'm quite confident that the new FAR calls are better and moderately confident that the new isDefinition values are better (not to mention moderately skeptical that anyone will ever hit a case where the difference matters).
@@ -308,20 +310,9 @@ namespace ts.projectSystem { | |||
kind: ScriptElementKind.functionElement, | |||
kindModifiers: "export", | |||
}, | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This tests mimics the request of an old VS Code client. A new client will not pass a file in the default allOpenProjects
mode, but will in the opt-in currentProjectMode
. I didn't think it made sense to return other-project results when the user selected currentProjectMode
and scoping in this way improved consistency with other scenarios (mostly VS).
@@ -122,7 +122,5 @@ Finding references to /user/username/projects/myproject/a/index.ts position 40 i | |||
FileWatcher:: Added:: WatchInfo: /user/username/projects/myproject/b/lib/index.d.ts.map 500 undefined WatchType: Closed Script info | |||
Search path: /user/username/projects/myproject/b | |||
For info: /user/username/projects/myproject/b/index.ts :: Config file name: /user/username/projects/myproject/b/tsconfig.json | |||
Search path: /user/username/projects/myproject/b |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A pair of lines like this indicates that we were walking up from (e.g.) /user/username/projects/myproject/b/index.ts
to find the nearest (aka "default"?) tsconfig, (e.g.) /user/username/projects/myproject/b/tsconfig.json
. If you scroll up, you will find an identical pair of lines indicating that this search was redundant.
Finding references to /user/username/projects/myproject/src/helpers/functions.ts position 13 in project /user/username/projects/myproject/tsconfig-src.json |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason for looking here instead is nuanced. Breadcrumb for when someone asks me: this is the definition location of the symbol at the initial location.
Finding references to /user/username/projects/myproject/src/helpers/functions.ts position 13 in project /user/username/projects/myproject/tsconfig-src.json | ||
response:{"response":{"refs":[{"file":"/user/username/projects/myproject/indirect3/main.ts","start":{"line":1,"offset":10},"end":{"line":1,"offset":13},"contextStart":{"line":1,"offset":1},"contextEnd":{"line":1,"offset":28},"lineText":"import { foo } from 'main';","isWriteAccess":true,"isDefinition":true},{"file":"/user/username/projects/myproject/indirect3/main.ts","start":{"line":2,"offset":1},"end":{"line":2,"offset":4},"lineText":"foo;","isWriteAccess":false,"isDefinition":false},{"file":"/user/username/projects/myproject/src/main.ts","start":{"line":1,"offset":10},"end":{"line":1,"offset":13},"contextStart":{"line":1,"offset":1},"contextEnd":{"line":1,"offset":41},"lineText":"import { foo } from 'helpers/functions';","isWriteAccess":true,"isDefinition":false},{"file":"/user/username/projects/myproject/src/main.ts","start":{"line":2,"offset":10},"end":{"line":2,"offset":13},"contextStart":{"line":2,"offset":1},"contextEnd":{"line":2,"offset":16},"lineText":"export { foo };","isWriteAccess":true,"isDefinition":false},{"file":"/user/username/projects/myproject/src/helpers/functions.ts","start":{"line":1,"offset":14},"end":{"line":1,"offset":17},"contextStart":{"line":1,"offset":1},"contextEnd":{"line":1,"offset":22},"lineText":"export const foo = 1;","isWriteAccess":true,"isDefinition":true}],"symbolName":"foo","symbolStartOffset":10,"symbolDisplayString":"(alias) const foo: 1\nimport foo"},"responseRequired":true} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isDefinition
use to be true for the import and false for the const and now it is the opposite. Based on where the search started, I believe this is more correct.
Search path: /user/username/projects/myproject/src/helpers | ||
For info: /user/username/projects/myproject/src/helpers/functions.ts :: Config file name: /user/username/projects/myproject/tsconfig.json | ||
Finding references to /user/username/projects/myproject/src/helpers/functions.ts position 13 in project /user/username/projects/myproject/tsconfig-src.json |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a few places, a search happens earlier than it used to. This is part of trying to eliminate duplicate searches (pulling the high-quality search ahead of the low-quality search so the low-quality search can be skipped).
Finding references to /user/username/projects/myproject/src/helpers/functions.ts position 13 in project /user/username/projects/myproject/tsconfig-src.json | ||
response:{"response":{"refs":[{"file":"/user/username/projects/myproject/indirect3/main.ts","start":{"line":1,"offset":10},"end":{"line":1,"offset":13},"contextStart":{"line":1,"offset":1},"contextEnd":{"line":1,"offset":28},"lineText":"import { foo } from 'main';","isWriteAccess":true,"isDefinition":true},{"file":"/user/username/projects/myproject/indirect3/main.ts","start":{"line":2,"offset":1},"end":{"line":2,"offset":4},"lineText":"foo;","isWriteAccess":false,"isDefinition":false},{"file":"/user/username/projects/myproject/src/main.ts","start":{"line":1,"offset":10},"end":{"line":1,"offset":13},"contextStart":{"line":1,"offset":1},"contextEnd":{"line":1,"offset":41},"lineText":"import { foo } from 'helpers/functions';","isWriteAccess":true,"isDefinition":false},{"file":"/user/username/projects/myproject/src/main.ts","start":{"line":2,"offset":10},"end":{"line":2,"offset":13},"contextStart":{"line":2,"offset":1},"contextEnd":{"line":2,"offset":16},"lineText":"export { foo };","isWriteAccess":true,"isDefinition":false},{"file":"/user/username/projects/myproject/src/helpers/functions.ts","start":{"line":1,"offset":14},"end":{"line":1,"offset":17},"contextStart":{"line":1,"offset":1},"contextEnd":{"line":1,"offset":22},"lineText":"export const foo = 1;","isWriteAccess":true,"isDefinition":true},{"file":"/user/username/projects/myproject/own/main.ts","start":{"line":1,"offset":10},"end":{"line":1,"offset":13},"contextStart":{"line":1,"offset":1},"contextEnd":{"line":1,"offset":28},"lineText":"import { foo } from 'main';","isWriteAccess":true,"isDefinition":false},{"file":"/user/username/projects/myproject/own/main.ts","start":{"line":2,"offset":1},"end":{"line":2,"offset":4},"lineText":"foo;","isWriteAccess":false,"isDefinition":false}],"symbolName":"foo","symbolStartOffset":10,"symbolDisplayString":"(alias) const foo: 1\nimport foo"},"responseRequired":true} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isDefinition
use to be true for the import and false for the const and now it is the opposite. Based on where the search started, I believe this is more correct.
@@ -294,71 +294,47 @@ namespace ts.server { | |||
return deduplicate(outputs, equateValues); | |||
} | |||
|
|||
type CombineOutputResult<T> = { project: Project; result: readonly T[]; }[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of these helpers were inlined. The survivors were renamed and probably nested.
|
||
const results: RenameLocation[] = []; | ||
|
||
for (const project of perProjectResults.projects) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The aggregation is now here, rather than in the callback.
); | ||
|
||
return outputs; | ||
// No filtering or dedup'ing is required if there's exactly one project |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Special-casing the single-project case is new and should avoid a bunch of linear-search de-duping.
projects: Projects, | ||
defaultProject: Project, | ||
initialLocation: DocumentPosition, | ||
initialPosition: DocumentPosition, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could be persuaded to rename these back to location, since that seems to be a convention, but it doesn't make sense to me.
* @returns In the common case where there's only one project, returns an array of results from {@link getResultsForPosition}. | ||
* If multiple projects were searched - even if they didn't return results - the result will be a {@link PerProjectResults}. | ||
*/ | ||
function getPerProjectReferences<TResult>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the meat of the FAR logic. I'd read it as standalone code and not try to map back to the old implementation. If the comments aren't sufficient, I'd like to improve them.
@@ -1610,11 +1689,22 @@ namespace ts.server { | |||
|
|||
private getFileReferences(args: protocol.FileRequestArgs, simplifiedResult: boolean): protocol.FileReferencesResponseBody | readonly ReferenceEntry[] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This no longer uses a shared helper, since it behaves differently.
@@ -2121,26 +2211,73 @@ namespace ts.server { | |||
); | |||
} | |||
|
|||
private getFullNavigateToItems(args: protocol.NavtoRequestArgs): CombineOutputResult<NavigateToItem> { | |||
private getFullNavigateToItems(args: protocol.NavtoRequestArgs): ProjectNavigateToItems[] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This no longer uses a shared helper and there are behavioral changes (see baseline). I also attempted to get rid of some of the linear searching, but JS wasn't very cooperative.
// VS's `Go to symbol` sends requests with just a project and doesn't want cascading since it will | ||
// send a separate request for each project of interest | ||
|
||
// TODO (https://github.com/microsoft/TypeScript/issues/47839) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I basically need Sheetal's input to resolve these, but I think the current behavior is acceptably similar to the current behavior.
I'm basically happy with this PR, but I've marked it as draft while I gather perf numbers and try dogfooding it. |
referencedSymbol.definition : | ||
{ | ||
...referencedSymbol.definition, | ||
textSpan: createTextSpan(mappedDefinitionFile.pos, referencedSymbol.definition.textSpan.length), // Why would the length be the same in the original? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are probably always tokens.
interface PerProjectResults<TResult> { | ||
// The projects that were searched in the order in which they were searched. | ||
// (The order may be slightly fudged to prioritize "authoritative" projects.) | ||
projects: readonly Project[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Talk to @sandersn about isDefinition
Consumers of
Edit: I filed a bug suggesting VS ignore it entirely. |
I'm going to split out the NavTo cleanup and the tsconfig search caching. Then I'll separately revisit how to correctly implement |
Split out #48106. |
Split out and abandoned #48113. |
Split out the remainder of this change as #48211 |
Remaining work is in #48619. |
with typescript 3.7, refactor stopped working. Is it a known issue? |
@rbadapanda Can you please file a new issue, specifying which refactoring(s) you are not seeing? Fair warning: you are likely to be asked to try to repro in the most recent release (4.6) or the nightly build. |
We have library functions for searching within a project for symbols, references, rename locations, etc. All of that code is exactly as before - this change is about the layer that sits on top of that and aggregates the results across multiple projects. In the course of some performance investigations, I discovered that user-FAR was invoking per-project-FAR more times than I expected and that the code wasn't readable enough for me to confidently determine why or how to prevent it. This change attempts to address both issues.
As a bonus, while doing this, I also discovered that we repeat the search for the nearest tsconfig because the results are only cached for open files, which FAR and navto results tend not to be in. This change de-dups those as well. (If we decide this change is too risky, I believe this bit can be salvaged.)