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:

committed by
Misko Hevery

parent
497619f25d
commit
cffd86260a
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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):
|
||||
|
Reference in New Issue
Block a user