-
Notifications
You must be signed in to change notification settings - Fork 24.8k
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
Global context autocompletion in the Ivy Language Service #39250
Conversation
579c3bb
to
f0f92b1
Compare
Reviewers: only the last commit is relevant, the others are part of #39594 and will go away after rebase. |
cb2f9f8
to
7406be9
Compare
2c052d5
to
11b4f94
Compare
it('should be able to complete a property binding', () => { | ||
const {ngLS, fileName, cursor} = | ||
setup('<h1 [model]="ti¦"></h1>', `title!: string; hero!: number;`); | ||
const completions = ngLS.getCompletionsAtPosition(fileName, cursor, /* options */ undefined); | ||
expectContain(completions, ts.ScriptElementKind.memberVariableElement, ['title', 'hero']); | ||
}); |
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.
Does an empty property binding work? [model]=""
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.
Added a test
}); | ||
|
||
it('should return completions inside an event binding', () => { | ||
const {ngLS, fileName, cursor} = setup(` <button (click)='t¦'></button>`, `title!: string;`); |
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.
Similarly, does an empty event binding 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.
It doesn't, currently, due to the way the template parser produces a weird, invalid token. I added a excluded test, which will be enabled after my refactoring of template_target
.
}); | ||
|
||
it('should return completions inside the RHS of a two-way binding', () => { | ||
const {ngLS, fileName, cursor} = setup(`<h1 [(model)]="t¦"></h1>`, `title!: string;`); |
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.
Similarly, does an empty two-way binding 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.
It took a bit of fixing, but I got this working, due to the broken way that such bindings are parsed.
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.
Other than my outstanding comments LGTM, this is awesome! adding Keen and Andrew as reviewers as well as they are probably interested too.
// Although this completion is "global" in the sense of an Angular expression (there is no | ||
// explicit receiver), it is not "global" in a TypeScript sense since Angular expressions have | ||
// the component as an implicit receiver. | ||
isGlobalCompletion: false, |
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.
optional bikeshed (though at this point, I'm leaning towards keeping this how it is because I can't think of a better name and it'd be a pain to rename):
This made me think about how this isn't really a "global" completion, but more like a "component scope" completion.
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 a TypeScript-defined field in ts.CompletionInfo
, we have no control over 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.
Right, what I meant was to possibly rename the entire implementation here to not say "global" because a "global" completion, as defined by this typescript field, is something else. But I'm also fine leaving this as-is.
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.
Ahhh, yeah, I see what you mean.
// Entries that reference a symbol in the template context refer either to local references or | ||
// variables. | ||
const symbol = templateTypeChecker.getSymbolOfNode(entry.node, this.component) as | ||
TemplateDeclarationSymbol | |
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.
Suggestion: maybe we should update the overloads of getSymbolOfNode
to return the more narrow types for TmplAstVariable
and TmplAstReference
inputs?
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 don't think that would actually work in this circumstance, as TypeScript will match a signature based on the union input type (TmplAstVariable|TmplAstReference
), and not "split" the union to match each branch individually and then create a union of the return types. So we'll need the cast either way.
const program = this.strategy.getProgram(); | ||
const compiler = this.compilerFactory.getOrCreateWithChangedFile(fileName); | ||
const templateInfo = getTemplateInfoAtPosition(fileName, position, compiler); | ||
if (templateInfo === undefined) { | ||
return undefined; | ||
} | ||
const positionDetails = getTargetAtPosition(templateInfo.template, position); | ||
if (positionDetails === null) { | ||
return 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.
Suggestion: these ~10 lines appear to be exact duplicates of each other in each of these completion functions. Is it possible to refactor to a helper method? (I think this is also pretty much the same work as all of the other "getXAtPosition" with fileName and position)
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.
Done - getCompletionBuilder
is now a thing.
contents: 'Placeholder template', | ||
} | ||
]); | ||
const {nodes, cursor} = env.overrideTemplateWithCursor(codePath, 'AppCmp', templateWithCursor); |
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.
suggestion: You actually kind of don't need overrideTemplateWithCursor
if you expose extractCursorInfo
.
You can instead do
const {text: templateText, cursor} = extractCursortInfo(templateWithCursor);
...
{
name: templatePath,
contents: templateText
}
Especially given that you don't need the overridden template nodes
.
I kind of prefer this because it eliminates some of the possible string errors (i.e. mistyping the class name) and doesn't create the file initially with placeholder text.
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.
👍 but will leave this as a future refactoring. I'd prefer to find a way not to "decouple" the extractCursorInfo
operation from the path/content it's representing.
const symbol = | ||
templateTypeChecker.getSymbolOfNode(node, this.component) as TemplateDeclarationSymbol | | ||
null; | ||
if (symbol === null || symbol.tsSymbol === null) { |
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: consider replacing these 4 lines with return symbol?.tsSymbol
. I think it is in line with the Angular "style guide" since this function could return 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.
I don't think that would work, since symbol.tsSymbol
would be null
and not undefined
. You'd need symbol?.tsSymbol ?? undefined
. I prefer the explicitness of the if
branch, which makes it very clear that this is exactly how we want to handle both cases.
return result; | ||
} | ||
|
||
getCompletionEntrySymbol(fileName: string, position: number, name: string): ts.Symbol|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.
Could you please add a comment to describe the difference between getCompletionEntryDetails
and getCompletionEntrySymbol
? When is one used over the other?
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.
Oh I have no idea - these are both defined in the TypeScript API so we implement them both.
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.
Oh, okay. For our purpose I don't think this matters much since we pick the API to call in the language server, but I'll add a card for myself to investigate how they're used in TypeScript language service.
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.
getCompletionEntrySymbol
was added because of microsoft/TypeScript#10812, but the issue doesn't give an example motivation.
330528d
to
f435d37
Compare
…s (Ivy) This commit adds support in the Ivy Language Service for autocompletion in a global context - e.g. a {{foo|}} completion. Support is added both for the primary function `getCompletionsAtPosition` as well as the detail functions `getCompletionEntryDetails` and `getCompletionEntrySymbol`. These latter operations are not used yet as an upstream change to the extension is required to advertise and support this capability.
f435d37
to
5469b2b
Compare
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
No description provided.