Skip to content

feat(compiler-cli): provide the animations for language service#40300

Closed
ivanwonder wants to merge 5 commits intoangular:masterfrom
ivanwonder:provide-animation-meta
Closed

feat(compiler-cli): provide the animations for language service#40300
ivanwonder wants to merge 5 commits intoangular:masterfrom
ivanwonder:provide-animation-meta

Conversation

@ivanwonder
Copy link
Contributor

@ivanwonder ivanwonder commented Jan 4, 2021

See individual commits.

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • angular.io application / infrastructure changes
  • Other... Please describe:

What is the current behavior?

Issue Number: N/A

What is the new behavior?

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

@google-cla google-cla bot added the cla: yes label Jan 4, 2021
@pullapprove pullapprove bot requested a review from AndrewKushnir January 4, 2021 01:48
@pullapprove pullapprove bot added the area: compiler Issues related to `ngc`, Angular's template compiler label Jan 4, 2021
@ngbot ngbot bot added this to the Backlog milestone Jan 4, 2021
@ivanwonder ivanwonder force-pushed the provide-animation-meta branch from 4e85642 to 5457817 Compare January 4, 2021 02:09
@AndrewKushnir AndrewKushnir requested a review from JoostK January 5, 2021 19:09
Copy link
Contributor

@AndrewKushnir AndrewKushnir left a comment

Choose a reason for hiding this comment

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

@ivanwonder, thanks for the PR!

I've left a couple comments and also added @JoostK as a reviewer to take another look as well.

Thank you.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd propose to use null instead of undefined here:

Suggested change
let animations: Map<string, string>[]|undefined;
let animations: Map<string, string>[]|null = null;

and also make the animations field of the DirectiveMeta structure non-optional. This should be more inline with the rest of the code.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we avoid having any here?

Copy link
Member

Choose a reason for hiding this comment

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

The any is definitely an issue here. The static evaluation result of the animations metadata can also be DynamicValue, as we no longer require static understanding of the animations data at compile time (unlike VE) so for cases where someone does animations: buildComplexAnimation(foo, bar) this will be a DynamicValue.

Can we do additional processing of this data to arrive at just the animation trigger names? I suppose that's sufficient for autocompletion purposes and it's potentially useful during template type-checking. This could be modelled as e.g. string[] | 'dynamic' | null to distinguish statically analyzable animation triggers, dynamic animations and no animations. It does not allow for partially dynamic animations, but I would keep it simple for now (treat it as dynamic if it's not possible to extract all trigger names)

@AndrewKushnir AndrewKushnir added the action: review The PR is still awaiting reviews from at least one requested reviewer label Jan 5, 2021
Copy link
Member

@JoostK JoostK left a comment

Choose a reason for hiding this comment

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

Hi @ivanwonder, thanks for taking the time to make progress towards support animation completions. I have a few suggestions below, PTAL.

Additionally, I would prefer if support for animation completions could land together with adding the necessary compiler bits, as to avoid introducing logic without any usages and knowing that it will end up satisfying the desired goal. It's fine to have multiple commits to separate the work into smaller chunks, though, so you can just extend this PR with new commits as you see fit.

Copy link
Member

Choose a reason for hiding this comment

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

The any is definitely an issue here. The static evaluation result of the animations metadata can also be DynamicValue, as we no longer require static understanding of the animations data at compile time (unlike VE) so for cases where someone does animations: buildComplexAnimation(foo, bar) this will be a DynamicValue.

Can we do additional processing of this data to arrive at just the animation trigger names? I suppose that's sufficient for autocompletion purposes and it's potentially useful during template type-checking. This could be modelled as e.g. string[] | 'dynamic' | null to distinguish statically analyzable animation triggers, dynamic animations and no animations. It does not allow for partially dynamic animations, but I would keep it simple for now (treat it as dynamic if it's not possible to extract all trigger names)

Copy link
Member

Choose a reason for hiding this comment

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

The DecoratorHandler.register callback is designed to register analysis data from a previous build into the object graph of the current build, e.g. for publishing directives into the NgModule scope registry. As such, it should ideally not be doing "real" work such as partial evaluation; if this information has to be computed then that is done in DecoratorHandler.analyze and stored in the resulting metadata type.

Copy link
Member

Choose a reason for hiding this comment

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

The value type of string is not accurate here; animations are a complex structure of objects to describe the animation sequences. This also isn't necessarily an array of Maps, as it could be entirely dynamic as mentioned before, and animations also support nested arrays (e.g. [[trigger('trigger')]] is supported). If we have logic to extract just the animation trigger names as suggested before, this comment will also be addressed.

@ivanwonder ivanwonder force-pushed the provide-animation-meta branch from ef1ec97 to e112946 Compare January 6, 2021 03:20
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@JoostK I add a new animationsMate here because I don't know why the meta.animations will be replaced during the compilation step, see the comment in line 346.

Copy link
Member

Choose a reason for hiding this comment

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

That comment is outdated, it used to be associated with pipes and directives (see 27bc7dc). Feel free to remove it.

You'll need a new field for this data anyway, as the existing animations is a WrappedNodeExpr that just contains the original ts.Node from the source, without having gone through any static analysis.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This could be modelled as e.g. string[] | 'dynamic' | null to distinguish statically analyzable animation triggers, dynamic animations and no animations. It does not allow for partially dynamic animations, but I would keep it simple for now (treat it as dynamic if it's not possible to extract all trigger names)

@JoostK I think it's better to be Array<string|DynamicValue>|null. It's still useful to provide the completion if not all trigger names can be extracted.
Now I will ignore the DynamicValue if the nested array contains the dynamic animations. Or I should clear the collected name and treat it as dynamic

Copy link
Member

Choose a reason for hiding this comment

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

I was deliberately avoiding DynamicValue as it is only produced in the partial evaluator, whereas we'll also want to use 'dynamic' (or similar) in your retrieveAnimationName for objects that aren't recognized (at which point we aren't able to create a DynamicValue). An alternative could be to model it using something like Array<string | null> | null, although that's a bit less descriptive.

@ivanwonder ivanwonder requested a review from JoostK January 6, 2021 05:50
Copy link
Member

Choose a reason for hiding this comment

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

I suggest to name this more aptly animationTriggerNames, as meta is such a generic term.

Copy link
Member

Choose a reason for hiding this comment

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

I was deliberately avoiding DynamicValue as it is only produced in the partial evaluator, whereas we'll also want to use 'dynamic' (or similar) in your retrieveAnimationName for objects that aren't recognized (at which point we aren't able to create a DynamicValue). An alternative could be to model it using something like Array<string | null> | null, although that's a bit less descriptive.

Copy link
Member

Choose a reason for hiding this comment

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

Please make this a free function, as there's no class data needed here. As a suggestion for the name, consider using collectAnimationTriggerNames as collect is a good indication that res is filled (in favor of a return value)

Copy link
Member

Choose a reason for hiding this comment

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

We generally use for-of loops instead of forEach.
The underscored _val name typically indicates that the variable is not used, but it is here.

Copy link
Member

Choose a reason for hiding this comment

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

I would expect that we record the else case here (and above if name is not a string) also as dynamic. It's possible that _val is a DynamicValue at this level, as the partial evaluator retains arrays even if it contains a dynamic value.

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* collect the animation names form the static evaluation result.
* Collect the animation names from the static evaluation result.

Copy link
Member

Choose a reason for hiding this comment

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

That comment is outdated, it used to be associated with pipes and directives (see 27bc7dc). Feel free to remove it.

You'll need a new field for this data anyway, as the existing animations is a WrappedNodeExpr that just contains the original ts.Node from the source, without having gone through any static analysis.

Copy link
Member

Choose a reason for hiding this comment

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

Consider using a more descriptive name for this as well, ideally it's identical to the name used in the analysis object.

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
}) class TestCmp {}
})
class TestCmp {}

Copy link
Member

Choose a reason for hiding this comment

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

You'll also want tests for dynamic data, where two cases are relevant:

  1. the value itself is completely dynamic:
animations: buildComplexAnimations(),
  1. an individual animation is dynamic:
animations: [trigger('a'), buildComplexAnimations()],

where buildComplexAnimations can be something that will be dynamic due to containing multiple statements:

function buildComplexAnimations() {
  const name = 'complex';
  return [trigger(complex)];
}

@ivanwonder ivanwonder force-pushed the provide-animation-meta branch from 554b038 to d08d8ee Compare January 7, 2021 02:25
@ivanwonder ivanwonder requested a review from JoostK January 7, 2021 02:28
Comment on lines 39 to 47
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I just add a new interface, that's more descriptive.
Maybe it's better to use isPartial instead of includeDynamicAnimations.

Copy link
Member

Choose a reason for hiding this comment

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

The interface was a great idea! I don't mind either name, so sticking with the one you have is fine (although I did suggest a minor change to the name)

Copy link
Member

@JoostK JoostK left a comment

Choose a reason for hiding this comment

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

Hi @ivanwonder this looks much better now! I left a bunch of suggestions.

What is your plan on incorporating this into the LS? As mentioned before I would prefer it we could make this change together with the support in the language service's autocompletion capabilities. That avoids leaving the compiler in a state where the computed data isn't yet used.

Additionally, the commit is currently a "feat", but I'd consider it just a "refactor" as there's no user facing change that is worth noting in the changelog.

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
function collectAnimationName(
function collectAnimationNames(

Also could you move this function after the main class, as this is only auxiliary logic I feel it fits better there.

Copy link
Member

Choose a reason for hiding this comment

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

Could you update this description please

Copy link
Member

Choose a reason for hiding this comment

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

This can be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The animations should be Array, but if the user's input is not Array (although the ts will report the error). This is why I add the condition here. If it's no Array, the function collectAnimationNames will be broken.

Copy link
Member

Choose a reason for hiding this comment

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

You are correct. My suggestion was to remove the res variable but I somehow goofed up the selection range. But I notice you removed the res so this one has been resolved :-)

Copy link
Member

Choose a reason for hiding this comment

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

Please avoid such non-descriptive variables names:

Suggested change
const eva = this.evaluator.evaluate(component.get('animations')!);
const animationsValue = this.evaluator.evaluate(component.get('animations')!);

Copy link
Member

Choose a reason for hiding this comment

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

super nit:

Suggested change
animationTriggerNames: analysis.animationTriggerNames
animationTriggerNames: analysis.animationTriggerNames,

Comment on lines 39 to 47
Copy link
Member

Choose a reason for hiding this comment

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

The interface was a great idea! I don't mind either name, so sticking with the one you have is fine (although I did suggest a minor change to the name)

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* Only include the animation names.
* Only includes the animation names.

Copy link
Member

Choose a reason for hiding this comment

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

This assertion is superfluous, given the next expectation that test for the full array contents. The same is true for the two tests below.

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
it('should tell if the animations include the dynamic value', () => {
it('should tell if the animations include a dynamic value', () => {

Copy link
Member

Choose a reason for hiding this comment

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

Was this meant to be something like this?

Suggested change
it('should return `AnimationTriggerNames` when animations is complex', () => {
it('should treat complex animations expressions as dynamic', () => {

@ivanwonder
Copy link
Contributor Author

Thanks, I will add another commit to provide the capabilities in language service in this PR soon.

@ivanwonder ivanwonder force-pushed the provide-animation-meta branch from bca0f6d to caa227d Compare January 16, 2021 10:11
@pullapprove pullapprove bot requested a review from atscott January 16, 2021 10:11
@ivanwonder ivanwonder force-pushed the provide-animation-meta branch from caa227d to 93b0226 Compare January 16, 2021 10:15
@ivanwonder ivanwonder force-pushed the provide-animation-meta branch from 93b0226 to 7c333f3 Compare January 16, 2021 10:52
@ivanwonder
Copy link
Contributor Author

Now I have added a new commit to support animation in the language service.
I pass the position into the CompletionBuilder because I need to determine where the cursor is (e.g. <input (@my¦.done¦)>). I don't know if it's the right way, but if not, maybe we need three new Node for animation name and animation phase to locate which node the cursor is in.

@ivanwonder ivanwonder changed the title feat(compiler-cli): provide the animations for DirectiveMeta feat(compiler-cli): provide the animations for language service Jan 21, 2021
@atscott
Copy link
Contributor

atscott commented Dec 16, 2021

@ivanwonder Sorry this got lost. I think the best way forward would be to create a new PR for this if it's something you still want to contribute. Thanks for all your hard work with your language service contributions!

@atscott atscott closed this Dec 16, 2021
@ivanwonder ivanwonder deleted the provide-animation-meta branch December 17, 2021 11:50
@ivanwonder
Copy link
Contributor Author

Why the packages/language-service/test/completions_spec.ts has been removed? 🤔️

@JoostK
Copy link
Member

JoostK commented Dec 17, 2021

Why the packages/language-service/test/completions_spec.ts has been removed? 🤔️

@ivanwonder looks like it was accidentally removed in bb9ff60, together with packages/language-service/test/definitions_spec.ts (and perhaps others). The Ivy implementation had already been promoted to take the place where the VE tests used to be, perhaps some wires got crossed during a rebase there.

@atscott
Copy link
Contributor

atscott commented Dec 17, 2021

@ivanwonder @JoostK Those were intentionally removed because they were tests for the legacy VE language service. Every Ivy test lived under the packages/language-service/ivy subfolder.

@JoostK
Copy link
Member

JoostK commented Dec 17, 2021

@ivanwonder @JoostK Those were intentionally removed because they were tests for the legacy VE language service. Every Ivy test lived under the packages/language-service/ivy subfolder.

These tests did exist for Ivy as well:

https://github.com/angular/angular/blob/12.2.x/packages/language-service/ivy/test/completions_spec.ts

your commit to promote the Ivy LS into the primary language-service folder landed before the VE cleanup, which is how I think the VE cleanup ended up removing files that had since been replaced with Ivy variants.

@atscott
Copy link
Contributor

atscott commented Dec 17, 2021

@JoostK Ahhhhh. Yes, you're right. Anything in that commit which touched the language service should likely be reverted since I had already removed VE in a separate commit.

Edit: Looks like it was just two files:
image

@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 17, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

action: review The PR is still awaiting reviews from at least one requested reviewer area: compiler Issues related to `ngc`, Angular's template compiler cla: yes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants