Skip to content

Commit a6d5aab

Browse files
committed
refactor(@angular/build): use prefix tagging for object literal detection in StringAstFactory
1 parent a3c5817 commit a6d5aab

2 files changed

Lines changed: 32 additions & 10 deletions

File tree

packages/angular/build/src/tools/angular/linker/string-ast-factory.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,28 +21,36 @@ import type {
2121
Parameter,
2222
} from '@angular/compiler-cli/src/ngtsc/translator/src/api/ast_factory';
2323

24+
const OBJECT_LITERAL_PREFIX = '\0obj_';
25+
2426
/**
2527
* An implementation of `AstFactory` that generates JavaScript code strings directly.
2628
*/
2729
export class StringAstFactory implements AstFactory<string, unknown, string> {
2830
constructor(private readonly sourceCode: string = '') {}
2931

3032
private render(expr: unknown): string {
33+
let rendered: string;
3134
if (typeof expr === 'string') {
32-
return expr;
33-
}
34-
if (
35+
rendered = expr;
36+
} else if (
3537
typeof expr === 'object' &&
3638
expr !== null &&
3739
typeof (expr as { start?: number; end?: number }).start === 'number' &&
3840
typeof (expr as { end?: number }).end === 'number'
3941
) {
4042
const { start, end } = expr as { start: number; end: number };
4143

42-
return this.sourceCode.slice(start, end);
44+
rendered = this.sourceCode.slice(start, end);
45+
} else {
46+
rendered = String(expr);
4347
}
4448

45-
return String(expr);
49+
if (rendered.startsWith(OBJECT_LITERAL_PREFIX)) {
50+
return rendered.slice(OBJECT_LITERAL_PREFIX.length);
51+
}
52+
53+
return rendered;
4654
}
4755

4856
/**
@@ -177,9 +185,8 @@ export class StringAstFactory implements AstFactory<string, unknown, string> {
177185

178186
createArrowFunctionExpression(parameters: Parameter<string>[], body: unknown): string {
179187
const params = parameters.map((p) => p.name).join(', ');
188+
const isObjectLiteral = typeof body === 'string' && body.startsWith(OBJECT_LITERAL_PREFIX);
180189
const renderedBody = this.render(body);
181-
const isObjectLiteral =
182-
renderedBody.startsWith('{') && renderedBody.endsWith('}') && !renderedBody.includes(';');
183190
const formattedBody = isObjectLiteral ? `(${renderedBody})` : renderedBody;
184191

185192
return `(${params}) => ${formattedBody}`;
@@ -222,7 +229,7 @@ export class StringAstFactory implements AstFactory<string, unknown, string> {
222229
return `${key}: ${this.render(p.value)}`;
223230
});
224231

225-
return `{\n${props.join(',\n')}\n}`;
232+
return `${OBJECT_LITERAL_PREFIX}{\n${props.join(',\n')}\n}`;
226233
}
227234

228235
createParenthesizedExpression(expression: unknown): string {

packages/angular/build/src/tools/angular/linker/string-ast-factory_spec.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,15 @@ describe('StringAstFactory', () => {
6363
{ kind: 'property' as const, propertyName: 'foo', value: '1', quoted: false },
6464
{ kind: 'property' as const, propertyName: 'foo-bar', value: '2', quoted: true },
6565
];
66-
expect(factory.createObjectLiteral(props)).toBe('{\nfoo: 1,\n"foo-bar": 2\n}');
66+
expect(factory.createObjectLiteral(props)).toContain('{\nfoo: 1,\n"foo-bar": 2\n}');
6767
});
6868

6969
it('should correctly format object literals with spread properties', () => {
7070
const props = [
7171
{ kind: 'property' as const, propertyName: 'foo', value: '1', quoted: false },
7272
{ kind: 'spread' as const, expression: 'bar' },
7373
];
74-
expect(factory.createObjectLiteral(props)).toBe('{\nfoo: 1,\n...bar\n}');
74+
expect(factory.createObjectLiteral(props)).toContain('{\nfoo: 1,\n...bar\n}');
7575
});
7676
});
7777

@@ -165,6 +165,21 @@ describe('StringAstFactory', () => {
165165
expect(factory.createArrowFunctionExpression(params, 'a + b')).toBe('(a, b) => a + b');
166166
});
167167

168+
it('should parenthesize object literal expression bodies in arrow functions even when containing semicolons', () => {
169+
const params = [{ name: 'a', type: null }];
170+
const obj = factory.createObjectLiteral([
171+
{
172+
kind: 'property',
173+
propertyName: 'factory',
174+
value: '() => { return new Service(); }',
175+
quoted: false,
176+
},
177+
]);
178+
expect(factory.createArrowFunctionExpression(params, obj)).toBe(
179+
'(a) => ({\nfactory: () => { return new Service(); }\n})',
180+
);
181+
});
182+
168183
it('should format statements', () => {
169184
expect(factory.createBlock(['foo();', 'bar();'])).toBe('{\nfoo();\nbar();\n}');
170185
expect(factory.createIfStatement('cond', 'foo();', 'bar();')).toBe(

0 commit comments

Comments
 (0)