Skip to content
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

Closed
wants to merge 1 commit into from

Conversation

alxhub
Copy link
Member

@alxhub alxhub commented Oct 13, 2020

No description provided.

@google-cla google-cla bot added the cla: yes label Oct 13, 2020
@atscott atscott added the area: language-service Issues related to Angular's VS Code language service label Oct 13, 2020
@ngbot ngbot bot added this to the needsTriage milestone Oct 13, 2020
@alxhub alxhub force-pushed the ngtsc/ls/autocomplete-3 branch 4 times, most recently from 579c3bb to f0f92b1 Compare November 12, 2020 19:47
@alxhub
Copy link
Member Author

alxhub commented Nov 12, 2020

Reviewers: only the last commit is relevant, the others are part of #39594 and will go away after rebase.

@alxhub alxhub force-pushed the ngtsc/ls/autocomplete-3 branch 3 times, most recently from cb2f9f8 to 7406be9 Compare November 17, 2020 20:07
@alxhub alxhub marked this pull request as ready for review November 17, 2020 20:08
@alxhub alxhub added the target: minor This PR is targeted for the next minor release label Nov 17, 2020
@pullapprove pullapprove bot requested a review from ayazhafiz November 17, 2020 20:08
packages/language-service/ivy/completions.ts Outdated Show resolved Hide resolved
packages/language-service/ivy/completions.ts Outdated Show resolved Hide resolved
packages/language-service/ivy/completions.ts Outdated Show resolved Hide resolved
packages/language-service/ivy/completions.ts Show resolved Hide resolved
packages/language-service/ivy/completions.ts Outdated Show resolved Hide resolved
packages/language-service/ivy/completions.ts Show resolved Hide resolved
packages/language-service/ivy/completions.ts Show resolved Hide resolved
packages/language-service/ivy/completions.ts Outdated Show resolved Hide resolved
packages/language-service/ivy/completions.ts Outdated Show resolved Hide resolved
@alxhub alxhub force-pushed the ngtsc/ls/autocomplete-3 branch 4 times, most recently from 2c052d5 to 11b4f94 Compare November 18, 2020 20:27
Comment on lines +36 to +41
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']);
});
Copy link
Member

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]=""

Copy link
Member Author

Choose a reason for hiding this comment

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

Added a test

packages/language-service/ivy/test/completions_spec.ts Outdated Show resolved Hide resolved
});

it('should return completions inside an event binding', () => {
const {ngLS, fileName, cursor} = setup(` <button (click)='t¦'></button>`, `title!: string;`);
Copy link
Member

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?

Copy link
Member Author

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.

packages/language-service/ivy/test/completions_spec.ts Outdated Show resolved Hide resolved
});

it('should return completions inside the RHS of a two-way binding', () => {
const {ngLS, fileName, cursor} = setup(`<h1 [(model)]="t¦"></h1>`, `title!: string;`);
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

@ayazhafiz ayazhafiz left a 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.

packages/language-service/ivy/completions.ts Outdated Show resolved Hide resolved
// 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,
Copy link
Contributor

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.

Copy link
Member Author

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.

Copy link
Contributor

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.

Copy link
Member Author

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 |
Copy link
Contributor

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?

Copy link
Member Author

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.

packages/language-service/ivy/completions.ts Outdated Show resolved Hide resolved
Comment on lines 146 to 155
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;
}
Copy link
Contributor

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)

Copy link
Member Author

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.

packages/language-service/ivy/test/completions_spec.ts Outdated Show resolved Hide resolved
contents: 'Placeholder template',
}
]);
const {nodes, cursor} = env.overrideTemplateWithCursor(codePath, 'AppCmp', templateWithCursor);
Copy link
Contributor

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.

Copy link
Member Author

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) {
Copy link
Contributor

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.

Copy link
Member Author

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 {
Copy link
Contributor

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?

Copy link
Member Author

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.

Copy link
Contributor

@kyliau kyliau Dec 2, 2020

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.

Copy link
Member

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.

@alxhub alxhub force-pushed the ngtsc/ls/autocomplete-3 branch 2 times, most recently from 330528d to f435d37 Compare December 3, 2020 00:14
@alxhub alxhub added the action: merge The PR is ready for merge by the caretaker label Dec 3, 2020
…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.
@mhevery mhevery closed this in 28a0bcb Dec 4, 2020
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Jan 4, 2021
@pullapprove pullapprove bot added the area: compiler Issues related to `ngc`, Angular's template compiler label Jan 4, 2021
@ngbot ngbot bot modified the milestones: needsTriage, Backlog Jan 4, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker area: compiler Issues related to `ngc`, Angular's template compiler area: language-service Issues related to Angular's VS Code language service cla: yes target: minor This PR is targeted for the next minor release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants