Skip to content

Commit 0cf1116

Browse files
crisbetoatscott
authored andcommitted
fix(compiler-cli): incorrectly detecting forward refs when symbol already exists in file (#48988)
In #48898 the `isForwardRef` flag was added to indicate whether a reference should be wrapped in a `forwardRef`. This logic assumed that the node can't be referring to another node within the same file, however from testing it looks like that's not actually the case, because we hit the same code path when an external import to the same symbol exists already. PR Close #48988
1 parent 57d0c03 commit 0cf1116

File tree

2 files changed

+53
-6
lines changed

2 files changed

+53
-6
lines changed

packages/compiler-cli/src/ngtsc/typecheck/src/checker.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -719,12 +719,19 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
719719
}
720720
const emitted = emittedRef.expression;
721721
if (emitted instanceof WrappedNodeExpr) {
722+
let isForwardReference = false;
723+
if (emitted.node.getStart() > inContext.getStart()) {
724+
const declaration = this.programDriver.getProgram()
725+
.getTypeChecker()
726+
.getTypeAtLocation(emitted.node)
727+
.getSymbol()
728+
?.declarations?.[0];
729+
if (declaration && declaration.getSourceFile() === inContext.getSourceFile()) {
730+
isForwardReference = true;
731+
}
732+
}
722733
// An appropriate identifier is already in scope.
723-
return {
724-
kind,
725-
symbolName: emitted.node.text,
726-
isForwardReference: emitted.node.getStart() > inContext.getStart()
727-
};
734+
return {kind, symbolName: emitted.node.text, isForwardReference};
728735
} else if (
729736
emitted instanceof ExternalExpr && emitted.value.moduleName !== null &&
730737
emitted.value.name !== null) {

packages/core/schematics/test/standalone_migration_spec.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,16 @@ describe('standalone migration', () => {
109109
`);
110110

111111
writeFile('/node_modules/@angular/router/index.d.ts', `
112-
import {ModuleWithProviders} from '@angular/core';
112+
import {ModuleWithProviders, ɵɵDirectiveDeclaration, ɵɵNgModuleDeclaration} from '@angular/core';
113+
114+
export declare class RouterLink {
115+
// Router link is intentionally not standalone.
116+
static ɵdir: ɵɵDirectiveDeclaration<RouterLink, '[routerLink]', never, {}, {}, never, never, false>;
117+
}
113118
114119
export declare class RouterModule {
115120
static forRoot(routes: any[], config?: any): ModuleWithProviders<RouterModule>;
121+
static ɵmod: ɵɵNgModuleDeclaration<CommonModule, [typeof RouterLink], [], [typeof RouterLink]>;
116122
}
117123
`);
118124

@@ -1340,6 +1346,40 @@ describe('standalone migration', () => {
13401346
`));
13411347
});
13421348

1349+
it('should not generate a forwardRef when adding an imported module dependency', async () => {
1350+
writeFile('./comp.ts', `
1351+
import {Component, NgModule} from '@angular/core';
1352+
import {RouterModule} from '@angular/router';
1353+
1354+
@Component({
1355+
selector: 'comp',
1356+
template: '<div routerLink="/"></div>'
1357+
})
1358+
export class MyComp {}
1359+
1360+
@NgModule({imports: [RouterModule], declarations: [MyComp]})
1361+
export class Mod {}
1362+
`);
1363+
1364+
await runMigration('convert-to-standalone');
1365+
1366+
expect(stripWhitespace(tree.readContent('comp.ts'))).toEqual(stripWhitespace(`
1367+
import {Component, NgModule} from '@angular/core';
1368+
import {RouterModule} from '@angular/router';
1369+
1370+
@Component({
1371+
selector: 'comp',
1372+
template: '<div routerLink="/"></div>',
1373+
standalone: true,
1374+
imports: [RouterModule]
1375+
})
1376+
export class MyComp {}
1377+
1378+
@NgModule({imports: [RouterModule, MyComp]})
1379+
export class Mod {}
1380+
`));
1381+
});
1382+
13431383
it('should not duplicate doc strings', async () => {
13441384
writeFile('module.ts', `
13451385
import {NgModule, Directive} from '@angular/core';

0 commit comments

Comments
 (0)