Skip to content

Commit

Permalink
fix(compiler-cli): Support resolve animation name from the DTS
Browse files Browse the repository at this point in the history
Before this, the compiler resolves the value in the DTS as dynamic.
If the `trigger` is imported from `@angular/animations`, this PR will
use FFR to simulate the actual implementation in JS and extracts the
animation name.
  • Loading branch information
ivanwonder committed Feb 16, 2022
1 parent f1d10ba commit cce1dd7
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {DependencyTracker} from '../../../incremental/api';
import {extractSemanticTypeParameters, SemanticDepGraphUpdater} from '../../../incremental/semantic_graph';
import {IndexingContext} from '../../../indexer';
import {DirectiveMeta, extractDirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry, MetaType, ResourceRegistry} from '../../../metadata';
import {PartialEvaluator} from '../../../partial_evaluator';
import {ForeignFunctionResolver, PartialEvaluator} from '../../../partial_evaluator';
import {PerfEvent, PerfRecorder} from '../../../perf';
import {ClassDeclaration, DeclarationNode, Decorator, ReflectionHost, reflectObjectLiteral} from '../../../reflection';
import {ComponentScopeReader, DtsModuleScopeResolver, LocalModuleScopeRegistry, TypeCheckScopeRegistry} from '../../../scope';
Expand All @@ -39,6 +39,8 @@ import {collectAnimationNames, validateAndFlattenComponentImports} from './util'

const EMPTY_MAP = new Map<string, Expression>();
const EMPTY_ARRAY: any[] = [];
const angularAnimation = '@angular/animations';
const animationTriggerMethodName = 'trigger';

/**
* `DecoratorHandler` which handles the `@Component` annotation.
Expand Down Expand Up @@ -210,8 +212,40 @@ export class ComponentDecoratorHandler implements
let animations: Expression|null = null;
let animationTriggerNames: AnimationTriggerNames|null = null;
if (component.has('animations')) {
animations = new WrappedNodeExpr(component.get('animations')!);
const animationsValue = this.evaluator.evaluate(component.get('animations')!);
const animationExpression = component.get('animations')!;
animations = new WrappedNodeExpr(animationExpression);

const animationTriggerResolver: ForeignFunctionResolver = ref => {
const triggerIdentifier = ref.getIdentityInExpression(animationExpression);
if (!triggerIdentifier) {
return null;
}
const importMetadata = this.reflector.getImportOfIdentifier(triggerIdentifier);
if (!importMetadata) {
return null;
}
const isTriggerFromAngularAnimations = importMetadata.from === angularAnimation &&
importMetadata.name === animationTriggerMethodName;
if (!isTriggerFromAngularAnimations) {
return null;
}
const triggerNameExpression = ts.isCallExpression(triggerIdentifier.parent) ?
triggerIdentifier.parent.arguments[0] :
undefined;
if (!triggerNameExpression) {
return null;
}
const factory = ts.factory;
return factory.createObjectLiteralExpression(
[
factory.createPropertyAssignment(
factory.createIdentifier('name'), triggerNameExpression),
],
true);
};

const animationsValue =
this.evaluator.evaluate(animationExpression, animationTriggerResolver);
animationTriggerNames = {includesDynamicAnimations: false, staticTriggerNames: []};
collectAnimationNames(animationsValue, animationTriggerNames);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,49 @@ runInEachFileSystem(() => {
expect(meta?.animationTriggerNames?.includesDynamicAnimations).toBeFalse();
});

it('should evaluate the name of animations in the DTS', () => {
const {program, options, host} = makeProgram([
{
name: _('/node_modules/@angular/core/index.d.ts'),
contents: 'export const Component: any;',
},
{
name: _('/node_modules/@angular/animations/index.d.ts'),
contents: 'export declare function trigger(name: any): any',
},
{
name: _('/entry.ts'),
contents: `
import {Component} from '@angular/core';
import {trigger} from '@angular/animations';
const complexName = 'nested' + 'AnimationName';
@Component({
template: '',
animations: [
trigger('animationName'),
[trigger(complexName)],
],
})
class TestCmp {}
`
},
]);
const {reflectionHost, handler, metaRegistry} = setup(program, options, host);
const TestCmp = getDeclaration(program, _('/entry.ts'), 'TestCmp', isNamedClassDeclaration);
const detected = handler.detect(TestCmp, reflectionHost.getDecoratorsOfDeclaration(TestCmp));
if (detected === undefined) {
return fail('Failed to recognize @Component');
}
const {analysis} = handler.analyze(TestCmp, detected.metadata);
handler.register(TestCmp, analysis!);
const meta = metaRegistry.getDirectiveMetadata(new Reference(TestCmp));
expect(meta?.animationTriggerNames?.staticTriggerNames).toEqual([
'animationName', 'nestedAnimationName'
]);
expect(meta?.animationTriggerNames?.includesDynamicAnimations).toBeFalse();
});

it('should tell if the animations include a dynamic value', () => {
const {program, options, host} = makeProgram([
{
Expand Down

0 comments on commit cce1dd7

Please sign in to comment.