Skip to content

Commit 820602f

Browse files
committed
fix(@angular/build): support parenthesized expressions in oxc linker
Unwrap parenthesized expressions in `OxcAstHost` to ensure parity with Babel and prevent fatal linker errors when parsing functions returning parenthesized expressions (such as `resolveMetadata: () => ({ ... })` in deferred component metadata) and other parenthesized declaration properties. Fixes #34129
1 parent eb1edeb commit 820602f

3 files changed

Lines changed: 154 additions & 13 deletions

File tree

packages/angular/build/src/tools/angular/linker/oxc-ast-host.ts

Lines changed: 57 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,20 @@ function isNode(node: unknown): node is Node {
2626
return typeof node === 'object' && node !== null && 'type' in node;
2727
}
2828

29+
function unwrapParentheses(node: unknown): unknown {
30+
while (isNode(node) && node.type === 'ParenthesizedExpression') {
31+
node = (node as { expression: unknown }).expression;
32+
}
33+
34+
return node;
35+
}
36+
2937
/**
3038
* An implementation of `AstHost` that queries information from `oxc-parser` AST nodes.
3139
*/
3240
export class OxcAstHost implements AstHost<unknown> {
3341
getSymbolName(node: unknown): string | null {
42+
node = unwrapParentheses(node);
3443
if (!isNode(node)) {
3544
return null;
3645
}
@@ -47,10 +56,13 @@ export class OxcAstHost implements AstHost<unknown> {
4756
}
4857

4958
isStringLiteral(node: unknown): node is StringLiteral {
59+
node = unwrapParentheses(node);
60+
5061
return isNode(node) && node.type === 'Literal' && typeof node.value === 'string';
5162
}
5263

5364
parseStringLiteral(str: unknown): string {
65+
str = unwrapParentheses(str);
5466
if (!this.isStringLiteral(str)) {
5567
throw new FatalLinkerError(str as object, 'Unsupported syntax, expected a string literal.');
5668
}
@@ -59,10 +71,13 @@ export class OxcAstHost implements AstHost<unknown> {
5971
}
6072

6173
isNumericLiteral(node: unknown): node is NumericLiteral {
74+
node = unwrapParentheses(node);
75+
6276
return isNode(node) && node.type === 'Literal' && typeof node.value === 'number';
6377
}
6478

6579
parseNumericLiteral(num: unknown): number {
80+
num = unwrapParentheses(num);
6681
if (!this.isNumericLiteral(num)) {
6782
throw new FatalLinkerError(num as object, 'Unsupported syntax, expected a numeric literal.');
6883
}
@@ -71,6 +86,7 @@ export class OxcAstHost implements AstHost<unknown> {
7186
}
7287

7388
isBooleanLiteral(node: unknown): node is BooleanLiteral | UnaryExpression {
89+
node = unwrapParentheses(node);
7490
if (!isNode(node)) {
7591
return false;
7692
}
@@ -81,27 +97,35 @@ export class OxcAstHost implements AstHost<unknown> {
8197
}
8298

8399
parseBooleanLiteral(bool: unknown): boolean {
100+
bool = unwrapParentheses(bool);
84101
if (isNode(bool)) {
85102
if (bool.type === 'Literal' && typeof bool.value === 'boolean') {
86103
return bool.value;
87104
}
88105
if (isMinifiedBooleanLiteral(bool)) {
89-
return !bool.argument.value;
106+
const arg = unwrapParentheses(bool.argument) as NumericLiteral;
107+
108+
return !arg.value;
90109
}
91110
}
92111

93112
throw new FatalLinkerError(bool as object, 'Unsupported syntax, expected a boolean literal.');
94113
}
95114

96115
isNull(node: unknown): node is NullLiteral {
116+
node = unwrapParentheses(node);
117+
97118
return isNode(node) && node.type === 'Literal' && node.value === null;
98119
}
99120

100121
isArrayLiteral(node: unknown): node is ArrayExpression {
122+
node = unwrapParentheses(node);
123+
101124
return isNode(node) && node.type === 'ArrayExpression';
102125
}
103126

104127
parseArrayLiteral(array: unknown): unknown[] {
128+
array = unwrapParentheses(array);
105129
if (!this.isArrayLiteral(array)) {
106130
throw new FatalLinkerError(array as object, 'Unsupported syntax, expected an array literal.');
107131
}
@@ -115,23 +139,27 @@ export class OxcAstHost implements AstHost<unknown> {
115139
'Unsupported syntax, element in array not to be empty.',
116140
);
117141
}
118-
if (element.type === 'SpreadElement') {
142+
const unwrappedElement = unwrapParentheses(element);
143+
if (isNode(unwrappedElement) && unwrappedElement.type === 'SpreadElement') {
119144
throw new FatalLinkerError(
120-
element as object,
145+
unwrappedElement as object,
121146
'Unsupported syntax, element in array not to use spread syntax.',
122147
);
123148
}
124-
result.push(element);
149+
result.push(unwrappedElement);
125150
}
126151

127152
return result;
128153
}
129154

130155
isObjectLiteral(node: unknown): node is ObjectExpression {
156+
node = unwrapParentheses(node);
157+
131158
return isNode(node) && node.type === 'ObjectExpression';
132159
}
133160

134161
parseObjectLiteral(obj: unknown): Map<string, unknown> {
162+
obj = unwrapParentheses(obj);
135163
if (!this.isObjectLiteral(obj)) {
136164
throw new FatalLinkerError(obj as object, 'Unsupported syntax, expected an object literal.');
137165
}
@@ -146,7 +174,13 @@ export class OxcAstHost implements AstHost<unknown> {
146174
);
147175
}
148176

149-
const keyNode = property.key;
177+
const keyNode = unwrapParentheses(property.key);
178+
if (!isNode(keyNode)) {
179+
throw new FatalLinkerError(
180+
property.key as object,
181+
'Unsupported syntax, expected a property name.',
182+
);
183+
}
150184

151185
let key: string;
152186
if (keyNode.type === 'Identifier') {
@@ -162,13 +196,14 @@ export class OxcAstHost implements AstHost<unknown> {
162196
);
163197
}
164198

165-
result.set(key, property.value);
199+
result.set(key, unwrapParentheses(property.value));
166200
}
167201

168202
return result;
169203
}
170204

171205
isFunctionExpression(node: unknown): node is FunctionNode | ArrowFunctionExpression {
206+
node = unwrapParentheses(node);
172207
if (!isNode(node)) {
173208
return false;
174209
}
@@ -181,6 +216,7 @@ export class OxcAstHost implements AstHost<unknown> {
181216
}
182217

183218
parseReturnValue(fn: unknown): unknown {
219+
fn = unwrapParentheses(fn);
184220
if (!this.isFunctionExpression(fn)) {
185221
throw new FatalLinkerError(fn as object, 'Unsupported syntax, expected a function.');
186222
}
@@ -191,7 +227,7 @@ export class OxcAstHost implements AstHost<unknown> {
191227
}
192228

193229
if (body.type !== 'BlockStatement') {
194-
return body;
230+
return unwrapParentheses(body);
195231
}
196232

197233
const statements = body.body;
@@ -217,10 +253,11 @@ export class OxcAstHost implements AstHost<unknown> {
217253
);
218254
}
219255

220-
return stmt.argument;
256+
return unwrapParentheses(stmt.argument);
221257
}
222258

223259
parseParameters(fn: unknown): unknown[] {
260+
fn = unwrapParentheses(fn);
224261
if (!this.isFunctionExpression(fn)) {
225262
throw new FatalLinkerError(fn as object, 'Unsupported syntax, expected a function.');
226263
}
@@ -229,38 +266,44 @@ export class OxcAstHost implements AstHost<unknown> {
229266
}
230267

231268
isCallExpression(node: unknown): node is CallExpression {
269+
node = unwrapParentheses(node);
270+
232271
return isNode(node) && node.type === 'CallExpression';
233272
}
234273

235274
parseCallee(call: unknown): unknown {
275+
call = unwrapParentheses(call);
236276
if (!this.isCallExpression(call)) {
237277
throw new FatalLinkerError(call as object, 'Unsupported syntax, expected a call expression.');
238278
}
239279

240-
return call.callee;
280+
return unwrapParentheses(call.callee);
241281
}
242282

243283
parseArguments(call: unknown): unknown[] {
284+
call = unwrapParentheses(call);
244285
if (!this.isCallExpression(call)) {
245286
throw new FatalLinkerError(call as object, 'Unsupported syntax, expected a call expression.');
246287
}
247288

248289
const result: unknown[] = [];
249290

250291
for (const arg of call.arguments) {
251-
if (arg.type === 'SpreadElement') {
292+
const unwrappedArg = unwrapParentheses(arg);
293+
if (isNode(unwrappedArg) && unwrappedArg.type === 'SpreadElement') {
252294
throw new FatalLinkerError(
253-
arg as object,
295+
unwrappedArg as object,
254296
'Unsupported syntax, argument not to use spread syntax.',
255297
);
256298
}
257-
result.push(arg);
299+
result.push(unwrappedArg);
258300
}
259301

260302
return result;
261303
}
262304

263305
getRange(node: unknown): Range {
306+
node = unwrapParentheses(node);
264307
if (!isNode(node) || typeof node.start !== 'number' || typeof node.end !== 'number') {
265308
throw new FatalLinkerError(
266309
node as object,
@@ -284,11 +327,12 @@ function isMinifiedBooleanLiteral(
284327
return false;
285328
}
286329

287-
const arg = node.argument;
330+
const arg = unwrapParentheses(node.argument);
288331

289332
return (
290333
node.prefix === true &&
291334
node.operator === '!' &&
335+
isNode(arg) &&
292336
arg.type === 'Literal' &&
293337
typeof arg.value === 'number' &&
294338
(arg.value === 0 || arg.value === 1)

0 commit comments

Comments
 (0)