diff --git a/packages/compiler/src/i18n/extractor_merger.ts b/packages/compiler/src/i18n/extractor_merger.ts index 66a8e7a1df..b0da1c372b 100644 --- a/packages/compiler/src/i18n/extractor_merger.ts +++ b/packages/compiler/src/i18n/extractor_merger.ts @@ -394,10 +394,14 @@ class _Visitor implements html.Visitor { const nodes = this._translations.get(message); if (nodes) { if (nodes.length == 0) { - translatedAttributes.push(new html.Attribute(attr.name, '', attr.sourceSpan)); + translatedAttributes.push(new html.Attribute( + attr.name, '', attr.sourceSpan, undefined /* keySpan */, undefined /* valueSpan */, + undefined /* i18n */)); } else if (nodes[0] instanceof html.Text) { const value = (nodes[0] as html.Text).value; - translatedAttributes.push(new html.Attribute(attr.name, value, attr.sourceSpan)); + translatedAttributes.push(new html.Attribute( + attr.name, value, attr.sourceSpan, undefined /* keySpan */, + undefined /* valueSpan */, undefined /* i18n */)); } else { this._reportError( el, diff --git a/packages/compiler/src/ml_parser/ast.ts b/packages/compiler/src/ml_parser/ast.ts index ead5c11cce..c962e04a9d 100644 --- a/packages/compiler/src/ml_parser/ast.ts +++ b/packages/compiler/src/ml_parser/ast.ts @@ -53,7 +53,8 @@ export class ExpansionCase implements Node { export class Attribute extends NodeWithI18n { constructor( public name: string, public value: string, sourceSpan: ParseSourceSpan, - public valueSpan?: ParseSourceSpan, i18n?: I18nMeta) { + readonly keySpan: ParseSourceSpan|undefined, public valueSpan?: ParseSourceSpan, + i18n?: I18nMeta) { super(sourceSpan, i18n); } visit(visitor: Visitor, context: any): any { diff --git a/packages/compiler/src/ml_parser/icu_ast_expander.ts b/packages/compiler/src/ml_parser/icu_ast_expander.ts index 120a433001..9e08c954dd 100644 --- a/packages/compiler/src/ml_parser/icu_ast_expander.ts +++ b/packages/compiler/src/ml_parser/icu_ast_expander.ts @@ -102,10 +102,14 @@ function _expandPluralForm(ast: html.Expansion, errors: ParseError[]): html.Elem errors.push(...expansionResult.errors); return new html.Element( - `ng-template`, [new html.Attribute('ngPluralCase', `${c.value}`, c.valueSourceSpan)], + `ng-template`, [new html.Attribute( + 'ngPluralCase', `${c.value}`, c.valueSourceSpan, undefined /* keySpan */, + undefined /* valueSpan */, undefined /* i18n */)], expansionResult.nodes, c.sourceSpan, c.sourceSpan, c.sourceSpan); }); - const switchAttr = new html.Attribute('[ngPlural]', ast.switchValue, ast.switchValueSourceSpan); + const switchAttr = new html.Attribute( + '[ngPlural]', ast.switchValue, ast.switchValueSourceSpan, undefined /* keySpan */, + undefined /* valueSpan */, undefined /* i18n */); return new html.Element( 'ng-container', [switchAttr], children, ast.sourceSpan, ast.sourceSpan, ast.sourceSpan); } @@ -119,15 +123,21 @@ function _expandDefaultForm(ast: html.Expansion, errors: ParseError[]): html.Ele if (c.value === 'other') { // other is the default case when no values match return new html.Element( - `ng-template`, [new html.Attribute('ngSwitchDefault', '', c.valueSourceSpan)], + `ng-template`, [new html.Attribute( + 'ngSwitchDefault', '', c.valueSourceSpan, undefined /* keySpan */, + undefined /* valueSpan */, undefined /* i18n */)], expansionResult.nodes, c.sourceSpan, c.sourceSpan, c.sourceSpan); } return new html.Element( - `ng-template`, [new html.Attribute('ngSwitchCase', `${c.value}`, c.valueSourceSpan)], + `ng-template`, [new html.Attribute( + 'ngSwitchCase', `${c.value}`, c.valueSourceSpan, undefined /* keySpan */, + undefined /* valueSpan */, undefined /* i18n */)], expansionResult.nodes, c.sourceSpan, c.sourceSpan, c.sourceSpan); }); - const switchAttr = new html.Attribute('[ngSwitch]', ast.switchValue, ast.switchValueSourceSpan); + const switchAttr = new html.Attribute( + '[ngSwitch]', ast.switchValue, ast.switchValueSourceSpan, undefined /* keySpan */, + undefined /* valueSpan */, undefined /* i18n */); return new html.Element( 'ng-container', [switchAttr], children, ast.sourceSpan, ast.sourceSpan, ast.sourceSpan); } diff --git a/packages/compiler/src/ml_parser/parser.ts b/packages/compiler/src/ml_parser/parser.ts index 2bd172921e..df73b9b6ac 100644 --- a/packages/compiler/src/ml_parser/parser.ts +++ b/packages/compiler/src/ml_parser/parser.ts @@ -351,9 +351,10 @@ class _TreeBuilder { const quoteToken = this._advance(); end = quoteToken.sourceSpan.end; } + const keySpan = new ParseSourceSpan(attrName.sourceSpan.start, attrName.sourceSpan.end); return new html.Attribute( fullName, value, - new ParseSourceSpan(attrName.sourceSpan.start, end, attrName.sourceSpan.fullStart), + new ParseSourceSpan(attrName.sourceSpan.start, end, attrName.sourceSpan.fullStart), keySpan, valueSpan); } diff --git a/packages/compiler/src/render3/r3_ast.ts b/packages/compiler/src/render3/r3_ast.ts index ef2d1a732a..0d408651bb 100644 --- a/packages/compiler/src/render3/r3_ast.ts +++ b/packages/compiler/src/render3/r3_ast.ts @@ -30,10 +30,17 @@ export class BoundText implements Node { } } +/** + * Represents a text attribute in the template. + * + * `valueSpan` may not be present in cases where there is no value `
`. + * `keySpan` may also not be present for synthetic attributes from ICU expansions. + */ export class TextAttribute implements Node { constructor( public name: string, public value: string, public sourceSpan: ParseSourceSpan, - public valueSpan?: ParseSourceSpan, public i18n?: I18nMeta) {} + readonly keySpan: ParseSourceSpan|undefined, public valueSpan?: ParseSourceSpan, + public i18n?: I18nMeta) {} visit