From 15b15be25916a30e6a4bbfe5e23892975afe62ee Mon Sep 17 00:00:00 2001 From: ivanwonder Date: Wed, 2 Dec 2020 11:14:07 +0800 Subject: [PATCH] fix(compiler): recover event parse when animation event name is empty (#39925) Now when the animation trigger output event is missing its phase value name, the `BoundEvent` will be ignored, but it's useful for completion in language service. PR Close #39925 --- .../src/template_parser/binding_parser.ts | 26 +++++++++---------- .../render3/r3_template_transform_spec.ts | 25 ++++++++++++++++-- packages/compiler/test/render3/view/util.ts | 10 ++++--- 3 files changed, 41 insertions(+), 20 deletions(-) diff --git a/packages/compiler/src/template_parser/binding_parser.ts b/packages/compiler/src/template_parser/binding_parser.ts index ea3636673a5dc..ec709ad94cd4d 100644 --- a/packages/compiler/src/template_parser/binding_parser.ts +++ b/packages/compiler/src/template_parser/binding_parser.ts @@ -443,21 +443,19 @@ export class BindingParser { const matches = splitAtPeriod(name, [name, '']); const eventName = matches[0]; const phase = matches[1].toLowerCase(); + const ast = this._parseAction(expression, handlerSpan); + targetEvents.push(new ParsedEvent( + eventName, phase, ParsedEventType.Animation, ast, sourceSpan, handlerSpan, keySpan)); + + if (eventName.length === 0) { + this._reportError(`Animation event name is missing in binding`, sourceSpan); + } if (phase) { - switch (phase) { - case 'start': - case 'done': - const ast = this._parseAction(expression, handlerSpan); - targetEvents.push(new ParsedEvent( - eventName, phase, ParsedEventType.Animation, ast, sourceSpan, handlerSpan, keySpan)); - break; - - default: - this._reportError( - `The provided animation output phase value "${phase}" for "@${ - eventName}" is not supported (use start or done)`, - sourceSpan); - break; + if (phase !== 'start' && phase !== 'done') { + this._reportError( + `The provided animation output phase value "${phase}" for "@${ + eventName}" is not supported (use start or done)`, + sourceSpan); } } else { this._reportError( diff --git a/packages/compiler/test/render3/r3_template_transform_spec.ts b/packages/compiler/test/render3/r3_template_transform_spec.ts index 8f74ce86eb3d8..c2594850730d8 100644 --- a/packages/compiler/test/render3/r3_template_transform_spec.ts +++ b/packages/compiler/test/render3/r3_template_transform_spec.ts @@ -92,8 +92,8 @@ class R3AstHumanizer implements t.Visitor { } } -function expectFromHtml(html: string) { - const res = parse(html); +function expectFromHtml(html: string, ignoreError = false) { + const res = parse(html, {ignoreError}); return expectFromR3Nodes(res.nodes); } @@ -399,6 +399,27 @@ describe('R3 template transform', () => { expect(() => parse('
')).toThrowError(/Empty expressions are not allowed/); expect(() => parse('
')).toThrowError(/Empty expressions are not allowed/); }); + + it('should parse bound animation events when event name is empty', () => { + expectFromHtml('
', true).toEqual([ + ['Element', 'div'], + ['BoundEvent', '', null, 'onAnimationEvent($event)'], + ]); + expect(() => parse('
')) + .toThrowError(/Animation event name is missing in binding/); + }); + + it('should report invalid phase value of animation event', () => { + expect(() => parse('
')) + .toThrowError( + /The provided animation output phase value "invalidphase" for "@event" is not supported \(use start or done\)/); + expect(() => parse('
')) + .toThrowError( + /The animation trigger output event \(@event\) is missing its phase value name \(start or done are currently supported\)/); + expect(() => parse('
')) + .toThrowError( + /The animation trigger output event \(@event\) is missing its phase value name \(start or done are currently supported\)/); + }); }); describe('variables', () => { diff --git a/packages/compiler/test/render3/view/util.ts b/packages/compiler/test/render3/view/util.ts index e974e0e155cfc..dd3f47125cd24 100644 --- a/packages/compiler/test/render3/view/util.ts +++ b/packages/compiler/test/render3/view/util.ts @@ -78,15 +78,17 @@ export function toStringExpression(expr: e.AST): string { // Parse an html string to IVY specific info export function parseR3( - input: string, options: {preserveWhitespaces?: boolean, leadingTriviaChars?: string[]} = {}): - Render3ParseResult { + input: string, + options: {preserveWhitespaces?: boolean, + leadingTriviaChars?: string[], + ignoreError?: boolean} = {}): Render3ParseResult { const htmlParser = new HtmlParser(); const parseResult = htmlParser.parse( input, 'path:://to/template', {tokenizeExpansionForms: true, leadingTriviaChars: options.leadingTriviaChars}); - if (parseResult.errors.length > 0) { + if (parseResult.errors.length > 0 && !options.ignoreError) { const msg = parseResult.errors.map(e => e.toString()).join('\n'); throw new Error(msg); } @@ -105,7 +107,7 @@ export function parseR3( new BindingParser(expressionParser, DEFAULT_INTERPOLATION_CONFIG, schemaRegistry, null, []); const r3Result = htmlAstToRender3Ast(htmlNodes, bindingParser); - if (r3Result.errors.length > 0) { + if (r3Result.errors.length > 0 && !options.ignoreError) { const msg = r3Result.errors.map(e => e.toString()).join('\n'); throw new Error(msg); }