Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion packages/animations/browser/src/dsl/animation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {AnimationMetadata, AnimationMetadataType, AnimationOptions, ɵStyleDataM
import {buildingFailed, validationFailed} from '../error_helpers';
import {AnimationDriver} from '../render/animation_driver';
import {ENTER_CLASSNAME, LEAVE_CLASSNAME, normalizeStyles} from '../util';
import {warnValidation} from '../warning_helpers';

import {Ast} from './animation_ast';
import {buildAnimationAst} from './animation_ast_builder';
Expand All @@ -21,10 +22,14 @@ export class Animation {
private _animationAst: Ast<AnimationMetadataType>;
constructor(private _driver: AnimationDriver, input: AnimationMetadata|AnimationMetadata[]) {
const errors: Error[] = [];
const ast = buildAnimationAst(_driver, input, errors);
const warnings: string[] = [];
const ast = buildAnimationAst(_driver, input, errors, warnings);
if (errors.length) {
throw validationFailed(errors);
}
if (warnings.length) {
warnValidation(warnings);
}
this._animationAst = ast;
}

Expand Down
25 changes: 18 additions & 7 deletions packages/animations/browser/src/dsl/animation_ast_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {invalidDefinition, invalidKeyframes, invalidOffset, invalidParallelAnima
import {AnimationDriver} from '../render/animation_driver';
import {getOrSetDefaultValue} from '../render/shared';
import {convertToMap, copyObj, extractStyleParams, iteratorToArray, NG_ANIMATING_SELECTOR, NG_TRIGGER_SELECTOR, normalizeAnimationEntry, resolveTiming, SUBSTITUTION_EXPR_START, validateStyleParams, visitDslNode} from '../util';
import {pushUnrecognizedPropertiesWarning} from '../warning_helpers';

import {AnimateAst, AnimateChildAst, AnimateRefAst, Ast, DynamicTimingAst, GroupAst, KeyframesAst, QueryAst, ReferenceAst, SequenceAst, StaggerAst, StateAst, StyleAst, TimingAst, TransitionAst, TriggerAst} from './animation_ast';
import {AnimationDslVisitor} from './animation_dsl_visitor';
Expand Down Expand Up @@ -56,22 +57,30 @@ const SELF_TOKEN_REGEX = new RegExp(`\s*${SELF_TOKEN}\s*,?`, 'g');
* Otherwise an error will be thrown.
*/
export function buildAnimationAst(
driver: AnimationDriver, metadata: AnimationMetadata|AnimationMetadata[],
errors: Error[]): Ast<AnimationMetadataType> {
return new AnimationAstBuilderVisitor(driver).build(metadata, errors);
driver: AnimationDriver, metadata: AnimationMetadata|AnimationMetadata[], errors: Error[],
warnings: string[]): Ast<AnimationMetadataType> {
return new AnimationAstBuilderVisitor(driver).build(metadata, errors, warnings);
}

const ROOT_SELECTOR = '';

export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
constructor(private _driver: AnimationDriver) {}

build(metadata: AnimationMetadata|AnimationMetadata[], errors: Error[]):
build(metadata: AnimationMetadata|AnimationMetadata[], errors: Error[], warnings: string[]):
Ast<AnimationMetadataType> {
const context = new AnimationAstBuilderContext(errors);
this._resetContextStyleTimingState(context);
return <Ast<AnimationMetadataType>>visitDslNode(
this, normalizeAnimationEntry(metadata), context);
const ast =
<Ast<AnimationMetadataType>>visitDslNode(this, normalizeAnimationEntry(metadata), context);

if (context.unsupportedCSSPropertiesFound.size) {
pushUnrecognizedPropertiesWarning(
warnings,
[...context.unsupportedCSSPropertiesFound.keys()],
);
}
return ast;
}

private _resetContextStyleTimingState(context: AnimationAstBuilderContext) {
Expand Down Expand Up @@ -297,7 +306,8 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {

tuple.forEach((value, prop) => {
if (!this._driver.validateStyleProperty(prop)) {
context.errors.push(invalidProperty(prop));
tuple.delete(prop);
context.unsupportedCSSPropertiesFound.add(prop);
return;
}

Expand Down Expand Up @@ -503,6 +513,7 @@ export class AnimationAstBuilderContext {
public currentTime: number = 0;
public collectedStyles = new Map<string, Map<string, StyleTimeTuple>>();
public options: AnimationOptions|null = null;
public unsupportedCSSPropertiesFound: Set<string> = new Set<string>();
constructor(public errors: Error[]) {}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {buildAnimationAst} from '../dsl/animation_ast_builder';
import {AnimationTrigger, buildTrigger} from '../dsl/animation_trigger';
import {AnimationStyleNormalizer} from '../dsl/style_normalization/animation_style_normalizer';
import {triggerBuildFailed} from '../error_helpers';
import {warnTriggerBuild} from '../warning_helpers';

import {AnimationDriver} from './animation_driver';
import {parseTimelineCommand} from './shared';
Expand Down Expand Up @@ -44,11 +45,15 @@ export class AnimationEngine {
let trigger = this._triggerCache[cacheKey];
if (!trigger) {
const errors: Error[] = [];
const ast =
buildAnimationAst(this._driver, metadata as AnimationMetadata, errors) as TriggerAst;
const warnings: string[] = [];
const ast = buildAnimationAst(
this._driver, metadata as AnimationMetadata, errors, warnings) as TriggerAst;
if (errors.length) {
throw triggerBuildFailed(name, errors);
}
if (warnings.length) {
warnTriggerBuild(name, warnings);
}
trigger = buildTrigger(name, ast, this._normalizer);
this._triggerCache[cacheKey] = trigger;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {ElementInstructionMap} from '../dsl/element_instruction_map';
import {AnimationStyleNormalizer} from '../dsl/style_normalization/animation_style_normalizer';
import {createAnimationFailed, missingOrDestroyedAnimation, missingPlayer, registerFailed} from '../error_helpers';
import {ENTER_CLASSNAME, LEAVE_CLASSNAME} from '../util';
import {warnRegister} from '../warning_helpers';

import {AnimationDriver} from './animation_driver';
import {getOrSetDefaultValue, listenOnPlayer, makeAnimationEvent, normalizeKeyframes, optimizeGroupPlayer} from './shared';
Expand All @@ -32,10 +33,14 @@ export class TimelineAnimationEngine {

register(id: string, metadata: AnimationMetadata|AnimationMetadata[]) {
const errors: Error[] = [];
const ast = buildAnimationAst(this._driver, metadata, errors);
const warnings: string[] = [];
const ast = buildAnimationAst(this._driver, metadata, errors, warnings);
if (errors.length) {
throw registerFailed(errors);
} else {
if (warnings.length) {
warnRegister(warnings);
}
this._animations.set(id, ast);
}
}
Expand Down
43 changes: 43 additions & 0 deletions packages/animations/browser/src/warning_helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

const NG_DEV_MODE = typeof ngDevMode === 'undefined' || !!ngDevMode;

function createListOfWarnings(warnings: string[]): string {
const LINE_START = '\n - ';
return `${LINE_START}${warnings.filter(Boolean).map(warning => warning).join(LINE_START)}`;
}

export function warnValidation(warnings: string[]): void {
NG_DEV_MODE && console.warn(`animation validation warnings:${createListOfWarnings(warnings)}`);
}

export function warnTriggerBuild(name: string, warnings: string[]): void {
NG_DEV_MODE &&
console.warn(`The animation trigger "${name}" has built with the following warnings:${
createListOfWarnings(warnings)}`);
}

export function warnRegister(warnings: string[]): void {
NG_DEV_MODE &&
console.warn(`Animation built with the following warnings:${createListOfWarnings(warnings)}`);
}

export function triggerParsingWarnings(name: string, warnings: string[]): void {
NG_DEV_MODE &&
console.warn(`Animation parsing for the ${name} trigger presents the following warnings:${
createListOfWarnings(warnings)}`);
}

export function pushUnrecognizedPropertiesWarning(warnings: string[], props: string[]): void {
if (ngDevMode && props.length) {
warnings.push(
`The provided CSS properties are not recognized properties supported for animations: ${
props.join(', ')}`);
}
}
34 changes: 27 additions & 7 deletions packages/animations/browser/test/dsl/animation_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,16 +218,28 @@ function createDiv() {
/state\("panfinal", ...\) must define default values for all the following style substitutions: greyColor/);
});

it('should throw an error if an invalid CSS property is used in the animation', () => {
it('should provide a warning if an invalid CSS property is used in the animation', () => {
const steps = [animate(1000, style({abc: '500px'}))];

expect(() => {
validateAndThrowAnimationSequence(steps);
})
.toThrowError(
/The provided animation property "abc" is not a supported CSS property for animations/);
expect(getValidationWarningsForAnimationSequence(steps)).toEqual([
'The provided CSS properties are not recognized properties supported for animations: abc'
]);
});

it('should provide a warning if multiple invalid CSS properties are used in the animation',
() => {
const steps = [
state('state', style({
'123': '100px',
})),
style({abc: '200px'}), animate(1000, style({xyz: '300px'}))
];

expect(getValidationWarningsForAnimationSequence(steps)).toEqual([
'The provided CSS properties are not recognized properties supported for animations: 123, abc, xyz'
]);
});

it('should allow a vendor-prefixed property to be used in an animation sequence without throwing an error',
() => {
const steps = [
Expand Down Expand Up @@ -1116,12 +1128,20 @@ function invokeAnimationSequence(
function validateAndThrowAnimationSequence(steps: AnimationMetadata|AnimationMetadata[]) {
const driver = new MockAnimationDriver();
const errors: Error[] = [];
const ast = buildAnimationAst(driver, steps, errors);
const ast = buildAnimationAst(driver, steps, errors, []);
if (errors.length) {
throw new Error(errors.join('\n'));
}
}

function getValidationWarningsForAnimationSequence(steps: AnimationMetadata|
AnimationMetadata[]): string[] {
const driver = new MockAnimationDriver();
const warnings: string[] = [];
buildAnimationAst(driver, steps, [], warnings);
return warnings;
}

function buildParams(params: {[name: string]: any}): AnimationOptions {
return <AnimationOptions>{params};
}
Original file line number Diff line number Diff line change
Expand Up @@ -735,9 +735,11 @@ function registerTrigger(
element: any, engine: TransitionAnimationEngine, metadata: AnimationTriggerMetadata,
id: string = DEFAULT_NAMESPACE_ID) {
const errors: Error[] = [];
const warnings: string[] = [];
const driver = new MockAnimationDriver();
const name = metadata.name;
const ast = buildAnimationAst(driver, metadata as AnimationMetadata, errors) as TriggerAst;
const ast =
buildAnimationAst(driver, metadata as AnimationMetadata, errors, warnings) as TriggerAst;
if (errors.length) {
}
const trigger = buildTrigger(name, ast, new NoopAnimationStyleNormalizer());
Expand Down
7 changes: 6 additions & 1 deletion packages/animations/browser/test/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,21 @@ import {buildAnimationAst} from '../src/dsl/animation_ast_builder';
import {AnimationTrigger, buildTrigger} from '../src/dsl/animation_trigger';
import {NoopAnimationStyleNormalizer} from '../src/dsl/style_normalization/animation_style_normalizer';
import {triggerParsingFailed} from '../src/error_helpers';
import {triggerParsingWarnings} from '../src/warning_helpers';
import {MockAnimationDriver} from '../testing/src/mock_animation_driver';

export function makeTrigger(
name: string, steps: any, skipErrors: boolean = false): AnimationTrigger {
const driver = new MockAnimationDriver();
const errors: Error[] = [];
const warnings: string[] = [];
const triggerData = trigger(name, steps);
const triggerAst = buildAnimationAst(driver, triggerData, errors) as TriggerAst;
const triggerAst = buildAnimationAst(driver, triggerData, errors, warnings) as TriggerAst;
if (!skipErrors && errors.length) {
throw triggerParsingFailed(name, errors);
}
if (warnings.length) {
triggerParsingWarnings(name, warnings);
}
return buildTrigger(name, triggerAst, new NoopAnimationStyleNormalizer());
}