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

@ -50,13 +50,15 @@ export class BoundAttribute implements Node {
export class BoundEvent implements Node {
constructor(
public name: string, public type: ParsedEventType, public handler: AST,
public target: string|null, public phase: string|null, public sourceSpan: ParseSourceSpan) {}
public target: string|null, public phase: string|null, public sourceSpan: ParseSourceSpan,
public handlerSpan: ParseSourceSpan) {}
static fromParsedEvent(event: ParsedEvent) {
const target: string|null = event.type === ParsedEventType.Regular ? event.targetOrPhase : null;
const phase: string|null =
event.type === ParsedEventType.Animation ? event.targetOrPhase : null;
return new BoundEvent(event.name, event.type, event.handler, target, phase, event.sourceSpan);
return new BoundEvent(
event.name, event.type, event.handler, target, phase, event.sourceSpan, event.handlerSpan);
}
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitBoundEvent(this); }
@ -67,7 +69,12 @@ export class Element implements Node {
public name: string, public attributes: TextAttribute[], public inputs: BoundAttribute[],
public outputs: BoundEvent[], public children: Node[], public references: Reference[],
public sourceSpan: ParseSourceSpan, public startSourceSpan: ParseSourceSpan|null,
public endSourceSpan: ParseSourceSpan|null, public i18n?: I18nAST) {}
public endSourceSpan: ParseSourceSpan|null, public i18n?: I18nAST) {
// If the element is empty then the source span should include any closing tag
if (children.length === 0 && startSourceSpan && endSourceSpan) {
this.sourceSpan = {...sourceSpan, end: endSourceSpan.end};
}
}
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitElement(this); }
}

View File

@ -284,13 +284,15 @@ class HtmlAstToIvyAst implements html.Visitor {
} else if (bindParts[KW_ON_IDX]) {
const events: ParsedEvent[] = [];
this.bindingParser.parseEvent(
bindParts[IDENT_KW_IDX], value, srcSpan, matchableAttributes, events);
bindParts[IDENT_KW_IDX], value, srcSpan, attribute.valueSpan || srcSpan,
matchableAttributes, events);
addEvents(events, boundEvents);
} else if (bindParts[KW_BINDON_IDX]) {
this.bindingParser.parsePropertyBinding(
bindParts[IDENT_KW_IDX], value, false, srcSpan, matchableAttributes, parsedProperties);
this.parseAssignmentEvent(
bindParts[IDENT_KW_IDX], value, srcSpan, matchableAttributes, boundEvents);
bindParts[IDENT_KW_IDX], value, srcSpan, attribute.valueSpan, matchableAttributes,
boundEvents);
} else if (bindParts[KW_AT_IDX]) {
this.bindingParser.parseLiteralAttr(
name, value, srcSpan, matchableAttributes, parsedProperties);
@ -300,7 +302,8 @@ class HtmlAstToIvyAst implements html.Visitor {
bindParts[IDENT_BANANA_BOX_IDX], value, false, srcSpan, matchableAttributes,
parsedProperties);
this.parseAssignmentEvent(
bindParts[IDENT_BANANA_BOX_IDX], value, srcSpan, matchableAttributes, boundEvents);
bindParts[IDENT_BANANA_BOX_IDX], value, srcSpan, attribute.valueSpan,
matchableAttributes, boundEvents);
} else if (bindParts[IDENT_PROPERTY_IDX]) {
this.bindingParser.parsePropertyBinding(
@ -310,7 +313,8 @@ class HtmlAstToIvyAst implements html.Visitor {
} else if (bindParts[IDENT_EVENT_IDX]) {
const events: ParsedEvent[] = [];
this.bindingParser.parseEvent(
bindParts[IDENT_EVENT_IDX], value, srcSpan, matchableAttributes, events);
bindParts[IDENT_EVENT_IDX], value, srcSpan, attribute.valueSpan || srcSpan,
matchableAttributes, events);
addEvents(events, boundEvents);
}
} else {
@ -347,10 +351,12 @@ class HtmlAstToIvyAst implements html.Visitor {
private parseAssignmentEvent(
name: string, expression: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], boundEvents: t.BoundEvent[]) {
valueSpan: ParseSourceSpan|undefined, targetMatchableAttrs: string[][],
boundEvents: t.BoundEvent[]) {
const events: ParsedEvent[] = [];
this.bindingParser.parseEvent(
`${name}Change`, `${expression}=$event`, sourceSpan, targetMatchableAttrs, events);
`${name}Change`, `${expression}=$event`, sourceSpan, valueSpan || sourceSpan,
targetMatchableAttrs, events);
addEvents(events, boundEvents);
}

View File

@ -79,7 +79,8 @@ export function prepareEventListenerParameters(
}
const bindingExpr = convertActionBinding(
scope, bindingContext, handler, 'b', () => error('Unexpected interpolation'));
scope, bindingContext, handler, 'b', () => error('Unexpected interpolation'),
eventAst.handlerSpan);
const statements = [];
if (scope) {