fix(core): resolve component import by exact specifier in route lazy-loading schematic

Avoid substring matching on importClause.getText() which caused suffix collisions (e.g., BarComponent vs FooBarComponent). Use AST-based matching for default and named (including aliased) imports to reliably resolve the correct import path when generating loadComponent.
This commit is contained in:
tomer953
2025-08-12 10:21:29 +03:00
committed by Pawel Kozlowski
parent 7ba8929504
commit 8fa6617352
2 changed files with 73 additions and 3 deletions
@@ -278,9 +278,26 @@ function migrateRoute(
return routeMigrationResults;
}
const componentImport = route.routeFileImports.find((importDecl) =>
importDecl.importClause?.getText().includes(componentClassName),
)!;
// Resolve the import that provides this component by exact specifier match
// Handles default imports, named imports, and aliases (e.g., `import { Foo as Bar }`).
const componentImport = route.routeFileImports.find((importDecl) => {
const clause = importDecl.importClause;
if (!clause) return false;
// Default import: import FooComponent from '...'
if (clause.name && ts.isIdentifier(clause.name) && clause.name.text === componentClassName) {
return true;
}
// Named imports: import { FooComponent } from '...'
const named = clause.namedBindings;
if (named && ts.isNamedImports(named)) {
return named.elements.some((el: ts.ImportSpecifier) => {
// Support alias: import { Foo as Bar }
const importedName = el.propertyName ? el.propertyName.text : el.name.text;
return importedName === componentClassName;
});
}
return false;
})!;
// remove single and double quotes from the import path
let componentImportPath = ts.isStringLiteral(componentImport?.moduleSpecifier)
@@ -850,6 +850,59 @@ describe('route lazy loading migration', () => {
);
});
it('should resolve the correct import when one component name is a suffix of another', async () => {
writeFile(
'app.module.ts',
`
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
import {FooBarComponent} from './foo-bar';
import {BarComponent} from './bar';
@NgModule({
imports: [RouterModule.forRoot([
{path: 'foo-bar', component: FooBarComponent},
{path: 'bar', component: BarComponent},
])],
})
export class AppModule {}
`,
);
writeFile(
'foo-bar.ts',
`
import {Component} from '@angular/core';
@Component({template: 'foo bar', standalone: true})
export class FooBarComponent {}
`,
);
writeFile(
'bar.ts',
`
import {Component} from '@angular/core';
@Component({template: 'bar', standalone: true})
export class BarComponent {}
`,
);
await runMigration('route-lazy-loading');
const result = stripWhitespace(tree.readContent('app.module.ts'));
expect(result).toContain(
stripWhitespace(
`{path: 'foo-bar', loadComponent: () => import('./foo-bar').then(m => m.FooBarComponent)}`,
),
);
expect(result).toContain(
stripWhitespace(
`{path: 'bar', loadComponent: () => import('./bar').then(m => m.BarComponent)}`,
),
);
});
// TODO: support multiple imports of components
// ex import * as Components from './components';
// export const MenuRoutes: Routes = [