diff --git a/packages/core/schematics/migrations/block-template-entities/util.ts b/packages/core/schematics/migrations/block-template-entities/util.ts index b9675e5028fd..f2521b953fd1 100644 --- a/packages/core/schematics/migrations/block-template-entities/util.ts +++ b/packages/core/schematics/migrations/block-template-entities/util.ts @@ -66,11 +66,7 @@ export class AnalyzedFile { * @param analyzedFiles Map in which to store the results. */ export function analyze(sourceFile: ts.SourceFile, analyzedFiles: Map) { - for (const node of sourceFile.statements) { - if (!ts.isClassDeclaration(node)) { - continue; - } - + forEachClass(sourceFile, node => { // Note: we have a utility to resolve the Angular decorators from a class declaration already. // We don't use it here, because it requires access to the type checker which makes it more // time-consuming to run internally. @@ -86,7 +82,7 @@ export function analyze(sourceFile: ts.SourceFile, analyzedFiles: Map void) { + sourceFile.forEachChild(function walk(node) { + if (ts.isClassDeclaration(node)) { + callback(node); + } + node.forEachChild(walk); + }); +} diff --git a/packages/core/schematics/ng-generate/control-flow-migration/util.ts b/packages/core/schematics/ng-generate/control-flow-migration/util.ts index 5a007d412577..e627a3746760 100644 --- a/packages/core/schematics/ng-generate/control-flow-migration/util.ts +++ b/packages/core/schematics/ng-generate/control-flow-migration/util.ts @@ -18,11 +18,7 @@ import {AnalyzedFile, boundngif, CaseCollector, ElementCollector, ElementToMigra * @param analyzedFiles Map in which to store the results. */ export function analyze(sourceFile: ts.SourceFile, analyzedFiles: Map) { - for (const node of sourceFile.statements) { - if (!ts.isClassDeclaration(node)) { - continue; - } - + forEachClass(sourceFile, node => { // Note: we have a utility to resolve the Angular decorators from a class declaration already. // We don't use it here, because it requires access to the type checker which makes it more // time-consuming to run internally. @@ -38,7 +34,7 @@ export function analyze(sourceFile: ts.SourceFile, analyzedFiles: Map void) { + sourceFile.forEachChild(function walk(node) { + if (ts.isClassDeclaration(node)) { + callback(node); + } + node.forEachChild(walk); + }); +} diff --git a/packages/core/schematics/test/block_template_entities_spec.ts b/packages/core/schematics/test/block_template_entities_spec.ts index cd28bc2550af..cd9c2e15d532 100644 --- a/packages/core/schematics/test/block_template_entities_spec.ts +++ b/packages/core/schematics/test/block_template_entities_spec.ts @@ -276,4 +276,23 @@ describe('Block template entities migration', () => { expect(content).toBe('My email is admin@test.com'); }); + + it('should migrate a component that is not at the top level', async () => { + writeFile('/comp.ts', ` + import {Component} from '@angular/core'; + + function foo() { + @Component({ + template: \`
My email is admin@test.com

This is a brace }

\` + }) + class Comp {} + } + `); + + await runMigration(); + const content = tree.readContent('/comp.ts'); + + expect(content).toContain( + 'template: `
My email is admin@test.com

This is a brace }

`'); + }); }); diff --git a/packages/core/schematics/test/control_flow_migration_spec.ts b/packages/core/schematics/test/control_flow_migration_spec.ts index 7133351b7476..8995f102e53d 100644 --- a/packages/core/schematics/test/control_flow_migration_spec.ts +++ b/packages/core/schematics/test/control_flow_migration_spec.ts @@ -570,6 +570,51 @@ describe('control flow migration', () => { ``, ].join('\n')); }); + + it('should migrate a nested class', async () => { + writeFile('/comp.ts', ` + import {Component} from '@angular/core'; + import {NgIf} from '@angular/common'; + + function foo() { + @Component({ + imports: [NgIf], + template: \`
This should be hidden
\` + }) + class Comp { + toggle = false; + } + } + `); + + await runMigration(); + const content = tree.readContent('/comp.ts'); + + expect(content).toContain( + 'template: `
@if (toggle) {This should be hidden}
`'); + }); + + it('should migrate a nested class', async () => { + writeFile('/comp.ts', ` + import {Component} from '@angular/core'; + import {NgIf} from '@angular/common'; + function foo() { + @Component({ + imports: [NgIf], + template: \`
This should be hidden
\` + }) + class Comp { + toggle = false; + } + } + `); + + await runMigration(); + const content = tree.readContent('/comp.ts'); + + expect(content).toContain( + 'template: `
@if (toggle) {This should be hidden}
`'); + }); }); describe('ngFor', () => { @@ -870,6 +915,59 @@ describe('control flow migration', () => { expect(content).toContain( 'template: `@for (item of items; track item) {

{{item.text}}

}`'); }); + + it('should migrate a nested class', async () => { + writeFile('/comp.ts', ` + import {Component} from '@angular/core'; + import {NgFor} from '@angular/common'; + interface Item { + id: number; + text: string; + } + + function foo() { + @Component({ + imports: [NgFor], + template: \`
  • {{item.text}}
\` + }) + class Comp { + items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}]; + } + } + `); + + await runMigration(); + const content = tree.readContent('/comp.ts'); + + expect(content).toContain( + 'template: `
    @for (item of items; track item) {
  • {{item.text}}
  • }
`'); + }); + + it('should migrate a nested class', async () => { + writeFile('/comp.ts', ` + import {Component} from '@angular/core'; + import {NgFor} from '@angular/common'; + interface Item { + id: number; + text: string; + } + function foo() { + @Component({ + imports: [NgFor], + template: \`
  • {{item.text}}
\` + }) + class Comp { + items: Item[] = [{id: 1, text: 'blah'},{id: 2, text: 'stuff'}]; + } + } + `); + + await runMigration(); + const content = tree.readContent('/comp.ts'); + + expect(content).toContain( + 'template: `
    @for (item of items; track item) {
  • {{item.text}}
  • }
`'); + }); }); describe('ngSwitch', () => { @@ -1131,6 +1229,32 @@ describe('control flow migration', () => { expect(content).toContain( 'template: `
@switch (testOpts) { @case (1) {

Option 1

} @case (2) {

Option 2

} @default {

Option 3

}}
'); }); + + it('should migrate a nested class', async () => { + writeFile( + '/comp.ts', + ` + import {Component} from '@angular/core'; + import {ngSwitch, ngSwitchCase} from '@angular/common'; + function foo() { + @Component({ + template: \`
` + + `

Option 1

` + + `

Option 2

` + + `
\` + }) + class Comp { + testOpts = "1"; + } + } + `); + + await runMigration(); + const content = tree.readContent('/comp.ts'); + + expect(content).toContain( + 'template: `
@switch (testOpts) { @case (1) {

Option 1

} @case (2) {

Option 2

}}
`'); + }); }); describe('nested structures', () => {