Skip to content

Commit 9971510

Browse files
JoostKthePunderWoman
authored andcommitted
fix(core): correctly perform lazy routes migration for components with additional decorators (#58796)
This fixes an issue where the lazy-routes migration would crash for component classes a decorator without arguments in front of the `@Component` decorator (in particular, it needed to be the first decorator). Fixes #58793 PR Close #58796
1 parent 6b057bc commit 9971510

File tree

5 files changed

+77
-15
lines changed

5 files changed

+77
-15
lines changed

packages/compiler-cli/src/ngtsc/annotations/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
export {
1212
forwardRefResolver,
13+
findAngularDecorator,
1314
getAngularDecorators,
1415
isAngularDecorator,
1516
NoopReferencesRegistry,

packages/core/schematics/ng-generate/route-lazy-loading/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ ts_library(
2020
deps = [
2121
"//packages/compiler-cli",
2222
"//packages/compiler-cli/private",
23+
"//packages/compiler-cli/src/ngtsc/annotations",
24+
"//packages/compiler-cli/src/ngtsc/reflection",
2325
"//packages/core/schematics/utils",
2426
"@npm//@angular-devkit/schematics",
2527
"@npm//@types/node",

packages/core/schematics/ng-generate/route-lazy-loading/to-lazy-routes.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88

99
import ts from 'typescript';
1010

11+
import {
12+
ReflectionHost,
13+
TypeScriptReflectionHost,
14+
} from '@angular/compiler-cli/src/ngtsc/reflection/index';
1115
import {ChangeTracker, PendingChange} from '../../utils/change_tracker';
1216

1317
import {findClassDeclaration} from '../../utils/typescript/class_declaration';
@@ -46,6 +50,7 @@ export function migrateFileToLazyRoutes(
4650
skippedRoutes: RouteMigrationData[];
4751
} {
4852
const typeChecker = program.getTypeChecker();
53+
const reflector = new TypeScriptReflectionHost(typeChecker);
4954
const printer = ts.createPrinter();
5055
const tracker = new ChangeTracker(printer);
5156

@@ -58,6 +63,7 @@ export function migrateFileToLazyRoutes(
5863
const {skippedRoutes, migratedRoutes} = migrateRoutesArray(
5964
routeArraysToMigrate,
6065
typeChecker,
66+
reflector,
6167
tracker,
6268
);
6369

@@ -146,6 +152,7 @@ function findRoutesArrayToMigrate(sourceFile: ts.SourceFile, typeChecker: ts.Typ
146152
function migrateRoutesArray(
147153
routesArray: RouteData[],
148154
typeChecker: ts.TypeChecker,
155+
reflector: ReflectionHost,
149156
tracker: ChangeTracker,
150157
): {migratedRoutes: RouteMigrationData[]; skippedRoutes: RouteMigrationData[]} {
151158
const migratedRoutes: RouteMigrationData[] = [];
@@ -159,7 +166,7 @@ function migrateRoutesArray(
159166
migratedRoutes: migrated,
160167
skippedRoutes: toBeSkipped,
161168
importsToRemove: toBeRemoved,
162-
} = migrateRoute(element, route, typeChecker, tracker);
169+
} = migrateRoute(element, route, typeChecker, reflector, tracker);
163170
migratedRoutes.push(...migrated);
164171
skippedRoutes.push(...toBeSkipped);
165172
importsToRemove.push(...toBeRemoved);
@@ -182,6 +189,7 @@ function migrateRoute(
182189
element: ts.ObjectLiteralExpression,
183190
route: RouteData,
184191
typeChecker: ts.TypeChecker,
192+
reflector: ReflectionHost,
185193
tracker: ChangeTracker,
186194
): {
187195
migratedRoutes: RouteMigrationData[];
@@ -207,7 +215,7 @@ function migrateRoute(
207215
migratedRoutes: migrated,
208216
skippedRoutes: toBeSkipped,
209217
importsToRemove: toBeRemoved,
210-
} = migrateRoute(childRoute, route, typeChecker, tracker);
218+
} = migrateRoute(childRoute, route, typeChecker, reflector, tracker);
211219
migratedRoutes.push(...migrated);
212220
skippedRoutes.push(...toBeSkipped);
213221
importsToRemove.push(...toBeRemoved);
@@ -228,7 +236,7 @@ function migrateRoute(
228236
}
229237

230238
// if component is not a standalone component, skip it
231-
if (!isStandaloneComponent(componentDeclaration)) {
239+
if (!isStandaloneComponent(componentDeclaration, reflector)) {
232240
skippedRoutes.push({path: routePath, file: route.routeFilePath});
233241
return routeMigrationResults;
234242
}

packages/core/schematics/ng-generate/route-lazy-loading/util.ts

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,35 @@
77
*/
88

99
import ts from 'typescript';
10+
import {findAngularDecorator} from '@angular/compiler-cli/src/ngtsc/annotations';
11+
import {ReflectionHost} from '@angular/compiler-cli/src/ngtsc/reflection/index';
1012
import {findLiteralProperty} from '../../utils/typescript/property_name';
1113

1214
/**
1315
* Checks whether a component is standalone.
1416
* @param node Class being checked.
17+
* @param reflector The reflection host to use.
1518
*/
16-
export function isStandaloneComponent(node: ts.ClassDeclaration): boolean {
17-
const decorator = node.modifiers?.find((m) => m.kind === ts.SyntaxKind.Decorator);
18-
if (!decorator) {
19+
export function isStandaloneComponent(
20+
node: ts.ClassDeclaration,
21+
reflector: ReflectionHost,
22+
): boolean {
23+
const decorators = reflector.getDecoratorsOfDeclaration(node);
24+
if (decorators === null) {
25+
return false;
26+
}
27+
const decorator = findAngularDecorator(decorators, 'Component', false);
28+
if (decorator === undefined || decorator.args === null || decorator.args.length !== 1) {
1929
return false;
2030
}
2131

22-
if (ts.isCallExpression(decorator.expression)) {
23-
const arg = decorator.expression.arguments[0];
24-
if (ts.isObjectLiteralExpression(arg)) {
25-
const property = findLiteralProperty(arg, 'standalone') as ts.PropertyAssignment;
26-
if (property) {
27-
return property.initializer.getText() === 'true';
28-
} else {
29-
return true; // standalone is true by default in v19
30-
}
32+
const arg = decorator.args[0];
33+
if (ts.isObjectLiteralExpression(arg)) {
34+
const property = findLiteralProperty(arg, 'standalone') as ts.PropertyAssignment;
35+
if (property) {
36+
return property.initializer.getText() === 'true';
37+
} else {
38+
return true; // standalone is true by default in v19
3139
}
3240
}
3341

packages/core/schematics/test/standalone_routes_spec.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,49 @@ describe('route lazy loading migration', () => {
733733
);
734734
});
735735

736+
it('should support components with additional decorators', async () => {
737+
writeFile(
738+
'app.module.ts',
739+
`
740+
import {NgModule} from '@angular/core';
741+
import {RouterModule} from '@angular/router';
742+
import {TestComponent} from './test';
743+
744+
@NgModule({
745+
imports: [RouterModule.forRoot([{path: 'test', component: TestComponent}])],
746+
})
747+
export class AppModule {}
748+
`,
749+
);
750+
751+
writeFile(
752+
'test.ts',
753+
`
754+
import {Component, Directive} from '@angular/core';
755+
756+
function OtherDecorator() {}
757+
758+
@OtherDecorator()
759+
@Component({template: 'hello', standalone: true})
760+
export class TestComponent {}
761+
`,
762+
);
763+
764+
await runMigration('route-lazy-loading');
765+
766+
expect(stripWhitespace(tree.readContent('app.module.ts'))).toContain(
767+
stripWhitespace(`
768+
import {NgModule} from '@angular/core';
769+
import {RouterModule} from '@angular/router';
770+
771+
@NgModule({
772+
imports: [RouterModule.forRoot([{path: 'test', loadComponent: () => import('./test').then(m => m.TestComponent)}])],
773+
})
774+
export class AppModule {}
775+
`),
776+
);
777+
});
778+
736779
it('should not migrate routes if the routes array doesnt have type and is not referenced', async () => {
737780
writeFile(
738781
'routes.ts',

0 commit comments

Comments
 (0)