Skip to content

Commit 5e5f9fb

Browse files
authored
feat: support Angular 20 (#326)
1 parent 7e8cb64 commit 5e5f9fb

8 files changed

+143
-102
lines changed

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"release": "yarn build && standard-version"
2828
},
2929
"devDependencies": {
30-
"@angular/compiler": "19.2.2",
30+
"@angular/compiler": "20.0.0-next.8",
3131
"@babel/code-frame": "7.26.2",
3232
"@babel/parser": "7.26.10",
3333
"@babel/types": "7.26.10",
@@ -50,7 +50,7 @@
5050
"vitest": "3.0.8"
5151
},
5252
"peerDependencies": {
53-
"@angular/compiler": ">=19.2.2"
53+
"@angular/compiler": ">=19.2.2 || ^20.0.0"
5454
},
5555
"engines": {
5656
"node": ">= 20"

src/transform-node.ts

+44-11
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ class Transformer extends Source {
126126
if (node instanceof angular.Interpolation) {
127127
const { expressions } = node;
128128

129-
// istanbul ignore next 3
129+
/* c8 ignore next 3 */
130130
if (expressions.length !== 1) {
131131
throw new Error("Unexpected 'Interpolation'");
132132
}
@@ -373,7 +373,7 @@ class Transformer extends Source {
373373
{ type: 'Identifier', name: 'undefined', ...node.sourceSpan },
374374
{ hasParentParens: isInParentParens },
375375
);
376-
// istanbul ignore next
376+
/* c8 ignore next 4 */
377377
default:
378378
throw new Error(
379379
`Unexpected LiteralPrimitive value type ${typeof value}`,
@@ -427,20 +427,35 @@ class Transformer extends Source {
427427
);
428428
}
429429

430-
const isPrefixNot = node instanceof angular.PrefixNot;
431-
if (isPrefixNot || node instanceof angular.TypeofExpression) {
432-
const expression = this.#transform<babel.Expression>(node.expression);
430+
if (
431+
node instanceof angular.PrefixNot ||
432+
node instanceof angular.TypeofExpression ||
433+
node instanceof angular.VoidExpression
434+
) {
435+
const operator =
436+
node instanceof angular.PrefixNot
437+
? '!'
438+
: node instanceof angular.TypeofExpression
439+
? 'typeof'
440+
: node instanceof angular.VoidExpression
441+
? 'void'
442+
: /* c8 ignore next */
443+
undefined;
444+
445+
/* c8 ignore next 3 */
446+
if (!operator) {
447+
throw new Error('Unexpected expression.');
448+
}
433449

434-
const operator = isPrefixNot ? '!' : 'typeof';
435450
let { start } = node.sourceSpan;
436451

437-
if (!isPrefixNot) {
452+
if (operator === 'typeof' || operator === 'void') {
438453
const index = this.text.lastIndexOf(operator, start);
439454

440-
// istanbul ignore next 7
455+
/* c8 ignore next 7 */
441456
if (index === -1) {
442457
throw new Error(
443-
`Cannot find operator ${operator} from index ${start} in ${JSON.stringify(
458+
`Cannot find operator '${operator}' from index ${start} in ${JSON.stringify(
444459
this.text,
445460
)}`,
446461
);
@@ -449,6 +464,8 @@ class Transformer extends Source {
449464
start = index;
450465
}
451466

467+
const expression = this.#transform<babel.Expression>(node.expression);
468+
452469
return this.#create<babel.UnaryExpression>(
453470
{
454471
type: 'UnaryExpression',
@@ -539,6 +556,15 @@ class Transformer extends Source {
539556
);
540557
}
541558

559+
if (node instanceof angular.TaggedTemplateLiteral) {
560+
return this.#create<babel.TaggedTemplateExpression>({
561+
type: 'TaggedTemplateExpression',
562+
tag: this.#transform(node.tag) as babel.Expression,
563+
quasi: this.#transform(node.template) as babel.TemplateLiteral,
564+
...node.sourceSpan,
565+
});
566+
}
567+
542568
if (node instanceof angular.TemplateLiteral) {
543569
const { elements, expressions } = node;
544570

@@ -579,7 +605,11 @@ class Transformer extends Source {
579605
);
580606
}
581607

582-
// istanbul ignore next
608+
if (node instanceof angular.ParenthesizedExpression) {
609+
return this.#transformNode(node.expression);
610+
}
611+
612+
/* c8 ignore next */
583613
throw new Error(`Unexpected node type '${node.constructor.name}'`);
584614
}
585615
}
@@ -608,7 +638,10 @@ type SupportedNodes =
608638
| angular.EmptyExpr
609639
| angular.PrefixNot
610640
| angular.TypeofExpression
611-
| angular.TemplateLiteral; // Including `TemplateLiteralElement`
641+
| angular.VoidExpression
642+
| angular.TemplateLiteral // Including `TemplateLiteralElement`
643+
| angular.TaggedTemplateLiteral
644+
| angular.ParenthesizedExpression;
612645
function transform(node: SupportedNodes, text: string): NGNode {
613646
return new Transformer(node, text).node;
614647
}

src/transform-template-binding.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -168,13 +168,14 @@ class Transformer extends NodeTransformer {
168168
});
169169

170170
const lastNode = body.pop()!;
171-
// istanbul ignore else
171+
172172
if (lastNode.type === 'NGMicrosyntaxExpression') {
173173
body.push(updateExpressionAlias(lastNode));
174174
} else if (lastNode.type === 'NGMicrosyntaxKeyedExpression') {
175175
const expression = updateExpressionAlias(lastNode.expression);
176176
body.push(updateSpanEnd({ ...lastNode, expression }, expression.end));
177177
} else {
178+
/* c8 ignore next 2 */
178179
throw new Error(`Unexpected type ${lastNode.type}`);
179180
}
180181
} else {

src/utils.ts

+1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ export function getCharacterLastIndex(
106106
}
107107
}
108108

109+
/* c8 ignore next 4 */
109110
throw new Error(
110111
`Cannot find front char ${pattern} from index ${fromIndex} in ${JSON.stringify(
111112
text,

tests/helpers.ts

+8
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,17 @@ const KNOWN_AST_TYPES = [
169169
'SafePropertyRead',
170170
'ThisReceiver',
171171
'Interpolation',
172+
'VoidExpression',
173+
'TemplateLiteral',
174+
'TaggedTemplateLiteral',
175+
'ParenthesizedExpression',
172176
] as const;
173177

174178
export function getAngularNodeType(node: angular.AST) {
179+
if (node instanceof angular.ParenthesizedExpression) {
180+
return getAngularNodeType(node.expression);
181+
}
182+
175183
return (
176184
KNOWN_AST_TYPES.find((type) => node instanceof angular[type]) ??
177185
node.constructor.name

0 commit comments

Comments
 (0)