feat(core): undecorated-classes-with-decorated-fields migration should handle classes with lifecycle hooks (#36921)
As of v10, undecorated classes using Angular features are no longer supported. In v10, we plan on removing the undecorated classes compatibility code in ngtsc. This means that old patterns for undecorated classes will result in compilation errors. We had a migration for this in v9 already, but it looks like the migration does not handle cases where classes uses lifecycle hooks. This is handled in the ngtsc compatibility code, and we should handle it similarly in migrations too. This has not been outlined in the migration plan initially, but an appendix has been added for v10 to the plan document. https://hackmd.io/vuQfavzfRG6KUCtU7oK_EA?both. Note: The migration is unable to determine whether a given undecorated class that only defines `ngOnDestroy` is a directive or an actual service. This means that in some cases the migration cannot do more than adding a TODO and printing an failure. Certainly there are more ways to determine the type of such classes, but it would involve metadata and NgModule analysis. This is out of scope for this migration. PR Close #36921
This commit is contained in:

committed by
Alex Rickabaugh

parent
20cc3ab37e
commit
c6ecdc9a81
@ -18,6 +18,7 @@ describe('Undecorated classes with decorated fields migration', () => {
|
||||
let tree: UnitTestTree;
|
||||
let tmpDirPath: string;
|
||||
let previousWorkingDir: string;
|
||||
let warnings: string[];
|
||||
|
||||
beforeEach(() => {
|
||||
runner = new SchematicTestRunner('test', require.resolve('../migrations.json'));
|
||||
@ -29,6 +30,13 @@ describe('Undecorated classes with decorated fields migration', () => {
|
||||
projects: {t: {architect: {build: {options: {tsConfig: './tsconfig.json'}}}}}
|
||||
}));
|
||||
|
||||
warnings = [];
|
||||
runner.logger.subscribe(entry => {
|
||||
if (entry.level === 'warn') {
|
||||
warnings.push(entry.message);
|
||||
}
|
||||
});
|
||||
|
||||
previousWorkingDir = shx.pwd();
|
||||
tmpDirPath = getSystemPath(host.root);
|
||||
|
||||
@ -228,6 +236,45 @@ describe('Undecorated classes with decorated fields migration', () => {
|
||||
expect(tree.readContent('/index.ts')).toContain(`@Directive()\nexport class Base {`);
|
||||
});
|
||||
|
||||
it('should migrate undecorated class that uses "ngOnChanges" lifecycle hook',
|
||||
() => assertLifecycleHookMigrated('ngOnChanges'));
|
||||
it('should migrate undecorated class that uses "ngOnInit" lifecycle hook',
|
||||
() => assertLifecycleHookMigrated('ngOnInit'));
|
||||
it('should migrate undecorated class that uses "ngDoCheck" lifecycle hook',
|
||||
() => assertLifecycleHookMigrated('ngDoCheck'));
|
||||
it('should migrate undecorated class that uses "ngAfterViewInit" lifecycle hook',
|
||||
() => assertLifecycleHookMigrated('ngAfterViewInit'));
|
||||
it('should migrate undecorated class that uses "ngAfterViewChecked" lifecycle hook',
|
||||
() => assertLifecycleHookMigrated('ngAfterViewChecked'));
|
||||
it('should migrate undecorated class that uses "ngAfterContentInit" lifecycle hook',
|
||||
() => assertLifecycleHookMigrated('ngAfterContentInit'));
|
||||
it('should migrate undecorated class that uses "ngAfterContentChecked" lifecycle hook',
|
||||
() => assertLifecycleHookMigrated('ngAfterContentChecked'));
|
||||
|
||||
it(`should report an error and add a TODO for undecorated classes that only define ` +
|
||||
`the "ngOnDestroy" lifecycle hook`,
|
||||
async () => {
|
||||
writeFile('/index.ts', `
|
||||
import { Input } from '@angular/core';
|
||||
|
||||
export class SomeClassWithAngularFeatures {
|
||||
ngOnDestroy() {
|
||||
// noop for testing
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
|
||||
expect(warnings.length).toBe(1);
|
||||
expect(warnings[0])
|
||||
.toMatch(
|
||||
'index.ts@4:7: Class uses Angular features but cannot be migrated automatically. ' +
|
||||
'Please add an appropriate Angular decorator.');
|
||||
expect(tree.readContent('/index.ts'))
|
||||
.toMatch(/TODO: Add Angular decorator\.\nexport class SomeClassWithAngularFeatures {/);
|
||||
});
|
||||
|
||||
it('should add @Directive to undecorated derived classes of a migrated class', async () => {
|
||||
writeFile('/index.ts', `
|
||||
import { Input, Directive, NgModule } from '@angular/core';
|
||||
@ -314,6 +361,22 @@ describe('Undecorated classes with decorated fields migration', () => {
|
||||
expect(error).toBe(null);
|
||||
});
|
||||
|
||||
async function assertLifecycleHookMigrated(lifecycleHookName: string) {
|
||||
writeFile('/index.ts', `
|
||||
import { Input } from '@angular/core';
|
||||
|
||||
export class SomeClassWithAngularFeatures {
|
||||
${lifecycleHookName}() {
|
||||
// noop for testing
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
expect(tree.readContent('/index.ts'))
|
||||
.toContain(`@Directive()\nexport class SomeClassWithAngularFeatures {`);
|
||||
}
|
||||
|
||||
function writeFile(filePath: string, contents: string) {
|
||||
host.sync.write(normalize(filePath), virtualFs.stringToFileBuffer(contents));
|
||||
}
|
||||
|
Reference in New Issue
Block a user