feat(compiler-cli): provide the animations for language service#40300
feat(compiler-cli): provide the animations for language service#40300ivanwonder wants to merge 5 commits intoangular:masterfrom
Conversation
4e85642 to
5457817
Compare
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
I'd propose to use null instead of undefined here:
| 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.
There was a problem hiding this comment.
Can we avoid having any here?
There was a problem hiding this comment.
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)
JoostK
left a comment
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
ef1ec97 to
e112946
Compare
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
I suggest to name this more aptly animationTriggerNames, as meta is such a generic term.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
| * collect the animation names form the static evaluation result. | |
| * Collect the animation names from the static evaluation result. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Consider using a more descriptive name for this as well, ideally it's identical to the name used in the analysis object.
There was a problem hiding this comment.
| }) class TestCmp {} | |
| }) | |
| class TestCmp {} |
There was a problem hiding this comment.
You'll also want tests for dynamic data, where two cases are relevant:
- the value itself is completely dynamic:
animations: buildComplexAnimations(),- 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)];
}554b038 to
d08d8ee
Compare
There was a problem hiding this comment.
I just add a new interface, that's more descriptive.
Maybe it's better to use isPartial instead of includeDynamicAnimations.
There was a problem hiding this comment.
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)
JoostK
left a comment
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
| 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.
There was a problem hiding this comment.
Could you update this description please
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 :-)
There was a problem hiding this comment.
Please avoid such non-descriptive variables names:
| const eva = this.evaluator.evaluate(component.get('animations')!); | |
| const animationsValue = this.evaluator.evaluate(component.get('animations')!); |
There was a problem hiding this comment.
super nit:
| animationTriggerNames: analysis.animationTriggerNames | |
| animationTriggerNames: analysis.animationTriggerNames, |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
| * Only include the animation names. | |
| * Only includes the animation names. |
There was a problem hiding this comment.
This assertion is superfluous, given the next expectation that test for the full array contents. The same is true for the two tests below.
There was a problem hiding this comment.
| it('should tell if the animations include the dynamic value', () => { | |
| it('should tell if the animations include a dynamic value', () => { |
There was a problem hiding this comment.
Was this meant to be something like this?
| it('should return `AnimationTriggerNames` when animations is complex', () => { | |
| it('should treat complex animations expressions as dynamic', () => { |
|
Thanks, I will add another commit to provide the capabilities in language service in this PR soon. |
In `language-service`, the `checker.getDirectiveMetadata` doesn't return the animations meta of the `Component`. but it's useful for animation completion.
bca0f6d to
caa227d
Compare
caa227d to
93b0226
Compare
Support completions for animation.
93b0226 to
7c333f3
Compare
|
Now I have added a new commit to support animation in the language service. |
DirectiveMeta|
@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! |
|
Why the |
@ivanwonder looks like it was accidentally removed in bb9ff60, together with |
|
@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: 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. |
|
@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. |
|
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. |

See individual commits.
PR Checklist
Please check if your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
What is the current behavior?
Issue Number: N/A
What is the new behavior?
Does this PR introduce a breaking change?
Other information