Skip to content

Commit 67be7d2

Browse files
crisbetoAndrewKushnir
authored andcommitted
fix(compiler-cli): extract parenthesized dependencies during HMR (#59644)
Fixes that the HMR dependency extraction logic wasn't accounting for parenthesized identifiers correctly. PR Close #59644
1 parent 65f51e1 commit 67be7d2

File tree

2 files changed

+48
-3
lines changed

2 files changed

+48
-3
lines changed

packages/compiler-cli/src/ngtsc/hmr/src/extract_dependencies.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,11 @@ class PotentialTopLevelReadsVisitor extends o.RecursiveAstVisitor {
196196
* TypeScript identifiers are used both when referring to a variable (e.g. `console.log(foo)`)
197197
* and for names (e.g. `{foo: 123}`). This function determines if the identifier is a top-level
198198
* variable read, rather than a nested name.
199-
* @param node Identifier to check.
199+
* @param identifier Identifier to check.
200200
*/
201-
private isTopLevelIdentifierReference(node: ts.Identifier): boolean {
202-
const parent = node.parent;
201+
private isTopLevelIdentifierReference(identifier: ts.Identifier): boolean {
202+
let node = identifier as ts.Expression;
203+
let parent = node.parent;
203204

204205
// The parent might be undefined for a synthetic node or if `setParentNodes` is set to false
205206
// when the SourceFile was created. We can account for such cases using the type checker, at
@@ -209,6 +210,15 @@ class PotentialTopLevelReadsVisitor extends o.RecursiveAstVisitor {
209210
return false;
210211
}
211212

213+
// Unwrap parenthesized identifiers, but use the closest parenthesized expression
214+
// as the reference node so that we can check cases like `{prop: ((value))}`.
215+
if (ts.isParenthesizedExpression(parent) && parent.expression === node) {
216+
while (parent && ts.isParenthesizedExpression(parent)) {
217+
node = parent;
218+
parent = parent.parent;
219+
}
220+
}
221+
212222
// Identifier referenced at the top level. Unlikely.
213223
if (ts.isSourceFile(parent)) {
214224
return true;

packages/compiler-cli/test/ngtsc/hmr_spec.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,41 @@ runInEachFileSystem(() => {
549549
);
550550
});
551551

552+
it('should capture parenthesized dependencies', () => {
553+
enableHmr();
554+
env.write(
555+
'test.ts',
556+
`
557+
import {Component, InjectionToken} from '@angular/core';
558+
559+
const token = new InjectionToken<number>('TEST');
560+
const value = 123;
561+
const otherValue = 321;
562+
563+
@Component({
564+
template: '',
565+
providers: [{
566+
provide: token,
567+
useFactory: () => [(value), ((((otherValue))))]
568+
}]
569+
})
570+
export class Cmp {}
571+
`,
572+
);
573+
574+
env.driveMain();
575+
576+
const jsContents = env.getContents('test.js');
577+
const hmrContents = env.driveHmr('test.ts', 'Cmp');
578+
expect(jsContents).toContain(
579+
'ɵɵreplaceMetadata(Cmp, m.default, [i0], [token, value, otherValue, Component]));',
580+
);
581+
expect(jsContents).toContain('useFactory: () => [(value), ((((otherValue))))]');
582+
expect(hmrContents).toContain(
583+
'export default function Cmp_UpdateMetadata(Cmp, ɵɵnamespaces, token, value, otherValue, Component) {',
584+
);
585+
});
586+
552587
it('should preserve eager standalone imports in HMR even if they are not used in the template', () => {
553588
enableHmr({
554589
// Disable class metadata since it can add noise to the test.

0 commit comments

Comments
 (0)