-
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
Support multiple completions with the same name but different source module #19455
Conversation
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 protocol changes of this look good to me. Thank you for the quick turn around here
src/server/protocol.ts
Outdated
/** | ||
* Human-readable description of the `source` from the CompletionEntry. | ||
*/ | ||
source?: SymbolDisplayPart[] | undefined; |
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.
[nit] Is undefined
needed since this is already optional?
src/services/types.ts
Outdated
@@ -238,8 +238,8 @@ namespace ts { | |||
|
|||
getCompletionsAtPosition(fileName: string, position: number): CompletionInfo; | |||
// "options" is optional only for backwards-compatibility | |||
getCompletionEntryDetails(fileName: string, position: number, entryName: string, options?: FormatCodeOptions | FormatCodeSettings): CompletionEntryDetails; | |||
getCompletionEntrySymbol(fileName: string, position: number, entryName: string): Symbol; | |||
getCompletionEntryDetails(fileName: string, position: number, id: string | CompletionEntryIdentifier, options?: FormatCodeOptions | FormatCodeSettings): CompletionEntryDetails; |
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 going to be a breaking change for plugin authors. pinging @chuckjaz and @angelozerr
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.
an alternative is to make the source as a third optional parameter.
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.
To me this change seems unnecessary as it assumes that the key to the entry is now the name
+ source
where name
should be sufficient. Currently, if I am adding something to the completion list I need to ensure that my name
s are unique already so the call to getCompletionEntryDetails
just needs the name
.
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.
name
should be sufficient
It isn't -- different completions (with different associated code actions) could share the same name. See the test completionsImport_multipleWithSameName.ts
.
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.
It is unfortunate already merge this.
Adding an optional parameter was arguably a worse breaking change than changing the type of one of the parameters. Both break clients of the API but adding an API will cause all existing plugins to appear to break TypeScript as they will silently miss passing the additional parameter as well as the compiler will not point out that this happened. Changing the type would still be a breaking change but, at least, existing implementation that wrap the language service would probably just pass the parameter unmolested to the wrapped version of the language service and it would work and recompiling the plugin would point out that something changed. In other words, if this change had not been brought to my attention I probably would have missed it entirely, publishing a broken language plugin.
The fundamental problem here is that name
is treated as insertText
and they should be different. This would allow plugins to be able to add duplicate insertText
without repeating the name
. name
should have just been an arbitrary string that was guaranteed to be unique for each entry. Adding source
as a documentation seems reasonable.
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 plugin can check the ts.version and you can decide to throw if it is higher than 2.6 for instance.
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.
we also have ts.servicesVersion
that we can change, and then you can do the check. I would say this is cleaner than ts.version, since the two change independently.
if this works for you, we can rev tat to 0.6
(currently 0.5
).
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 issue is in plugins that have already shipped not in new plugins. Checking the version doesn't help older versions. The version is fine for creating a shim for older versions of the API so a plugin can support older versions of TypeScript but a breaking change a @anglular/[email protected]
does not contain the code, couldn't have known that 2.6 would break it to even know to refuse to load. Only 2.6 knows it broke the API. And only it can decide if the an older version of a plugin that doesn't know about its change will work.
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 understand. i am trying to solve it for future versions. what i am proposing is use the API version. we will increment the API version every time we make a breaking change (that is ts.servicesVersion
) independently from the version of the language/toolset. the plugin can check at load time if the API version is the version it expects, and if not it can chose to fail, or just issue a warning.
would that work?
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 older, already shipped, version of the language service do not check this. This would guard everything from 5.0.1+
and a LTS version both of which would then check for 0.6 or 0.5 which they both could support.
|
||
goTo.marker(""); | ||
verify.completionListContains({ name: "foo", source: "/a" }, "const foo: 0", "", "const", /*spanIndex*/ undefined, /*hasAction*/ true); | ||
verify.completionListContains({ name: "foo", source: "/b" }, "const foo: 1", "", "const", /*spanIndex*/ undefined, /*hasAction*/ 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.
so global should be in this list too?
src/services/completions.ts
Outdated
? getSymbolId(origin.moduleSymbol) | ||
// -2 for globals | ||
: symbol.parent === undefined && !some(symbol.declarations, d => d.getSourceFile() === location.getSourceFile()) | ||
? -2 |
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.
consider making these a local enum for expressiveness
src/services/completions.ts
Outdated
const { name } = entry; | ||
let uniquesForName = uniques.get(name); | ||
if (!uniquesForName) { | ||
uniques.set(name, uniquesForName = []); |
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 gonna be sparse by construction. might be better to make it a map as well.
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.
we are guaranteed to see globals/locals before ones from modules, so:
- add a comment that explains that, since it is not obvious
- why not make it a map of number | [], or even split them into two maps global/local and modules, the reason is the global/local-only is by far the common case, and in that case we can avoid creating an extra array per entry.
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.
Let's try to avoid the breaking change to the LS interface and optimize the array allocation in getCompletionEntriesFromSymbols
We will need to port this to release-2.6 |
…module (#19455) * Support multiple completions with the same name but different source module * Use optional parameters for source * Simplify use of `uniques` * Update test * Fix `undefined` error
Fixes #19417
A
CompletionEntry
may now come with asource
. This identifies where it came from. We will now return multipleCompletionEntry
s if they have differentsource
properties. It's only meant to be handed back to TypeScript when getting theCompletionEntryDetails
, not for being shown to a user. TheCompletionEntryDetails
will then have its ownsource
property which is human readable.