diff --git a/packages/compiler/src/i18n/message_bundle.ts b/packages/compiler/src/i18n/message_bundle.ts index f3ed1faedc4..534eefb1dae 100644 --- a/packages/compiler/src/i18n/message_bundle.ts +++ b/packages/compiler/src/i18n/message_bundle.ts @@ -6,10 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import * as html from '../ml_parser/ast'; import {InterpolationConfig} from '../ml_parser/defaults'; import {HtmlParser} from '../ml_parser/html_parser'; -import {WhitespaceVisitor} from '../ml_parser/html_whitespaces'; +import {WhitespaceVisitor, visitAllWithSiblings} from '../ml_parser/html_whitespaces'; import {ParseError} from '../parse_util'; import {extractMessages} from './extractor_merger'; @@ -49,7 +48,7 @@ export class MessageBundle { // affected message IDs. const rootNodes = this._preserveWhitespace ? htmlParserResult.rootNodes - : html.visitAll( + : visitAllWithSiblings( new WhitespaceVisitor(/* preserveSignificantWhitespace */ false), htmlParserResult.rootNodes, ); diff --git a/packages/compiler/src/ml_parser/html_whitespaces.ts b/packages/compiler/src/ml_parser/html_whitespaces.ts index 3e6af5dc9e2..6813aff8d4b 100644 --- a/packages/compiler/src/ml_parser/html_whitespaces.ts +++ b/packages/compiler/src/ml_parser/html_whitespaces.ts @@ -63,6 +63,7 @@ export class WhitespaceVisitor implements html.Visitor { constructor( private readonly preserveSignificantWhitespace: boolean, private readonly originalNodeMap?: Map, + private readonly requireContext = true, ) {} visitElement(element: html.Element, context: any): any { @@ -71,7 +72,7 @@ export class WhitespaceVisitor implements html.Visitor { // but still visit all attributes to eliminate one used as a market to preserve WS const newElement = new html.Element( element.name, - html.visitAll(this, element.attrs), + visitAllWithSiblings(this, element.attrs), element.children, element.sourceSpan, element.startSourceSpan, @@ -202,6 +203,18 @@ export class WhitespaceVisitor implements html.Visitor { visitLetDeclaration(decl: html.LetDeclaration, context: any) { return decl; } + + visit(_node: html.Node, context: any) { + // `visitAllWithSiblings` provides context necessary for ICU messages to be handled correctly. + // Prefer that over calling `html.visitAll` directly on this visitor. + if (this.requireContext && !context) { + throw new Error( + `WhitespaceVisitor requires context. Visit via \`visitAllWithSiblings\` to get this context.`, + ); + } + + return false; + } } function trimLeadingWhitespace( @@ -261,7 +274,7 @@ export function removeWhitespaces( preserveSignificantWhitespace: boolean, ): ParseTreeResult { return new ParseTreeResult( - html.visitAll( + visitAllWithSiblings( new WhitespaceVisitor(preserveSignificantWhitespace), htmlAstWithErrors.rootNodes, ), @@ -274,7 +287,7 @@ interface SiblingVisitorContext { next: html.Node | undefined; } -function visitAllWithSiblings(visitor: WhitespaceVisitor, nodes: html.Node[]): any[] { +export function visitAllWithSiblings(visitor: WhitespaceVisitor, nodes: html.Node[]): any[] { const result: any[] = []; nodes.forEach((ast, i) => { diff --git a/packages/compiler/src/render3/view/i18n/meta.ts b/packages/compiler/src/render3/view/i18n/meta.ts index e1786d1668a..1c55eb12175 100644 --- a/packages/compiler/src/render3/view/i18n/meta.ts +++ b/packages/compiler/src/render3/view/i18n/meta.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {WhitespaceVisitor} from '../../../ml_parser/html_whitespaces'; +import {WhitespaceVisitor, visitAllWithSiblings} from '../../../ml_parser/html_whitespaces'; import {computeDecimalDigest, computeDigest, decimalDigest} from '../../../i18n/digest'; import * as i18n from '../../../i18n/i18n_ast'; import {createI18nMessageFactory, VisitNodeFn} from '../../../i18n/i18n_parser'; @@ -129,7 +129,7 @@ export class I18nMetaVisitor implements html.Visitor { const originalNodeMap = new Map(); const trimmedNodes = this.preserveSignificantWhitespace ? element.children - : html.visitAll( + : visitAllWithSiblings( new WhitespaceVisitor(false /* preserveSignificantWhitespace */, originalNodeMap), element.children, ); diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index 555396acdec..6fb920b10a3 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -237,8 +237,19 @@ export function parseTemplate( // and `$localize` calls which should retain significant whitespace in order to render the // correct output. We let this diverge from the message IDs generated earlier which might not // have preserved significant whitespace. + // + // This should use `visitAllWithSiblings` to set `WhitespaceVisitor` context correctly, however + // there is an existing bug where significant whitespace is not properly retained in the JS + // output of leading/trailing whitespace for ICU messages due to the existing lack of context\ + // in `WhitespaceVisitor`. Using `visitAllWithSiblings` here would fix that bug and retain the + // whitespace, however it would also change the runtime representation which we don't want to do + // right now. rootNodes = html.visitAll( - new WhitespaceVisitor(/* preserveSignificantWhitespace */ true), + new WhitespaceVisitor( + /* preserveSignificantWhitespace */ true, + /* originalNodeMap */ undefined, + /* requireContext */ false, + ), rootNodes, ); diff --git a/packages/compiler/test/render3/view/util.ts b/packages/compiler/test/render3/view/util.ts index 9281b7144da..041f084d6e5 100644 --- a/packages/compiler/test/render3/view/util.ts +++ b/packages/compiler/test/render3/view/util.ts @@ -12,7 +12,7 @@ import {Parser} from '../../../src/expression_parser/parser'; import * as html from '../../../src/ml_parser/ast'; import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../../src/ml_parser/defaults'; import {HtmlParser} from '../../../src/ml_parser/html_parser'; -import {WhitespaceVisitor} from '../../../src/ml_parser/html_whitespaces'; +import {WhitespaceVisitor, visitAllWithSiblings} from '../../../src/ml_parser/html_whitespaces'; import {ParseTreeResult} from '../../../src/ml_parser/parser'; import * as a from '../../../src/render3/r3_ast'; import {htmlAstToRender3Ast, Render3ParseResult} from '../../../src/render3/r3_template_transform'; @@ -164,7 +164,7 @@ export function parseR3( let htmlNodes = processI18nMeta(parseResult).rootNodes; if (!options.preserveWhitespaces) { - htmlNodes = html.visitAll( + htmlNodes = visitAllWithSiblings( new WhitespaceVisitor(true /* preserveSignificantWhitespace */), htmlNodes, );