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
This commit is contained in:
ivanwonder 2020-12-02 11:14:07 +08:00 committed by atscott
parent 57043721db
commit 15b15be259
3 changed files with 41 additions and 20 deletions

View File

@ -443,21 +443,19 @@ export class BindingParser {
const matches = splitAtPeriod(name, [name, '']);
const eventName = matches[0];
const phase = matches[1].toLowerCase();
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;
const ast = this._parseAction(expression, handlerSpan);
targetEvents.push(new ParsedEvent(
eventName, phase, ParsedEventType.Animation, ast, sourceSpan, handlerSpan, keySpan));
default:
this._reportError(
`The provided animation output phase value "${phase}" for "@${
eventName}" is not supported (use start or done)`,
sourceSpan);
break;
if (eventName.length === 0) {
this._reportError(`Animation event name is missing in binding`, sourceSpan);
}
if (phase) {
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(

View File

@ -92,8 +92,8 @@ class R3AstHumanizer implements t.Visitor<void> {
}
}
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('<div (event)="">')).toThrowError(/Empty expressions are not allowed/);
expect(() => parse('<div (event)=" ">')).toThrowError(/Empty expressions are not allowed/);
});
it('should parse bound animation events when event name is empty', () => {
expectFromHtml('<div (@)="onAnimationEvent($event)"></div>', true).toEqual([
['Element', 'div'],
['BoundEvent', '', null, 'onAnimationEvent($event)'],
]);
expect(() => parse('<div (@)></div>'))
.toThrowError(/Animation event name is missing in binding/);
});
it('should report invalid phase value of animation event', () => {
expect(() => parse('<div (@event.invalidPhase)></div>'))
.toThrowError(
/The provided animation output phase value "invalidphase" for "@event" is not supported \(use start or done\)/);
expect(() => parse('<div (@event.)></div>'))
.toThrowError(
/The animation trigger output event \(@event\) is missing its phase value name \(start or done are currently supported\)/);
expect(() => parse('<div (@event)></div>'))
.toThrowError(
/The animation trigger output event \(@event\) is missing its phase value name \(start or done are currently supported\)/);
});
});
describe('variables', () => {

View File

@ -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);
}