fix(compiler): ensure that event handlers have the correct source spans (#28055)

When template bindings are being parsed the event handlers
were receiving a source span that included the whole attribute.

Now they get a span that is focussed on the handler itself.

PR Close #28055
This commit is contained in:
Pete Bacon Darwin
2019-02-08 22:10:20 +00:00
committed by Misko Hevery
parent 497619f25d
commit cffd86260a
8 changed files with 58 additions and 32 deletions

View File

@ -83,7 +83,8 @@ export class BindingParser {
Object.keys(dirMeta.hostListeners).forEach(propName => {
const expression = dirMeta.hostListeners[propName];
if (typeof expression === 'string') {
this.parseEvent(propName, expression, sourceSpan, [], targetEvents);
// TODO: pass a more accurate handlerSpan for this event.
this.parseEvent(propName, expression, sourceSpan, sourceSpan, [], targetEvents);
} else {
this._reportError(
`Value of the host listener "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`,
@ -300,13 +301,14 @@ export class BindingParser {
}
parseEvent(
name: string, expression: string, sourceSpan: ParseSourceSpan,
name: string, expression: string, sourceSpan: ParseSourceSpan, handlerSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetEvents: ParsedEvent[]) {
if (isAnimationLabel(name)) {
name = name.substr(1);
this._parseAnimationEvent(name, expression, sourceSpan, targetEvents);
this._parseAnimationEvent(name, expression, sourceSpan, handlerSpan, targetEvents);
} else {
this._parseRegularEvent(name, expression, sourceSpan, targetMatchableAttrs, targetEvents);
this._parseRegularEvent(
name, expression, sourceSpan, handlerSpan, targetMatchableAttrs, targetEvents);
}
}
@ -317,7 +319,8 @@ export class BindingParser {
}
private _parseAnimationEvent(
name: string, expression: string, sourceSpan: ParseSourceSpan, targetEvents: ParsedEvent[]) {
name: string, expression: string, sourceSpan: ParseSourceSpan, handlerSpan: ParseSourceSpan,
targetEvents: ParsedEvent[]) {
const matches = splitAtPeriod(name, [name, '']);
const eventName = matches[0];
const phase = matches[1].toLowerCase();
@ -325,9 +328,9 @@ export class BindingParser {
switch (phase) {
case 'start':
case 'done':
const ast = this._parseAction(expression, sourceSpan);
targetEvents.push(
new ParsedEvent(eventName, phase, ParsedEventType.Animation, ast, sourceSpan));
const ast = this._parseAction(expression, handlerSpan);
targetEvents.push(new ParsedEvent(
eventName, phase, ParsedEventType.Animation, ast, sourceSpan, handlerSpan));
break;
default:
@ -344,13 +347,14 @@ export class BindingParser {
}
private _parseRegularEvent(
name: string, expression: string, sourceSpan: ParseSourceSpan,
name: string, expression: string, sourceSpan: ParseSourceSpan, handlerSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetEvents: ParsedEvent[]) {
// long format: 'target: eventName'
const [target, eventName] = splitAtColon(name, [null !, name]);
const ast = this._parseAction(expression, sourceSpan);
const ast = this._parseAction(expression, handlerSpan);
targetMatchableAttrs.push([name !, ast.source !]);
targetEvents.push(new ParsedEvent(eventName, target, ParsedEventType.Regular, ast, sourceSpan));
targetEvents.push(
new ParsedEvent(eventName, target, ParsedEventType.Regular, ast, sourceSpan, handlerSpan));
// Don't detect directives for event names for now,
// so don't add the event name to the matchableAttrs
}

View File

@ -114,7 +114,8 @@ export class BoundEventAst implements TemplateAst {
constructor(
public name: string, public target: string|null, public phase: string|null,
public handler: AST, public sourceSpan: ParseSourceSpan) {
public handler: AST, public sourceSpan: ParseSourceSpan,
public handlerSpan: ParseSourceSpan) {
this.fullName = BoundEventAst.calcFullName(this.name, this.target, this.phase);
this.isAnimation = !!this.phase;
}
@ -134,7 +135,8 @@ export class BoundEventAst implements TemplateAst {
const target: string|null = event.type === ParsedEventType.Regular ? event.targetOrPhase : null;
const phase: string|null =
event.type === ParsedEventType.Animation ? event.targetOrPhase : null;
return new BoundEventAst(event.name, target, phase, event.handler, event.sourceSpan);
return new BoundEventAst(
event.name, target, phase, event.handler, event.sourceSpan, event.handlerSpan);
}
visit(visitor: TemplateAstVisitor, context: any): any {

View File

@ -441,13 +441,15 @@ class TemplateParseVisitor implements html.Visitor {
} else if (bindParts[KW_ON_IDX]) {
this._bindingParser.parseEvent(
bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, boundEvents);
bindParts[IDENT_KW_IDX], value, srcSpan, attr.valueSpan || srcSpan,
targetMatchableAttrs, boundEvents);
} else if (bindParts[KW_BINDON_IDX]) {
this._bindingParser.parsePropertyBinding(
bindParts[IDENT_KW_IDX], value, false, srcSpan, targetMatchableAttrs, targetProps);
this._parseAssignmentEvent(
bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, boundEvents);
bindParts[IDENT_KW_IDX], value, srcSpan, attr.valueSpan || srcSpan,
targetMatchableAttrs, boundEvents);
} else if (bindParts[KW_AT_IDX]) {
this._bindingParser.parseLiteralAttr(
@ -458,7 +460,8 @@ class TemplateParseVisitor implements html.Visitor {
bindParts[IDENT_BANANA_BOX_IDX], value, false, srcSpan, targetMatchableAttrs,
targetProps);
this._parseAssignmentEvent(
bindParts[IDENT_BANANA_BOX_IDX], value, srcSpan, targetMatchableAttrs, boundEvents);
bindParts[IDENT_BANANA_BOX_IDX], value, srcSpan, attr.valueSpan || srcSpan,
targetMatchableAttrs, boundEvents);
} else if (bindParts[IDENT_PROPERTY_IDX]) {
this._bindingParser.parsePropertyBinding(
@ -467,7 +470,8 @@ class TemplateParseVisitor implements html.Visitor {
} else if (bindParts[IDENT_EVENT_IDX]) {
this._bindingParser.parseEvent(
bindParts[IDENT_EVENT_IDX], value, srcSpan, targetMatchableAttrs, boundEvents);
bindParts[IDENT_EVENT_IDX], value, srcSpan, attr.valueSpan || srcSpan,
targetMatchableAttrs, boundEvents);
}
} else {
hasBinding = this._bindingParser.parsePropertyInterpolation(
@ -507,10 +511,11 @@ class TemplateParseVisitor implements html.Visitor {
}
private _parseAssignmentEvent(
name: string, expression: string, sourceSpan: ParseSourceSpan,
name: string, expression: string, sourceSpan: ParseSourceSpan, valueSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetEvents: ParsedEvent[]) {
this._bindingParser.parseEvent(
`${name}Change`, `${expression}=$event`, sourceSpan, targetMatchableAttrs, targetEvents);
`${name}Change`, `${expression}=$event`, sourceSpan, valueSpan, targetMatchableAttrs,
targetEvents);
}
private _parseDirectives(selectorMatcher: SelectorMatcher, elementCssSelector: CssSelector):