mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(compiler): preserve &ngsp; between sibling control flow blocks
`findConnectedBlocks` scans siblings after an `@if` to collect connected `@else`/`@else if` blocks. Whitespace-only text nodes encountered during the scan were eagerly added to `processedNodes`, marking them as "do not emit", before confirming whether a connected block actually followed. By the time `findConnectedBlocks` runs, `WhitespaceVisitor` has already converted `&ngsp;` (and ` `) into a plain space character, making them indistinguishable from insignificant whitespace via `.trim().length`. When the next sibling was a second, unrelated `@if` instead of `@else`, the scan stopped but the text node was already silently dropped. Fix by deferring the `processedNodes` insertion into a pending buffer and only committing those nodes once a connected block is confirmed to follow. Fixes #55791
This commit is contained in:
@@ -217,9 +217,7 @@ class HtmlAstToIvyAst implements html.Visitor {
|
||||
attrs.bound,
|
||||
boundEvents,
|
||||
directives,
|
||||
[
|
||||
/* no template attributes */
|
||||
],
|
||||
[/* no template attributes */],
|
||||
children,
|
||||
references,
|
||||
variables,
|
||||
@@ -560,6 +558,11 @@ class HtmlAstToIvyAst implements html.Visitor {
|
||||
predicate: (blockName: string) => boolean,
|
||||
): html.Block[] {
|
||||
const relatedBlocks: html.Block[] = [];
|
||||
// Whitespace-only text nodes to mark as processed only if a connected block follows them.
|
||||
// We defer this so that significant whitespace (e.g. &ngsp; or , both of which look
|
||||
// like insignificant whitespace by the time we get here) between two unrelated @if blocks
|
||||
// is not silently eaten.
|
||||
const pendingTextNodes: html.Text[] = [];
|
||||
|
||||
for (let i = primaryBlockIndex + 1; i < siblings.length; i++) {
|
||||
const node = siblings[i];
|
||||
@@ -569,11 +572,10 @@ class HtmlAstToIvyAst implements html.Visitor {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore empty text nodes between blocks.
|
||||
if (node instanceof html.Text && node.value.trim().length === 0) {
|
||||
// Add the text node to the processed nodes since we don't want
|
||||
// it to be generated between the connected nodes.
|
||||
this.processedNodes.add(node);
|
||||
// Collect whitespace-only text nodes; only mark them as processed once we confirm
|
||||
// they precede a connected block (e.g. @else / @else if).
|
||||
pendingTextNodes.push(node);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -582,6 +584,12 @@ class HtmlAstToIvyAst implements html.Visitor {
|
||||
break;
|
||||
}
|
||||
|
||||
// A connected block was found — commit the pending whitespace nodes as processed.
|
||||
for (const pending of pendingTextNodes) {
|
||||
this.processedNodes.add(pending);
|
||||
}
|
||||
pendingTextNodes.length = 0;
|
||||
|
||||
relatedBlocks.push(node);
|
||||
this.processedNodes.add(node);
|
||||
}
|
||||
|
||||
@@ -2636,6 +2636,58 @@ describe('R3 template transform', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should preserve &ngsp; between two sibling @if blocks', () => {
|
||||
expectFromHtml(
|
||||
`@if (true) {<span>Hello</span>}&ngsp;@if (true) {<span>World</span>}`,
|
||||
).toEqual([
|
||||
['IfBlock'],
|
||||
['IfBlockBranch', 'true'],
|
||||
['Element', 'span'],
|
||||
['Text', 'Hello'],
|
||||
['Text', ' '],
|
||||
['IfBlock'],
|
||||
['IfBlockBranch', 'true'],
|
||||
['Element', 'span'],
|
||||
['Text', 'World'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should preserve plain whitespace between two sibling @if blocks when preserveWhitespaces is enabled', () => {
|
||||
expectFromR3Nodes(
|
||||
parse(`@if (true) {<span>Hello</span>}\n@if (true) {<span>World</span>}`, {
|
||||
preserveWhitespaces: true,
|
||||
}).nodes,
|
||||
).toEqual([
|
||||
['IfBlock'],
|
||||
['IfBlockBranch', 'true'],
|
||||
['Element', 'span'],
|
||||
['Text', 'Hello'],
|
||||
['Text', '\n'],
|
||||
['IfBlock'],
|
||||
['IfBlockBranch', 'true'],
|
||||
['Element', 'span'],
|
||||
['Text', 'World'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should preserve between two sibling @if blocks when preserveWhitespaces is enabled', () => {
|
||||
expectFromR3Nodes(
|
||||
parse(`@if (true) {<span>Hello</span>} @if (true) {<span>World</span>}`, {
|
||||
preserveWhitespaces: true,
|
||||
}).nodes,
|
||||
).toEqual([
|
||||
['IfBlock'],
|
||||
['IfBlockBranch', 'true'],
|
||||
['Element', 'span'],
|
||||
['Text', 'Hello'],
|
||||
['Text', '\u00A0'],
|
||||
['IfBlock'],
|
||||
['IfBlockBranch', 'true'],
|
||||
['Element', 'span'],
|
||||
['Text', 'World'],
|
||||
]);
|
||||
});
|
||||
|
||||
describe('validations', () => {
|
||||
it('should report an if block without a condition', () => {
|
||||
expect(() =>
|
||||
|
||||
Reference in New Issue
Block a user