Skip to content

Commit 40c5357

Browse files
crisbetoatscott
authored andcommitted
refactor(compiler): introduce unknown block node (#52047)
Adds an `UnknownBlock` node to the Ivy AST to represent blocks that haven't been recognized by the compiler. This will make it easier to integrate blocks into the language service. PR Close #52047
1 parent a90d85a commit 40c5357

File tree

11 files changed

+46
-8
lines changed

11 files changed

+46
-8
lines changed

packages/compiler-cli/src/ngtsc/typecheck/extended/api/api.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {AST, ASTWithSource, ParseSourceSpan, RecursiveAstVisitor, TmplAstBoundAttribute, TmplAstBoundDeferredTrigger, TmplAstBoundEvent, TmplAstBoundText, TmplAstContent, TmplAstDeferredBlock, TmplAstDeferredBlockError, TmplAstDeferredBlockLoading, TmplAstDeferredBlockPlaceholder, TmplAstDeferredTrigger, TmplAstElement, TmplAstForLoopBlock, TmplAstForLoopBlockEmpty, TmplAstIcu, TmplAstIfBlock, TmplAstIfBlockBranch, TmplAstNode, TmplAstRecursiveVisitor, TmplAstReference, TmplAstSwitchBlock, TmplAstSwitchBlockCase, TmplAstTemplate, TmplAstText, TmplAstTextAttribute, TmplAstVariable} from '@angular/compiler';
9+
import {AST, ASTWithSource, ParseSourceSpan, RecursiveAstVisitor, TmplAstBoundAttribute, TmplAstBoundDeferredTrigger, TmplAstBoundEvent, TmplAstBoundText, TmplAstContent, TmplAstDeferredBlock, TmplAstDeferredBlockError, TmplAstDeferredBlockLoading, TmplAstDeferredBlockPlaceholder, TmplAstDeferredTrigger, TmplAstElement, TmplAstForLoopBlock, TmplAstForLoopBlockEmpty, TmplAstIcu, TmplAstIfBlock, TmplAstIfBlockBranch, TmplAstNode, TmplAstRecursiveVisitor, TmplAstReference, TmplAstSwitchBlock, TmplAstSwitchBlockCase, TmplAstTemplate, TmplAstText, TmplAstTextAttribute, TmplAstUnknownBlock, TmplAstVariable} from '@angular/compiler';
1010
import ts from 'typescript';
1111

1212
import {NgCompilerOptions} from '../../../core/api';
@@ -147,6 +147,7 @@ class TemplateVisitor<Code extends ErrorCode> extends RecursiveAstVisitor implem
147147
visitVariable(variable: TmplAstVariable): void {}
148148
visitReference(reference: TmplAstReference): void {}
149149
visitTextAttribute(attribute: TmplAstTextAttribute): void {}
150+
visitUnknownBlock(block: TmplAstUnknownBlock): void {}
150151
visitBoundAttribute(attribute: TmplAstBoundAttribute): void {
151152
this.visitAst(attribute.value);
152153
}

packages/compiler/src/compiler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export {SourceMap} from './output/source_map';
6464
export * from './injectable_compiler_2';
6565
export * from './render3/partial/api';
6666
export * from './render3/view/api';
67-
export {BoundAttribute as TmplAstBoundAttribute, BoundEvent as TmplAstBoundEvent, BoundText as TmplAstBoundText, Content as TmplAstContent, Element as TmplAstElement, Icu as TmplAstIcu, Node as TmplAstNode, RecursiveVisitor as TmplAstRecursiveVisitor, Reference as TmplAstReference, Template as TmplAstTemplate, Text as TmplAstText, TextAttribute as TmplAstTextAttribute, Variable as TmplAstVariable, DeferredBlock as TmplAstDeferredBlock, DeferredBlockPlaceholder as TmplAstDeferredBlockPlaceholder, DeferredBlockLoading as TmplAstDeferredBlockLoading, DeferredBlockError as TmplAstDeferredBlockError, DeferredTrigger as TmplAstDeferredTrigger, BoundDeferredTrigger as TmplAstBoundDeferredTrigger, IdleDeferredTrigger as TmplAstIdleDeferredTrigger, ImmediateDeferredTrigger as TmplAstImmediateDeferredTrigger, HoverDeferredTrigger as TmplAstHoverDeferredTrigger, TimerDeferredTrigger as TmplAstTimerDeferredTrigger, InteractionDeferredTrigger as TmplAstInteractionDeferredTrigger, ViewportDeferredTrigger as TmplAstViewportDeferredTrigger, SwitchBlock as TmplAstSwitchBlock, SwitchBlockCase as TmplAstSwitchBlockCase, ForLoopBlock as TmplAstForLoopBlock, ForLoopBlockEmpty as TmplAstForLoopBlockEmpty, IfBlock as TmplAstIfBlock, IfBlockBranch as TmplAstIfBlockBranch, DeferredBlockTriggers as TmplAstDeferredBlockTriggers} from './render3/r3_ast';
67+
export {BoundAttribute as TmplAstBoundAttribute, BoundEvent as TmplAstBoundEvent, BoundText as TmplAstBoundText, Content as TmplAstContent, Element as TmplAstElement, Icu as TmplAstIcu, Node as TmplAstNode, RecursiveVisitor as TmplAstRecursiveVisitor, Reference as TmplAstReference, Template as TmplAstTemplate, Text as TmplAstText, TextAttribute as TmplAstTextAttribute, Variable as TmplAstVariable, DeferredBlock as TmplAstDeferredBlock, DeferredBlockPlaceholder as TmplAstDeferredBlockPlaceholder, DeferredBlockLoading as TmplAstDeferredBlockLoading, DeferredBlockError as TmplAstDeferredBlockError, DeferredTrigger as TmplAstDeferredTrigger, BoundDeferredTrigger as TmplAstBoundDeferredTrigger, IdleDeferredTrigger as TmplAstIdleDeferredTrigger, ImmediateDeferredTrigger as TmplAstImmediateDeferredTrigger, HoverDeferredTrigger as TmplAstHoverDeferredTrigger, TimerDeferredTrigger as TmplAstTimerDeferredTrigger, InteractionDeferredTrigger as TmplAstInteractionDeferredTrigger, ViewportDeferredTrigger as TmplAstViewportDeferredTrigger, SwitchBlock as TmplAstSwitchBlock, SwitchBlockCase as TmplAstSwitchBlockCase, ForLoopBlock as TmplAstForLoopBlock, ForLoopBlockEmpty as TmplAstForLoopBlockEmpty, IfBlock as TmplAstIfBlock, IfBlockBranch as TmplAstIfBlockBranch, DeferredBlockTriggers as TmplAstDeferredBlockTriggers, UnknownBlock as TmplAstUnknownBlock} from './render3/r3_ast';
6868
export * from './render3/view/t2_api';
6969
export * from './render3/view/t2_binder';
7070
export {Identifiers as R3Identifiers} from './render3/r3_identifiers';

packages/compiler/src/render3/r3_ast.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,14 @@ export class IfBlockBranch implements Node {
309309
}
310310
}
311311

312+
export class UnknownBlock implements Node {
313+
constructor(public name: string, public sourceSpan: ParseSourceSpan) {}
314+
315+
visit<Result>(visitor: Visitor<Result>): Result {
316+
return visitor.visitUnknownBlock(this);
317+
}
318+
}
319+
312320
export class Template implements Node {
313321
constructor(
314322
// tagName is the name of the container element, if applicable.
@@ -399,6 +407,7 @@ export interface Visitor<Result = any> {
399407
visitForLoopBlockEmpty(block: ForLoopBlockEmpty): Result;
400408
visitIfBlock(block: IfBlock): Result;
401409
visitIfBlockBranch(block: IfBlockBranch): Result;
410+
visitUnknownBlock(block: UnknownBlock): Result;
402411
}
403412

404413
export class RecursiveVisitor implements Visitor<void> {
@@ -461,6 +470,7 @@ export class RecursiveVisitor implements Visitor<void> {
461470
visitBoundText(text: BoundText): void {}
462471
visitIcu(icu: Icu): void {}
463472
visitDeferredTrigger(trigger: DeferredTrigger): void {}
473+
visitUnknownBlock(block: UnknownBlock): void {}
464474
}
465475

466476

packages/compiler/src/render3/r3_template_transform.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,10 @@ class HtmlAstToIvyAst implements html.Visitor {
382382
errorMessage = `Unrecognized block @${block.name}.`;
383383
}
384384

385-
result = {node: null, errors: [new ParseError(block.sourceSpan, errorMessage)]};
385+
result = {
386+
node: new t.UnknownBlock(block.name, block.sourceSpan),
387+
errors: [new ParseError(block.sourceSpan, errorMessage)],
388+
};
386389
break;
387390
}
388391

packages/compiler/src/render3/view/t2_binder.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import {AST, BindingPipe, ImplicitReceiver, PropertyRead, PropertyWrite, RecursiveAstVisitor, SafePropertyRead} from '../../expression_parser/ast';
1010
import {SelectorMatcher} from '../../selector';
11-
import {BoundAttribute, BoundDeferredTrigger, BoundEvent, BoundText, Content, DeferredBlock, DeferredBlockError, DeferredBlockLoading, DeferredBlockPlaceholder, DeferredTrigger, Element, ForLoopBlock, ForLoopBlockEmpty, HoverDeferredTrigger, Icu, IfBlock, IfBlockBranch, InteractionDeferredTrigger, Node, Reference, SwitchBlock, SwitchBlockCase, Template, Text, TextAttribute, Variable, ViewportDeferredTrigger, Visitor} from '../r3_ast';
11+
import {BoundAttribute, BoundDeferredTrigger, BoundEvent, BoundText, Content, DeferredBlock, DeferredBlockError, DeferredBlockLoading, DeferredBlockPlaceholder, DeferredTrigger, Element, ForLoopBlock, ForLoopBlockEmpty, HoverDeferredTrigger, Icu, IfBlock, IfBlockBranch, InteractionDeferredTrigger, Node, Reference, SwitchBlock, SwitchBlockCase, Template, Text, TextAttribute, UnknownBlock, Variable, ViewportDeferredTrigger, Visitor} from '../r3_ast';
1212

1313
import {BoundTarget, DirectiveMeta, ReferenceTarget, ScopedNode, Target, TargetBinder} from './t2_api';
1414
import {createCssSelector} from './template';
@@ -207,6 +207,7 @@ class Scope implements Visitor {
207207
visitTextAttribute(attr: TextAttribute) {}
208208
visitIcu(icu: Icu) {}
209209
visitDeferredTrigger(trigger: DeferredTrigger) {}
210+
visitUnknownBlock(block: UnknownBlock) {}
210211

211212
private maybeDeclare(thing: Reference|Variable) {
212213
// Declare something with a name, as long as that name isn't taken.
@@ -447,6 +448,7 @@ class DirectiveBinder<DirectiveT extends DirectiveMeta> implements Visitor {
447448
visitBoundText(text: BoundText): void {}
448449
visitIcu(icu: Icu): void {}
449450
visitDeferredTrigger(trigger: DeferredTrigger): void {}
451+
visitUnknownBlock(block: UnknownBlock) {}
450452
}
451453

452454
/**
@@ -590,6 +592,7 @@ class TemplateBinder extends RecursiveAstVisitor implements Visitor {
590592
visitText(text: Text) {}
591593
visitContent(content: Content) {}
592594
visitTextAttribute(attribute: TextAttribute) {}
595+
visitUnknownBlock(block: UnknownBlock) {}
593596
visitIcu(icu: Icu): void {
594597
Object.keys(icu.vars).forEach(key => icu.vars[key].visit(this));
595598
Object.keys(icu.placeholders).forEach(key => icu.placeholders[key].visit(this));

packages/compiler/src/render3/view/template.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,6 +1054,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
10541054
readonly visitIfBlockBranch = invalid;
10551055
readonly visitSwitchBlockCase = invalid;
10561056
readonly visitForLoopBlockEmpty = invalid;
1057+
readonly visitUnknownBlock = invalid;
10571058

10581059
visitBoundText(text: t.BoundText) {
10591060
if (this.i18n) {

packages/compiler/test/render3/r3_ast_spans_spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,10 @@ class R3AstSourceSpans implements t.Visitor<void> {
204204
this.visitAll([block.children]);
205205
}
206206

207+
visitUnknownBlock(block: t.UnknownBlock): void {
208+
this.result.push(['UnknownBlock', humanizeSpan(block.sourceSpan)]);
209+
}
210+
207211
private visitAll(nodes: t.Node[][]) {
208212
nodes.forEach(node => t.visitAll(this, node));
209213
}

packages/compiler/test/render3/r3_template_transform_spec.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,10 @@ class R3AstHumanizer implements t.Visitor<void> {
170170
this.visitAll([block.children]);
171171
}
172172

173+
visitUnknownBlock(block: t.UnknownBlock): void {
174+
this.result.push(['UnknownBlock', block.name]);
175+
}
176+
173177
private visitAll(nodes: t.Node[][]) {
174178
nodes.forEach(node => t.visitAll(this, node));
175179
}
@@ -1100,7 +1104,7 @@ describe('R3 template transform', () => {
11001104

11011105
it('should report unclosed parenthesis in `on` trigger', () => {
11021106
expect(() => parse('@defer (on viewport(button) {hello}'))
1103-
.toThrowError(/Unexpected character "EOF"/);
1107+
.toThrowError(/Incomplete block "defer"/);
11041108
});
11051109

11061110
it('should report incorrect closing parenthesis in `on` trigger', () => {
@@ -1109,7 +1113,7 @@ describe('R3 template transform', () => {
11091113
});
11101114

11111115
it('should report stray closing parenthesis in `on` trigger', () => {
1112-
expect(() => parse('@defer (on idle)) {hello}')).toThrowError(/Unexpected character "\)"/);
1116+
expect(() => parse('@defer (on idle)) {hello}')).toThrowError(/Unexpected character "EOF"/);
11131117
});
11141118

11151119
it('should report non-identifier token usage in `on` trigger', () => {
@@ -1565,7 +1569,7 @@ describe('R3 template transform', () => {
15651569
expect(() => parse(`@for ((a of b(); track c) {hello}`))
15661570
.toThrowError(/Unexpected end of expression: b\(/);
15671571
expect(() => parse(`@for (a of b); track c) {hello}`))
1568-
.toThrowError(/Unexpected character ";"/);
1572+
.toThrowError(/Unexpected character "EOF"/);
15691573
});
15701574

15711575
it('should report unrecognized for loop parameters', () => {
@@ -1849,4 +1853,12 @@ describe('R3 template transform', () => {
18491853
});
18501854
});
18511855
});
1856+
1857+
describe('unknown blocks', () => {
1858+
it('should parse unknown blocks', () => {
1859+
expectFromHtml('@unknown {}', true /* ignoreError */).toEqual([
1860+
['UnknownBlock', 'unknown'],
1861+
]);
1862+
});
1863+
});
18521864
});

packages/compiler/test/render3/util/expression.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ class ExpressionSourceHumanizer extends e.RecursiveAstVisitor implements t.Visit
138138
}
139139
visitContent(ast: t.Content) {}
140140
visitText(ast: t.Text) {}
141+
visitUnknownBlock(block: t.UnknownBlock) {}
141142
visitIcu(ast: t.Icu) {
142143
for (const key of Object.keys(ast.vars)) {
143144
ast.vars[key].visit(this);

packages/core/schematics/utils/template_ast_visitor.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import type {TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstBoundText, TmplAstContent, TmplAstDeferredBlock, TmplAstDeferredBlockError, TmplAstDeferredBlockLoading, TmplAstDeferredBlockPlaceholder, TmplAstDeferredTrigger, TmplAstElement, TmplAstIfBlockBranch, TmplAstForLoopBlock, TmplAstForLoopBlockEmpty, TmplAstIcu, TmplAstIfBlock, TmplAstNode, TmplAstRecursiveVisitor, TmplAstReference, TmplAstSwitchBlock, TmplAstSwitchBlockCase, TmplAstTemplate, TmplAstText, TmplAstTextAttribute, TmplAstVariable} from '@angular/compiler';
9+
import type {TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstBoundText, TmplAstContent, TmplAstDeferredBlock, TmplAstDeferredBlockError, TmplAstDeferredBlockLoading, TmplAstDeferredBlockPlaceholder, TmplAstDeferredTrigger, TmplAstElement, TmplAstIfBlockBranch, TmplAstForLoopBlock, TmplAstForLoopBlockEmpty, TmplAstIcu, TmplAstIfBlock, TmplAstNode, TmplAstRecursiveVisitor, TmplAstReference, TmplAstSwitchBlock, TmplAstSwitchBlockCase, TmplAstTemplate, TmplAstText, TmplAstTextAttribute, TmplAstVariable, TmplAstUnknownBlock} from '@angular/compiler';
1010

1111
/**
1212
* A base class that can be used to implement a Render3 Template AST visitor.
@@ -46,6 +46,7 @@ export class TemplateAstVisitor implements TmplAstRecursiveVisitor {
4646
visitDeferredBlockError(block: TmplAstDeferredBlockError): void {}
4747
visitDeferredBlockLoading(block: TmplAstDeferredBlockLoading): void {}
4848
visitDeferredTrigger(trigger: TmplAstDeferredTrigger): void {}
49+
visitUnknownBlock(block: TmplAstUnknownBlock): void {}
4950
visitSwitchBlock(block: TmplAstSwitchBlock): void {}
5051
visitSwitchBlockCase(block: TmplAstSwitchBlockCase): void {}
5152
visitForLoopBlock(block: TmplAstForLoopBlock): void {}

0 commit comments

Comments
 (0)