Skip to content

Commit 5e00e34

Browse files
committed
fix(migrations): move standalone migrations into imports
Normally having a standalone declaration in the `imports` array is an error and something we handle in the conversion to standalone, but tests can end up in this situation, because apps may have separate tsconfigs for the main app and for tests. These changes make it so that we move any incorrectly-defined standalone declarations, even if they aren't part of the current migration.
1 parent e30fb30 commit 5e00e34

File tree

3 files changed

+80
-21
lines changed

3 files changed

+80
-21
lines changed

packages/core/schematics/ng-generate/standalone-migration/standalone-bootstrap.ts

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export function toStandaloneBootstrap(
4141
const languageService = createLanguageService(program, host, rootFileNames, basePath);
4242
const bootstrapCalls: BootstrapCallAnalysis[] = [];
4343
const testObjects: ts.ObjectLiteralExpression[] = [];
44+
const allDeclarations: Reference<ts.ClassDeclaration>[] = [];
4445

4546
for (const sourceFile of sourceFiles) {
4647
sourceFile.forEachChild(function walk(node) {
@@ -59,24 +60,19 @@ export function toStandaloneBootstrap(
5960
testObjects.push(...findTestObjectsToMigrate(sourceFile, typeChecker));
6061
}
6162

62-
if (bootstrapCalls.length > 0) {
63-
const allDeclarations: Reference<ts.ClassDeclaration>[] = [];
64-
65-
for (const call of bootstrapCalls) {
66-
allDeclarations.push(...call.declarations);
67-
migrateBootstrapCall(call, tracker, languageService, typeChecker, printer);
68-
}
63+
for (const call of bootstrapCalls) {
64+
allDeclarations.push(...call.declarations);
65+
migrateBootstrapCall(call, tracker, languageService, typeChecker, printer);
66+
}
6967

70-
if (allDeclarations.length > 0) {
71-
// The previous migrations explicitly skip over bootstrapped
72-
// declarations so we have to migrate them now.
73-
allDeclarations.forEach(
74-
decl => convertNgModuleDeclarationToStandalone(
75-
decl, allDeclarations, tracker, templateTypeChecker));
76-
migrateTestDeclarations(testObjects, allDeclarations, tracker, typeChecker);
77-
}
68+
// The previous migrations explicitly skip over bootstrapped
69+
// declarations so we have to migrate them now.
70+
for (const declaration of allDeclarations) {
71+
convertNgModuleDeclarationToStandalone(
72+
declaration, allDeclarations, tracker, templateTypeChecker);
7873
}
7974

75+
migrateTestDeclarations(testObjects, allDeclarations, tracker, templateTypeChecker, typeChecker);
8076
return tracker.recordChanges();
8177
}
8278

packages/core/schematics/ng-generate/standalone-migration/to-standalone.ts

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ export function toStandalone(
5959
migrateNgModuleClass(node, declarations, tracker, typeChecker, templateTypeChecker);
6060
}
6161

62-
migrateTestDeclarations(testObjectsToMigrate, declarations, tracker, typeChecker);
62+
migrateTestDeclarations(
63+
testObjectsToMigrate, declarations, tracker, templateTypeChecker, typeChecker);
6364
return tracker.recordChanges();
6465
}
6566

@@ -167,7 +168,8 @@ function migrateNgModuleClass(
167168

168169
if (metadata) {
169170
moveDeclarationsToImports(
170-
metadata, allDeclarations.map(decl => decl.node), typeChecker, tracker);
171+
metadata, allDeclarations.map(decl => decl.node), typeChecker, templateTypeChecker,
172+
tracker);
171173
}
172174
}
173175

@@ -181,7 +183,8 @@ function migrateNgModuleClass(
181183
*/
182184
function moveDeclarationsToImports(
183185
literal: ts.ObjectLiteralExpression, allDeclarations: ts.ClassDeclaration[],
184-
typeChecker: ts.TypeChecker, tracker: ChangeTracker): void {
186+
typeChecker: ts.TypeChecker, templateTypeChecker: TemplateTypeChecker,
187+
tracker: ChangeTracker): void {
185188
const declarationsProp = findLiteralProperty(literal, 'declarations');
186189

187190
if (!declarationsProp) {
@@ -202,7 +205,12 @@ function moveDeclarationsToImports(
202205
if (ts.isIdentifier(el)) {
203206
const correspondingClass = findClassDeclaration(el, typeChecker);
204207

205-
if (!correspondingClass || allDeclarations.includes(correspondingClass)) {
208+
if (!correspondingClass ||
209+
// Check whether the declaration is either standalone already or is being converted
210+
// in this migration. We need to check if it's standalone already, in order to correct
211+
// some cases where the main app and the test files are being migrated in separate
212+
// programs.
213+
isStandaloneDeclaration(correspondingClass, allDeclarations, templateTypeChecker)) {
206214
declarationsToCopy.push(el);
207215
} else {
208216
declarationsToPreserve.push(el);
@@ -502,13 +510,15 @@ export function extractDeclarationsFromModule(
502510
/**
503511
* Migrates the `declarations` from a unit test file to standalone.
504512
* @param testObjects Object literals used to configure the testing modules.
513+
* @param declarationsOutsideOfTestFiles Non-testing declarations that are part of this migration.
505514
* @param tracker
515+
* @param templateTypeChecker
506516
* @param typeChecker
507517
*/
508518
export function migrateTestDeclarations(
509519
testObjects: ts.ObjectLiteralExpression[],
510520
declarationsOutsideOfTestFiles: Reference<ts.ClassDeclaration>[], tracker: ChangeTracker,
511-
typeChecker: ts.TypeChecker) {
521+
templateTypeChecker: TemplateTypeChecker, typeChecker: ts.TypeChecker) {
512522
const {decorators, componentImports} = analyzeTestingModules(testObjects, typeChecker);
513523
const allDeclarations: ts.ClassDeclaration[] =
514524
declarationsOutsideOfTestFiles.map(ref => ref.node);
@@ -544,7 +554,7 @@ export function migrateTestDeclarations(
544554
}
545555

546556
for (const obj of testObjects) {
547-
moveDeclarationsToImports(obj, allDeclarations, typeChecker, tracker);
557+
moveDeclarationsToImports(obj, allDeclarations, typeChecker, templateTypeChecker, tracker);
548558
}
549559
}
550560

@@ -636,3 +646,21 @@ function extractMetadataLiteral(decorator: ts.Decorator): ts.ObjectLiteralExpres
636646
decorator.expression.arguments[0] :
637647
null;
638648
}
649+
650+
/**
651+
* Checks whether a class is a standalone declaration.
652+
* @param node Class being checked.
653+
* @param declarationsInMigration Classes that are being converted to standalone in this migration.
654+
* @param templateTypeChecker
655+
*/
656+
function isStandaloneDeclaration(
657+
node: ts.ClassDeclaration, declarationsInMigration: ts.ClassDeclaration[],
658+
templateTypeChecker: TemplateTypeChecker): boolean {
659+
if (declarationsInMigration.includes(node)) {
660+
return true;
661+
}
662+
663+
const metadata =
664+
templateTypeChecker.getDirectiveMetadata(node) || templateTypeChecker.getPipeMetadata(node);
665+
return metadata != null && metadata.isStandalone;
666+
}

packages/core/schematics/test/standalone_migration_spec.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -930,6 +930,41 @@ describe('standalone migration', () => {
930930
`));
931931
});
932932

933+
it('should migrate tests where the declaration is already standalone', async () => {
934+
writeFile('comp.ts', `
935+
import {Component} from '@angular/core';
936+
937+
@Component({selector: 'comp', template: '', standalone: true})
938+
export class MyComp {}
939+
`);
940+
941+
writeFile('app.spec.ts', `
942+
import {TestBed} from '@angular/core/testing';
943+
import {MyComp} from './comp';
944+
945+
describe('bootrstrapping an app', () => {
946+
it('should work', () => {
947+
TestBed.configureTestingModule({declarations: [MyComp]});
948+
expect(() => TestBed.createComponent(MyComp)).not.toThrow();
949+
});
950+
});
951+
`);
952+
953+
await runMigration('convert-to-standalone');
954+
955+
expect(stripWhitespace(tree.readContent('app.spec.ts'))).toContain(stripWhitespace(`
956+
import {TestBed} from '@angular/core/testing';
957+
import {MyComp} from './comp';
958+
959+
describe('bootrstrapping an app', () => {
960+
it('should work', () => {
961+
TestBed.configureTestingModule({imports: [MyComp]});
962+
expect(() => TestBed.createComponent(MyComp)).not.toThrow();
963+
});
964+
});
965+
`));
966+
});
967+
933968
it('should import the module that declares a template dependency', async () => {
934969
writeFile('./should-migrate/module.ts', `
935970
import {NgModule} from '@angular/core';

0 commit comments

Comments
 (0)