Skip to content

Commit

Permalink
fix(animations): always normalize style properties and values during …
Browse files Browse the repository at this point in the history
…compilation (#12755)

Closes #11582
Closes #12481
Closes #12755
  • Loading branch information
matsko authored and vikerman committed Nov 8, 2016
1 parent 3dc6177 commit a0e9fde
Show file tree
Hide file tree
Showing 16 changed files with 448 additions and 271 deletions.
5 changes: 3 additions & 2 deletions modules/@angular/compiler-cli/src/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ export class CodeGenerator {
new compiler.DirectiveWrapperCompiler(
config, expressionParser, elementSchemaRegistry, console),
new compiler.NgModuleCompiler(), new compiler.TypeScriptEmitter(reflectorHost),
cliOptions.locale, cliOptions.i18nFormat);
cliOptions.locale, cliOptions.i18nFormat,
new compiler.AnimationParser(elementSchemaRegistry));

return new CodeGenerator(
options, program, compilerHost, staticReflector, offlineCompiler, reflectorHost);
Expand Down Expand Up @@ -181,4 +182,4 @@ export function extractProgramSymbols(
});

return staticSymbols;
}
}
1 change: 1 addition & 0 deletions modules/@angular/compiler/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,6 @@ export * from './src/selector';
export * from './src/style_compiler';
export * from './src/template_parser/template_parser';
export {ViewCompiler} from './src/view_compiler/view_compiler';
export {AnimationParser} from './src/animation/animation_parser';

// This file only reexports content of the `src` folder. Keep it that way.
74 changes: 45 additions & 29 deletions modules/@angular/compiler/src/animation/animation_parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Injectable} from '@angular/core';

import {CompileAnimationAnimateMetadata, CompileAnimationEntryMetadata, CompileAnimationGroupMetadata, CompileAnimationKeyframesSequenceMetadata, CompileAnimationMetadata, CompileAnimationSequenceMetadata, CompileAnimationStateDeclarationMetadata, CompileAnimationStateTransitionMetadata, CompileAnimationStyleMetadata, CompileAnimationWithStepsMetadata, CompileDirectiveMetadata} from '../compile_metadata';
import {StringMapWrapper} from '../facade/collection';
import {isBlank, isPresent} from '../facade/lang';
import {ParseError} from '../parse_util';
import {ANY_STATE, FILL_STYLE_FLAG} from '../private_import_core';
import {ElementSchemaRegistry} from '../schema/element_schema_registry';

import {AnimationAst, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateDeclarationAst, AnimationStateTransitionAst, AnimationStateTransitionExpression, AnimationStepAst, AnimationStylesAst, AnimationWithStepsAst} from './animation_ast';
import {StylesCollection} from './styles_collection';
Expand All @@ -32,7 +35,10 @@ export class AnimationEntryParseResult {
constructor(public ast: AnimationEntryAst, public errors: AnimationParseError[]) {}
}

@Injectable()
export class AnimationParser {
constructor(private _schema: ElementSchemaRegistry) {}

parseComponent(component: CompileDirectiveMetadata): AnimationEntryAst[] {
const errors: string[] = [];
const componentName = component.type.name;
Expand Down Expand Up @@ -73,7 +79,7 @@ export class AnimationParser {
var stateDeclarationAsts: AnimationStateDeclarationAst[] = [];
entry.definitions.forEach(def => {
if (def instanceof CompileAnimationStateDeclarationMetadata) {
_parseAnimationDeclarationStates(def, errors).forEach(ast => {
_parseAnimationDeclarationStates(def, this._schema, errors).forEach(ast => {
stateDeclarationAsts.push(ast);
stateStyles[ast.stateName] = ast.styles;
});
Expand All @@ -82,44 +88,34 @@ export class AnimationParser {
}
});

var stateTransitionAsts =
transitions.map(transDef => _parseAnimationStateTransition(transDef, stateStyles, errors));
var stateTransitionAsts = transitions.map(
transDef => _parseAnimationStateTransition(transDef, stateStyles, this._schema, errors));

var ast = new AnimationEntryAst(entry.name, stateDeclarationAsts, stateTransitionAsts);
return new AnimationEntryParseResult(ast, errors);
}
}

function _parseAnimationDeclarationStates(
stateMetadata: CompileAnimationStateDeclarationMetadata,
stateMetadata: CompileAnimationStateDeclarationMetadata, schema: ElementSchemaRegistry,
errors: AnimationParseError[]): AnimationStateDeclarationAst[] {
var styleValues: Styles[] = [];
stateMetadata.styles.styles.forEach(stylesEntry => {
// TODO (matsko): change this when we get CSS class integration support
if (typeof stylesEntry === 'object' && stylesEntry !== null) {
styleValues.push(stylesEntry as Styles);
} else {
errors.push(new AnimationParseError(
`State based animations cannot contain references to other states`));
}
});
var defStyles = new AnimationStylesAst(styleValues);

var normalizedStyles = _normalizeStyleMetadata(stateMetadata.styles, {}, schema, errors, false);
var defStyles = new AnimationStylesAst(normalizedStyles);
var states = stateMetadata.stateNameExpr.split(/\s*,\s*/);
return states.map(state => new AnimationStateDeclarationAst(state, defStyles));
}

function _parseAnimationStateTransition(
transitionStateMetadata: CompileAnimationStateTransitionMetadata,
stateStyles: {[key: string]: AnimationStylesAst},
stateStyles: {[key: string]: AnimationStylesAst}, schema: ElementSchemaRegistry,
errors: AnimationParseError[]): AnimationStateTransitionAst {
var styles = new StylesCollection();
var transitionExprs: AnimationStateTransitionExpression[] = [];
var transitionStates = transitionStateMetadata.stateChangeExpr.split(/\s*,\s*/);
transitionStates.forEach(
expr => { transitionExprs.push(..._parseAnimationTransitionExpr(expr, errors)); });
var entry = _normalizeAnimationEntry(transitionStateMetadata.steps);
var animation = _normalizeStyleSteps(entry, stateStyles, errors);
var animation = _normalizeStyleSteps(entry, stateStyles, schema, errors);
var animationAst = _parseTransitionAnimation(animation, 0, styles, stateStyles, errors);
if (errors.length == 0) {
_fillAnimationAstStartingKeyframes(animationAst, styles, errors);
Expand Down Expand Up @@ -176,22 +172,40 @@ function _normalizeAnimationEntry(entry: CompileAnimationMetadata | CompileAnima

function _normalizeStyleMetadata(
entry: CompileAnimationStyleMetadata, stateStyles: {[key: string]: AnimationStylesAst},
errors: AnimationParseError[]): {[key: string]: string | number}[] {
schema: ElementSchemaRegistry, errors: AnimationParseError[],
permitStateReferences: boolean): {[key: string]: string | number}[] {
var normalizedStyles: {[key: string]: string | number}[] = [];
entry.styles.forEach(styleEntry => {
if (typeof styleEntry === 'string') {
normalizedStyles.push(..._resolveStylesFromState(<string>styleEntry, stateStyles, errors));
if (permitStateReferences) {
normalizedStyles.push(..._resolveStylesFromState(<string>styleEntry, stateStyles, errors));
} else {
errors.push(new AnimationParseError(
`State based animations cannot contain references to other states`));
}
} else {
normalizedStyles.push(<{[key: string]: string | number}>styleEntry);
var stylesObj = <Styles>styleEntry;
var normalizedStylesObj: Styles = {};
Object.keys(stylesObj).forEach(propName => {
var normalizedProp = schema.normalizeAnimationStyleProperty(propName);
var normalizedOutput =
schema.normalizeAnimationStyleValue(normalizedProp, propName, stylesObj[propName]);
var normalizationError = normalizedOutput['error'];
if (normalizationError) {
errors.push(new AnimationParseError(normalizationError));
}
normalizedStylesObj[normalizedProp] = normalizedOutput['value'];
});
normalizedStyles.push(normalizedStylesObj);
}
});
return normalizedStyles;
}

function _normalizeStyleSteps(
entry: CompileAnimationMetadata, stateStyles: {[key: string]: AnimationStylesAst},
errors: AnimationParseError[]): CompileAnimationMetadata {
var steps = _normalizeStyleStepEntry(entry, stateStyles, errors);
schema: ElementSchemaRegistry, errors: AnimationParseError[]): CompileAnimationMetadata {
var steps = _normalizeStyleStepEntry(entry, stateStyles, schema, errors);
return (entry instanceof CompileAnimationGroupMetadata) ?
new CompileAnimationGroupMetadata(steps) :
new CompileAnimationSequenceMetadata(steps);
Expand All @@ -213,7 +227,7 @@ function _mergeAnimationStyles(

function _normalizeStyleStepEntry(
entry: CompileAnimationMetadata, stateStyles: {[key: string]: AnimationStylesAst},
errors: AnimationParseError[]): CompileAnimationMetadata[] {
schema: ElementSchemaRegistry, errors: AnimationParseError[]): CompileAnimationMetadata[] {
var steps: CompileAnimationMetadata[];
if (entry instanceof CompileAnimationWithStepsMetadata) {
steps = entry.steps;
Expand All @@ -232,7 +246,8 @@ function _normalizeStyleStepEntry(
if (!isPresent(combinedStyles)) {
combinedStyles = [];
}
_normalizeStyleMetadata(<CompileAnimationStyleMetadata>step, stateStyles, errors)
_normalizeStyleMetadata(
<CompileAnimationStyleMetadata>step, stateStyles, schema, errors, true)
.forEach(entry => { _mergeAnimationStyles(combinedStyles, entry); });
} else {
// it is important that we create a metadata entry of the combined styles
Expand All @@ -250,13 +265,14 @@ function _normalizeStyleStepEntry(
var animateStyleValue = (<CompileAnimationAnimateMetadata>step).styles;
if (animateStyleValue instanceof CompileAnimationStyleMetadata) {
animateStyleValue.styles =
_normalizeStyleMetadata(animateStyleValue, stateStyles, errors);
_normalizeStyleMetadata(animateStyleValue, stateStyles, schema, errors, true);
} else if (animateStyleValue instanceof CompileAnimationKeyframesSequenceMetadata) {
animateStyleValue.steps.forEach(
step => { step.styles = _normalizeStyleMetadata(step, stateStyles, errors); });
animateStyleValue.steps.forEach(step => {
step.styles = _normalizeStyleMetadata(step, stateStyles, schema, errors, true);
});
}
} else if (step instanceof CompileAnimationWithStepsMetadata) {
let innerSteps = _normalizeStyleStepEntry(step, stateStyles, errors);
let innerSteps = _normalizeStyleStepEntry(step, stateStyles, schema, errors);
step = step instanceof CompileAnimationGroupMetadata ?
new CompileAnimationGroupMetadata(innerSteps) :
new CompileAnimationSequenceMetadata(innerSteps);
Expand Down
4 changes: 3 additions & 1 deletion modules/@angular/compiler/src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, Injectable, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core';

import {AnimationParser} from './animation/animation_parser';
import {CompilerConfig} from './config';
import {DirectiveNormalizer} from './directive_normalizer';
import {DirectiveResolver} from './directive_resolver';
Expand Down Expand Up @@ -74,7 +75,8 @@ export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> =
UrlResolver,
DirectiveResolver,
PipeResolver,
NgModuleResolver
NgModuleResolver,
AnimationParser
];


Expand Down
4 changes: 2 additions & 2 deletions modules/@angular/compiler/src/offline_compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ export function analyzeNgModules(
}

export class OfflineCompiler {
private _animationParser = new AnimationParser();
private _animationCompiler = new AnimationCompiler();

constructor(
Expand All @@ -118,7 +117,8 @@ export class OfflineCompiler {
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
private _dirWrapperCompiler: DirectiveWrapperCompiler,
private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter,
private _localeId: string, private _translationFormat: string) {}
private _localeId: string, private _translationFormat: string,
private _animationParser: AnimationParser) {}

clearCache() {
this._directiveNormalizer.clearCache();
Expand Down
3 changes: 1 addition & 2 deletions modules/@angular/compiler/src/runtime_compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export class RuntimeCompiler implements Compiler {
private _compiledHostTemplateCache = new Map<Type<any>, CompiledTemplate>();
private _compiledDirectiveWrapperCache = new Map<Type<any>, Type<any>>();
private _compiledNgModuleCache = new Map<Type<any>, NgModuleFactory<any>>();
private _animationParser = new AnimationParser();
private _animationCompiler = new AnimationCompiler();

constructor(
Expand All @@ -52,7 +51,7 @@ export class RuntimeCompiler implements Compiler {
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
private _ngModuleCompiler: NgModuleCompiler,
private _directiveWrapperCompiler: DirectiveWrapperCompiler,
private _compilerConfig: CompilerConfig) {}
private _compilerConfig: CompilerConfig, private _animationParser: AnimationParser) {}

get injector(): Injector { return this._injector; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/

import {CUSTOM_ELEMENTS_SCHEMA, Injectable, NO_ERRORS_SCHEMA, SchemaMetadata, SecurityContext} from '@angular/core';
import {AUTO_STYLE, CUSTOM_ELEMENTS_SCHEMA, Injectable, NO_ERRORS_SCHEMA, SchemaMetadata, SecurityContext} from '@angular/core';

import {dashCaseToCamelCase} from '../util';

import {SECURITY_SCHEMA} from './dom_security_schema';
import {ElementSchemaRegistry} from './element_schema_registry';
Expand Down Expand Up @@ -373,4 +375,64 @@ export class DomElementSchemaRegistry extends ElementSchemaRegistry {
}

allKnownElementNames(): string[] { return Object.keys(this._schema); }

normalizeAnimationStyleProperty(propName: string): string {
return dashCaseToCamelCase(propName);
}

normalizeAnimationStyleValue(camelCaseProp: string, userProvidedProp: string, val: string|number):
{error: string, value: string} {
var unit: string = '';
var strVal = val.toString().trim();
var errorMsg: string = null;

if (_isPixelDimensionStyle(camelCaseProp) && val !== 0 && val !== '0') {
if (typeof val === 'number') {
unit = 'px';
} else {
let valAndSuffixMatch = val.match(/^[+-]?[\d\.]+([a-z]*)$/);
if (valAndSuffixMatch && valAndSuffixMatch[1].length == 0) {
errorMsg = `Please provide a CSS unit value for ${userProvidedProp}:${val}`;
}
}
}
return {error: errorMsg, value: strVal + unit};
}
}

function _isPixelDimensionStyle(prop: string): boolean {
switch (prop) {
case 'width':
case 'height':
case 'minWidth':
case 'minHeight':
case 'maxWidth':
case 'maxHeight':
case 'left':
case 'top':
case 'bottom':
case 'right':
case 'fontSize':
case 'outlineWidth':
case 'outlineOffset':
case 'paddingTop':
case 'paddingLeft':
case 'paddingBottom':
case 'paddingRight':
case 'marginTop':
case 'marginLeft':
case 'marginBottom':
case 'marginRight':
case 'borderRadius':
case 'borderWidth':
case 'borderTopWidth':
case 'borderLeftWidth':
case 'borderRightWidth':
case 'borderBottomWidth':
case 'textIndent':
return true;

default:
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ export abstract class ElementSchemaRegistry {
abstract getDefaultComponentElementName(): string;
abstract validateProperty(name: string): {error: boolean, msg?: string};
abstract validateAttribute(name: string): {error: boolean, msg?: string};
abstract normalizeAnimationStyleProperty(propName: string): string;
abstract normalizeAnimationStyleValue(
camelCaseProp: string, userProvidedProp: string,
val: string|number): {error: string, value: string};
}
5 changes: 5 additions & 0 deletions modules/@angular/compiler/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,16 @@ import {isBlank, isPrimitive, isStrictStringMap} from './facade/lang';
export const MODULE_SUFFIX = '';

const CAMEL_CASE_REGEXP = /([A-Z])/g;
const DASH_CASE_REGEXP = /-+([a-z0-9])/g;

export function camelCaseToDashCase(input: string): string {
return input.replace(CAMEL_CASE_REGEXP, (...m: any[]) => '-' + m[1].toLowerCase());
}

export function dashCaseToCamelCase(input: string): string {
return input.replace(DASH_CASE_REGEXP, (...m: any[]) => m[1].toUpperCase());
}

export function splitAtColon(input: string, defaultValues: string[]): string[] {
return _splitAt(input, ':', defaultValues);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,19 @@ import {AnimationCompiler, AnimationEntryCompileResult} from '../../src/animatio
import {AnimationParser} from '../../src/animation/animation_parser';
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileTemplateMetadata, CompileTypeMetadata} from '../../src/compile_metadata';
import {CompileMetadataResolver} from '../../src/metadata_resolver';
import {ElementSchemaRegistry} from '../../src/schema/element_schema_registry';

export function main() {
describe('RuntimeAnimationCompiler', () => {
var resolver: CompileMetadataResolver;
beforeEach(
inject([CompileMetadataResolver], (res: CompileMetadataResolver) => { resolver = res; }));
var parser: AnimationParser;
beforeEach(inject(
[CompileMetadataResolver, ElementSchemaRegistry],
(res: CompileMetadataResolver, schema: ElementSchemaRegistry) => {
resolver = res;
parser = new AnimationParser(schema);
}));

const parser = new AnimationParser();
const compiler = new AnimationCompiler();

var compileAnimations =
Expand Down
Loading

0 comments on commit a0e9fde

Please sign in to comment.