refactor(animations): collect parser / lookup errors in the same place
This commit is contained in:
		
							parent
							
								
									6d02d2f107
								
							
						
					
					
						commit
						79eda30f0f
					
				| @ -15,6 +15,7 @@ import {BaseException} from '../facade/exceptions'; | |||||||
| import {isArray, isBlank, isPresent} from '../facade/lang'; | import {isArray, isBlank, isPresent} from '../facade/lang'; | ||||||
| import {Identifiers} from '../identifiers'; | import {Identifiers} from '../identifiers'; | ||||||
| import * as o from '../output/output_ast'; | import * as o from '../output/output_ast'; | ||||||
|  | import {PropertyBindingType, TemplateAst, TemplateAstVisitor, NgContentAst, EmbeddedTemplateAst, ElementAst, ReferenceAst, VariableAst, BoundEventAst, BoundElementPropertyAst, AttrAst, BoundTextAst, TextAst, DirectiveAst, BoundDirectivePropertyAst, templateVisitAll,} from '../template_ast'; | ||||||
| 
 | 
 | ||||||
| import {AnimationAst, AnimationAstVisitor, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateAst, AnimationStateDeclarationAst, AnimationStateTransitionAst, AnimationStepAst, AnimationStylesAst} from './animation_ast'; | import {AnimationAst, AnimationAstVisitor, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateAst, AnimationStateDeclarationAst, AnimationStateTransitionAst, AnimationStepAst, AnimationStylesAst} from './animation_ast'; | ||||||
| import {AnimationParseError, ParsedAnimationResult, parseAnimationEntry} from './animation_parser'; | import {AnimationParseError, ParsedAnimationResult, parseAnimationEntry} from './animation_parser'; | ||||||
| @ -27,19 +28,21 @@ export class CompiledAnimation { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export class AnimationCompiler { | export class AnimationCompiler { | ||||||
|   compileComponent(component: CompileDirectiveMetadata): CompiledAnimation[] { |   compileComponent(component: CompileDirectiveMetadata, template: TemplateAst[]): | ||||||
|  |       CompiledAnimation[] { | ||||||
|     var compiledAnimations: CompiledAnimation[] = []; |     var compiledAnimations: CompiledAnimation[] = []; | ||||||
|     var index = 0; |     var index = 0; | ||||||
|  |     var groupedErrors: string[] = []; | ||||||
|  | 
 | ||||||
|     component.template.animations.forEach(entry => { |     component.template.animations.forEach(entry => { | ||||||
|       var result = parseAnimationEntry(entry); |       var result = parseAnimationEntry(entry); | ||||||
|       if (result.errors.length > 0) { |       if (result.errors.length > 0) { | ||||||
|         var errorMessage = ''; |         var errorMessage = | ||||||
|  |             `Unable to parse the animation sequence for "${entry.name}" due to the following errors:`; | ||||||
|         result.errors.forEach( |         result.errors.forEach( | ||||||
|             (error: AnimationParseError) => { errorMessage += '\n- ' + error.msg; }); |             (error: AnimationParseError) => { errorMessage += '\n-- ' + error.msg; }); | ||||||
|         // todo (matsko): include the component name when throwing
 |         // todo (matsko): include the component name when throwing
 | ||||||
|         throw new BaseException( |         groupedErrors.push(errorMessage); | ||||||
|             `Unable to parse the animation sequence for "${entry.name}" due to the following errors: ` + |  | ||||||
|             errorMessage); |  | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       var factoryName = `${component.type.name}_${entry.name}_${index}`; |       var factoryName = `${component.type.name}_${entry.name}_${index}`; | ||||||
| @ -48,6 +51,18 @@ export class AnimationCompiler { | |||||||
|       var visitor = new _AnimationBuilder(entry.name, factoryName); |       var visitor = new _AnimationBuilder(entry.name, factoryName); | ||||||
|       compiledAnimations.push(visitor.build(result.ast)); |       compiledAnimations.push(visitor.build(result.ast)); | ||||||
|     }); |     }); | ||||||
|  | 
 | ||||||
|  |     _validateAnimationProperties(compiledAnimations, template).forEach(entry => { | ||||||
|  |       groupedErrors.push(entry.msg); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     if (groupedErrors.length > 0) { | ||||||
|  |       var errorMessageStr = | ||||||
|  |           `Animation parsing for ${component.type.name} has failed due to the following errors:`; | ||||||
|  |       groupedErrors.forEach(error => errorMessageStr += `\n- ${error}`); | ||||||
|  |       throw new BaseException(errorMessageStr); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     return compiledAnimations; |     return compiledAnimations; | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @ -360,3 +375,49 @@ function _isEndStateAnimateStep(step: AnimationAst): boolean { | |||||||
| function _getStylesArray(obj: any): {[key: string]: any}[] { | function _getStylesArray(obj: any): {[key: string]: any}[] { | ||||||
|   return obj.styles.styles; |   return obj.styles.styles; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | function _validateAnimationProperties( | ||||||
|  |     compiledAnimations: CompiledAnimation[], template: TemplateAst[]): AnimationParseError[] { | ||||||
|  |   var visitor = new _AnimationTemplatePropertyVisitor(compiledAnimations); | ||||||
|  |   templateVisitAll(visitor, template); | ||||||
|  |   return visitor.errors; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class _AnimationTemplatePropertyVisitor implements TemplateAstVisitor { | ||||||
|  |   private _nodeIndex: number = 0; | ||||||
|  |   private _animationRegistry: {[key: string]: boolean} = {}; | ||||||
|  | 
 | ||||||
|  |   public errors: AnimationParseError[] = []; | ||||||
|  | 
 | ||||||
|  |   constructor(animations: CompiledAnimation[]) { | ||||||
|  |     animations.forEach(entry => { this._animationRegistry[entry.name] = true; }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   visitElement(ast: ElementAst, ctx: any): any { | ||||||
|  |     ast.inputs.forEach(input => { | ||||||
|  |       if (input.type == PropertyBindingType.Animation) { | ||||||
|  |         var animationName = input.name; | ||||||
|  |         if (!isPresent(this._animationRegistry[animationName])) { | ||||||
|  |           this.errors.push( | ||||||
|  |               new AnimationParseError(`couldn't find an animation entry for ${animationName}`)); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |     templateVisitAll(this, ast.children); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   visitBoundText(ast: BoundTextAst, ctx: any): any { this._nodeIndex++; } | ||||||
|  | 
 | ||||||
|  |   visitText(ast: TextAst, ctx: any): any { this._nodeIndex++; } | ||||||
|  | 
 | ||||||
|  |   visitEmbeddedTemplate(ast: EmbeddedTemplateAst, ctx: any): any { this._nodeIndex++; } | ||||||
|  | 
 | ||||||
|  |   visitNgContent(ast: NgContentAst, ctx: any): any {} | ||||||
|  |   visitAttr(ast: AttrAst, ctx: any): any {} | ||||||
|  |   visitDirective(ast: DirectiveAst, ctx: any): any {} | ||||||
|  |   visitEvent(ast: BoundEventAst, ctx: any): any {} | ||||||
|  |   visitReference(ast: ReferenceAst, ctx: any): any {} | ||||||
|  |   visitVariable(ast: VariableAst, ctx: any): any {} | ||||||
|  |   visitDirectiveProperty(ast: BoundDirectivePropertyAst, ctx: any): any {} | ||||||
|  |   visitElementProperty(ast: BoundElementPropertyAst, ctx: any): any {} | ||||||
|  | } | ||||||
|  | |||||||
| @ -6,6 +6,8 @@ | |||||||
|  * found in the LICENSE file at https://angular.io/license
 |  * found in the LICENSE file at https://angular.io/license
 | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
|  | import {BaseException, SecurityContext} from '@angular/core'; | ||||||
|  | 
 | ||||||
| import {EMPTY_STATE as EMPTY_ANIMATION_STATE, LifecycleHooks, isDefaultChangeDetectionStrategy} from '../../core_private'; | import {EMPTY_STATE as EMPTY_ANIMATION_STATE, LifecycleHooks, isDefaultChangeDetectionStrategy} from '../../core_private'; | ||||||
| import * as cdAst from '../expression_parser/ast'; | import * as cdAst from '../expression_parser/ast'; | ||||||
| import {isBlank, isPresent} from '../facade/lang'; | import {isBlank, isPresent} from '../facade/lang'; | ||||||
| @ -136,10 +138,6 @@ function bindAndWriteToRenderer( | |||||||
|       case PropertyBindingType.Animation: |       case PropertyBindingType.Animation: | ||||||
|         var animationName = boundProp.name; |         var animationName = boundProp.name; | ||||||
|         var animation = view.componentView.animations.get(animationName); |         var animation = view.componentView.animations.get(animationName); | ||||||
|         if (!isPresent(animation)) { |  | ||||||
|           throw new BaseException( |  | ||||||
|               `Internal Error: couldn't find an animation entry for ${boundProp.name}`); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         // it's important to normalize the void value as `void` explicitly
 |         // it's important to normalize the void value as `void` explicitly
 | ||||||
|         // so that the styles data can be obtained from the stringmap
 |         // so that the styles data can be obtained from the stringmap
 | ||||||
|  | |||||||
| @ -275,7 +275,7 @@ class ViewBuilderVisitor implements TemplateAstVisitor { | |||||||
|         ast.hasViewContainer, true, ast.references); |         ast.hasViewContainer, true, ast.references); | ||||||
|     this.view.nodes.push(compileElement); |     this.view.nodes.push(compileElement); | ||||||
| 
 | 
 | ||||||
|     var compiledAnimations = this._animationCompiler.compileComponent(this.view.component); |     var compiledAnimations = this._animationCompiler.compileComponent(this.view.component, [ast]); | ||||||
| 
 | 
 | ||||||
|     this.nestedViewCount++; |     this.nestedViewCount++; | ||||||
|     var embeddedView = new CompileView( |     var embeddedView = new CompileView( | ||||||
|  | |||||||
| @ -36,7 +36,7 @@ export class ViewCompiler { | |||||||
|       component: CompileDirectiveMetadata, template: TemplateAst[], styles: o.Expression, |       component: CompileDirectiveMetadata, template: TemplateAst[], styles: o.Expression, | ||||||
|       pipes: CompilePipeMetadata[]): ViewCompileResult { |       pipes: CompilePipeMetadata[]): ViewCompileResult { | ||||||
|     var dependencies: Array<ViewFactoryDependency|ComponentFactoryDependency> = []; |     var dependencies: Array<ViewFactoryDependency|ComponentFactoryDependency> = []; | ||||||
|     var compiledAnimations = this._animationCompiler.compileComponent(component); |     var compiledAnimations = this._animationCompiler.compileComponent(component, template); | ||||||
|     var statements: o.Statement[] = []; |     var statements: o.Statement[] = []; | ||||||
|     compiledAnimations.map(entry => { |     compiledAnimations.map(entry => { | ||||||
|       statements.push(entry.statesMapStatement); |       statements.push(entry.statesMapStatement); | ||||||
|  | |||||||
| @ -22,7 +22,7 @@ export function main() { | |||||||
|     var compiler = new AnimationCompiler(); |     var compiler = new AnimationCompiler(); | ||||||
| 
 | 
 | ||||||
|     var compileAnimations = (component: CompileDirectiveMetadata): CompiledAnimation => { |     var compileAnimations = (component: CompileDirectiveMetadata): CompiledAnimation => { | ||||||
|       return compiler.compileComponent(component)[0]; |       return compiler.compileComponent(component, [])[0]; | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     var compile = (seq: AnimationMetadata) => { |     var compile = (seq: AnimationMetadata) => { | ||||||
|  | |||||||
| @ -13,6 +13,7 @@ import {AnimationDriver} from '@angular/platform-browser/src/dom/animation_drive | |||||||
| import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; | import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; | ||||||
| import {MockAnimationDriver} from '@angular/platform-browser/testing/mock_animation_driver'; | import {MockAnimationDriver} from '@angular/platform-browser/testing/mock_animation_driver'; | ||||||
| 
 | 
 | ||||||
|  | import {BaseException} from '../../../compiler/src/facade/exceptions'; | ||||||
| import {Component} from '../../index'; | import {Component} from '../../index'; | ||||||
| import {DEFAULT_STATE} from '../../src/animation/animation_constants'; | import {DEFAULT_STATE} from '../../src/animation/animation_constants'; | ||||||
| import {AnimationKeyframe} from '../../src/animation/animation_keyframe'; | import {AnimationKeyframe} from '../../src/animation/animation_keyframe'; | ||||||
| @ -44,12 +45,15 @@ function declareTests({useJit}: {useJit: boolean}) { | |||||||
|     var makeAnimationCmp = |     var makeAnimationCmp = | ||||||
|         (tcb: TestComponentBuilder, tpl: string, |         (tcb: TestComponentBuilder, tpl: string, | ||||||
|          animationEntry: AnimationEntryMetadata | AnimationEntryMetadata[], |          animationEntry: AnimationEntryMetadata | AnimationEntryMetadata[], | ||||||
|          callback: any /** TODO #9100 */ = null) => { |          callback: Function = null, failure: Function = null) => { | ||||||
|           var entries = isArray(animationEntry) ? <AnimationEntryMetadata[]>animationEntry : |           var entries = isArray(animationEntry) ? <AnimationEntryMetadata[]>animationEntry : | ||||||
|                                                   [<AnimationEntryMetadata>animationEntry]; |                                                   [<AnimationEntryMetadata>animationEntry]; | ||||||
|           tcb = tcb.overrideTemplate(DummyIfCmp, tpl); |           tcb = tcb.overrideTemplate(DummyIfCmp, tpl); | ||||||
|           tcb = tcb.overrideAnimations(DummyIfCmp, entries); |           tcb = tcb.overrideAnimations(DummyIfCmp, entries); | ||||||
|           tcb.createAsync(DummyIfCmp).then((root) => { callback(root); }); |           var promise = tcb.createAsync(DummyIfCmp).then((root) => { callback(root); }); | ||||||
|  |           if (isPresent(failure)) { | ||||||
|  |             promise.catch(<any>failure); | ||||||
|  |           } | ||||||
|           tick(); |           tick(); | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
| @ -878,6 +882,24 @@ function declareTests({useJit}: {useJit: boolean}) { | |||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     describe('animation states', () => { |     describe('animation states', () => { | ||||||
|  |       it('should throw an error when an animation is referenced that isn\'t defined within the component annotation', | ||||||
|  |          inject( | ||||||
|  |              [TestComponentBuilder, AnimationDriver], | ||||||
|  |              fakeAsync((tcb: TestComponentBuilder, driver: MockAnimationDriver) => { | ||||||
|  |                makeAnimationCmp( | ||||||
|  |                    tcb, '<div class="target" [@status]="exp"></div>', [], | ||||||
|  |                    () => { | ||||||
|  |                      throw new BaseException( | ||||||
|  |                          'Error: expected animations for DummyIfCmp to throw an error within this spec'); | ||||||
|  |                    }, | ||||||
|  |                    (e: any) => { | ||||||
|  |                      var message = e.message; | ||||||
|  |                      expect(message).toMatch( | ||||||
|  |                          /Animation parsing for DummyIfCmp has failed due to the following errors:/); | ||||||
|  |                      expect(message).toMatch(/- couldn't find an animation entry for status/); | ||||||
|  |                    }); | ||||||
|  |              }))); | ||||||
|  | 
 | ||||||
|       it('should retain the destination animation state styles once the animation is complete', |       it('should retain the destination animation state styles once the animation is complete', | ||||||
|          inject( |          inject( | ||||||
|              [TestComponentBuilder, AnimationDriver], |              [TestComponentBuilder, AnimationDriver], | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user