Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions packages/core/schematics/ng-generate/standalone-migration/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,30 @@ export default function(options: Options): Rule {
const {buildPaths, testPaths} = await getProjectTsConfigPaths(tree);
const basePath = process.cwd();
const allPaths = [...buildPaths, ...testPaths];
// TS and Schematic use paths in POSIX format even on Windows. This is needed as otherwise
// string matching such as `sourceFile.fileName.startsWith(pathToMigrate)` might not work.
const pathToMigrate = normalizePath(join(basePath, options.path));
let migratedFiles = 0;

if (!allPaths.length) {
throw new SchematicsException(
'Could not find any tsconfig file. Cannot run the standalone migration.');
}

for (const tsconfigPath of allPaths) {
standaloneMigration(tree, tsconfigPath, basePath, options);
migratedFiles += standaloneMigration(tree, tsconfigPath, basePath, pathToMigrate, options);
}

if (migratedFiles === 0) {
throw new SchematicsException(`Could not find any files to migrate under the path ${
pathToMigrate}. Cannot run the standalone migration.`);
}
};
}

function standaloneMigration(tree: Tree, tsconfigPath: string, basePath: string, options: Options) {
function standaloneMigration(
tree: Tree, tsconfigPath: string, basePath: string, pathToMigrate: string,
options: Options): number {
if (options.path.startsWith('..')) {
throw new SchematicsException(
'Cannot run standalone migration outside of the current project.');
Expand All @@ -64,11 +75,6 @@ function standaloneMigration(tree: Tree, tsconfigPath: string, basePath: string,
}) as NgtscProgram;
const printer = ts.createPrinter();

// TS and Schematic use paths in POSIX format even on Windows.
// This is needed as otherwise string matching such as
// `sourceFile.fileName.startsWith(pathToMigrate)` will not work correctly.
const pathToMigrate = normalizePath(join(basePath, options.path));

if (existsSync(pathToMigrate) && !statSync(pathToMigrate).isDirectory()) {
throw new SchematicsException(`Migration path ${
pathToMigrate} has to be a directory. Cannot run the standalone migration.`);
Expand All @@ -80,8 +86,7 @@ function standaloneMigration(tree: Tree, tsconfigPath: string, basePath: string,
});

if (sourceFiles.length === 0) {
throw new SchematicsException(`Could not find any files to migrate under the path ${
pathToMigrate}. Cannot run the standalone migration.`);
return 0;
}

let pendingChanges: ChangesByFile;
Expand Down Expand Up @@ -122,4 +127,6 @@ function standaloneMigration(tree: Tree, tsconfigPath: string, basePath: string,
tree.delete(relative(basePath, file.fileName));
}
}

return sourceFiles.length;
}
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ function findImportLocation(
target: Reference<NamedClassDeclaration>, inComponent: Reference<ts.ClassDeclaration>,
importMode: PotentialImportMode, typeChecker: TemplateTypeChecker): PotentialImport|null {
const importLocations = typeChecker.getPotentialImportsFor(target, inComponent.node, importMode);
let firstSameFileImport: PotentialImport|null = null;
let firstModuleImport: PotentialImport|null = null;

for (const location of importLocations) {
Expand All @@ -318,12 +319,17 @@ function findImportLocation(
if (location.kind === PotentialImportKind.Standalone) {
return location;
}
if (location.kind === PotentialImportKind.NgModule && !firstModuleImport) {
if (!location.moduleSpecifier && !firstSameFileImport) {
firstSameFileImport = location;
}
if (location.kind === PotentialImportKind.NgModule && !firstModuleImport &&
// ɵ is used for some internal Angular modules that we want to skip over.
!location.symbolName.startsWith('ɵ')) {
firstModuleImport = location;
}
}

return firstModuleImport;
return firstSameFileImport || firstModuleImport || importLocations[0] || null;
}

/**
Expand Down
46 changes: 46 additions & 0 deletions packages/core/schematics/test/standalone_migration_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -970,6 +970,52 @@ describe('standalone migration', () => {
expect(myCompContent).toContain('imports: [ButtonModule]');
});

it('should not reference internal modules', async () => {
writeFile('./should-migrate/module.ts', `
import {NgModule} from '@angular/core';
import {MyComp} from './comp';
import {ɵButtonModule} from '../do-not-migrate/button.module';

@NgModule({imports: [ɵButtonModule], declarations: [MyComp]})
export class Mod {}
`);

writeFile('./should-migrate/comp.ts', `
import {Component} from '@angular/core';

@Component({selector: 'my-comp', template: '<my-button>Hello</my-button>'})
export class MyComp {}
`);

writeFile('./do-not-migrate/button.module.ts', `
import {NgModule, forwardRef} from '@angular/core';
import {MyButton} from './button';

@NgModule({
imports: [forwardRef(() => ɵButtonModule)],
exports: [forwardRef(() => ɵButtonModule)]
})
export class ExporterModule {}

@NgModule({declarations: [MyButton], exports: [MyButton]})
export class ɵButtonModule {}
`);

writeFile('./do-not-migrate/button.ts', `
import {Component} from '@angular/core';

@Component({selector: 'my-button', template: '<ng-content></ng-content>'})
export class MyButton {}
`);

await runMigration('convert-to-standalone', './should-migrate');

const myCompContent = tree.readContent('./should-migrate/comp.ts');
expect(myCompContent)
.toContain(`import { ExporterModule } from '../do-not-migrate/button.module';`);
expect(myCompContent).toContain('imports: [ExporterModule]');
});

it('should migrate tests with a component declared through TestBed', async () => {
writeFile('app.spec.ts', `
import {NgModule, Component} from '@angular/core';
Expand Down