diff --git a/packages/compiler/src/expression_parser/parser.ts b/packages/compiler/src/expression_parser/parser.ts index 9e7977a80a92a..9e0205bd9eebd 100644 --- a/packages/compiler/src/expression_parser/parser.ts +++ b/packages/compiler/src/expression_parser/parser.ts @@ -764,7 +764,7 @@ export class _ParseAST { const ast = this.parsePipe(); const source = this.input.substring(start - this.offset, this.inputIndex - this.offset); expression = - new ASTWithSource(ast, source, this.location, this.absoluteOffset, this.errors); + new ASTWithSource(ast, source, this.location, this.absoluteOffset + start, this.errors); } bindings.push(new TemplateBinding( diff --git a/packages/compiler/src/render3/r3_template_transform.ts b/packages/compiler/src/render3/r3_template_transform.ts index 4df6909321786..8739b66fbf2f6 100644 --- a/packages/compiler/src/render3/r3_template_transform.ts +++ b/packages/compiler/src/render3/r3_template_transform.ts @@ -151,10 +151,15 @@ class HtmlAstToIvyAst implements html.Visitor { const templateKey = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length); const parsedVariables: ParsedVariable[] = []; - const absoluteOffset = attribute.valueSpan ? attribute.valueSpan.start.offset : - attribute.sourceSpan.start.offset; + const absoluteValueOffset = attribute.valueSpan ? + attribute.valueSpan.start.offset : + // If there is no value span the attribute does not have a value, like `attr` in + //`
`. In this case, point to one character beyond the last character of + // the attribute name. + attribute.sourceSpan.start.offset + attribute.name.length; + this.bindingParser.parseInlineTemplateBinding( - templateKey, templateValue, attribute.sourceSpan, absoluteOffset, [], + templateKey, templateValue, attribute.sourceSpan, absoluteValueOffset, [], templateParsedProperties, parsedVariables); templateVariables.push( ...parsedVariables.map(v => new t.Variable(v.name, v.value, v.sourceSpan))); diff --git a/packages/compiler/src/template_parser/binding_parser.ts b/packages/compiler/src/template_parser/binding_parser.ts index 6da0d1495fea0..f6258db5e3d5e 100644 --- a/packages/compiler/src/template_parser/binding_parser.ts +++ b/packages/compiler/src/template_parser/binding_parser.ts @@ -113,12 +113,22 @@ export class BindingParser { } } - // Parse an inline template binding. ie `` + /** + * Parses an inline template binding, e.g. + * + * @param tplKey template binding name + * @param tplValue template binding value + * @param sourceSpan span of template binding relative to entire the template + * @param absoluteValueOffset start of the tplValue relative to the entire template + * @param targetMatchableAttrs potential attributes to match in the template + * @param targetProps target property bindings in the template + * @param targetVars target variables in the template + */ parseInlineTemplateBinding( - tplKey: string, tplValue: string, sourceSpan: ParseSourceSpan, absoluteOffset: number, + tplKey: string, tplValue: string, sourceSpan: ParseSourceSpan, absoluteValueOffset: number, targetMatchableAttrs: string[][], targetProps: ParsedProperty[], targetVars: ParsedVariable[]) { - const bindings = this._parseTemplateBindings(tplKey, tplValue, sourceSpan, absoluteOffset); + const bindings = this._parseTemplateBindings(tplKey, tplValue, sourceSpan, absoluteValueOffset); for (let i = 0; i < bindings.length; i++) { const binding = bindings[i]; @@ -131,20 +141,28 @@ export class BindingParser { } else { targetMatchableAttrs.push([binding.key, '']); this.parseLiteralAttr( - binding.key, null, sourceSpan, absoluteOffset, undefined, targetMatchableAttrs, + binding.key, null, sourceSpan, absoluteValueOffset, undefined, targetMatchableAttrs, targetProps); } } } + /** + * Parses the bindings in an inline template binding, e.g. + * + * @param tplKey template binding name + * @param tplValue template binding value + * @param sourceSpan span of template binding relative to entire the template + * @param absoluteValueOffset start of the tplValue relative to the entire template + */ private _parseTemplateBindings( tplKey: string, tplValue: string, sourceSpan: ParseSourceSpan, - absoluteOffset: number): TemplateBinding[] { + absoluteValueOffset: number): TemplateBinding[] { const sourceInfo = sourceSpan.start.toString(); try { const bindingsResult = - this._exprParser.parseTemplateBindings(tplKey, tplValue, sourceInfo, absoluteOffset); + this._exprParser.parseTemplateBindings(tplKey, tplValue, sourceInfo, absoluteValueOffset); this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan); bindingsResult.templateBindings.forEach((binding) => { if (binding.expression) { diff --git a/packages/compiler/test/render3/r3_ast_absolute_span_spec.ts b/packages/compiler/test/render3/r3_ast_absolute_span_spec.ts index cc38497f26822..6641e161089b6 100644 --- a/packages/compiler/test/render3/r3_ast_absolute_span_spec.ts +++ b/packages/compiler/test/render3/r3_ast_absolute_span_spec.ts @@ -322,4 +322,18 @@ describe('expression AST absolute source spans', () => { 'a:b', new AbsoluteSourceSpan(13, 16) ]); }); + + describe('absolute offsets for template expressions', () => { + it('should work for simple cases', () => { + expect( + humanizeExpressionSource(parse('
{{item}}
').nodes)) + .toContain(['items', new AbsoluteSourceSpan(25, 30)]); + }); + + it('should work with multiple bindings', () => { + expect(humanizeExpressionSource(parse('
').nodes)) + .toEqual(jasmine.arrayContaining( + [['As', new AbsoluteSourceSpan(22, 24)], ['Bs', new AbsoluteSourceSpan(35, 37)]])); + }); + }); });