Static methods that return a type of ModuleWithProviders currently do not have to specify a type because the generic falls back to any. This is problematic because the type of the actual module being returned is not present in the type information. Since Ivy uses d.ts files exclusively for downstream packages (rather than metadata.json files, for example), we no longer have the type of the actual module being created. For this reason, a generic type should be added for ModuleWithProviders that specifies the module type. This will be required for all users in v10, but will only be necessary for users of Ivy in v9. PR Close #33217
102 lines
4.1 KiB
TypeScript
102 lines
4.1 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import {Rule, SchematicContext, SchematicsException, Tree, UpdateRecorder} from '@angular-devkit/schematics';
|
|
import {dirname, relative} from 'path';
|
|
import * as ts from 'typescript';
|
|
|
|
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
|
|
import {createMigrationCompilerHost} from '../../utils/typescript/compiler_host';
|
|
import {parseTsconfigFile} from '../../utils/typescript/parse_tsconfig';
|
|
|
|
import {Collector} from './collector';
|
|
import {AnalysisFailure, ModuleWithProvidersTransform} from './transform';
|
|
|
|
|
|
|
|
/**
|
|
* Runs the ModuleWithProviders migration for all TypeScript projects in the current CLI workspace.
|
|
*/
|
|
export default function(): Rule {
|
|
return (tree: Tree, ctx: SchematicContext) => {
|
|
const {buildPaths, testPaths} = getProjectTsConfigPaths(tree);
|
|
const basePath = process.cwd();
|
|
const allPaths = [...buildPaths, ...testPaths];
|
|
const failures: string[] = [];
|
|
|
|
ctx.logger.info('------ ModuleWithProviders migration ------');
|
|
|
|
if (!allPaths.length) {
|
|
throw new SchematicsException(
|
|
'Could not find any tsconfig file. Cannot migrate ModuleWithProviders.');
|
|
}
|
|
|
|
for (const tsconfigPath of allPaths) {
|
|
failures.push(...runModuleWithProvidersMigration(tree, tsconfigPath, basePath));
|
|
}
|
|
|
|
if (failures.length) {
|
|
ctx.logger.info('Could not migrate all instances of ModuleWithProviders');
|
|
ctx.logger.info('Please manually fix the following failures:');
|
|
failures.forEach(message => ctx.logger.warn(`⮑ ${message}`));
|
|
} else {
|
|
ctx.logger.info('Successfully migrated all found ModuleWithProviders.');
|
|
}
|
|
|
|
ctx.logger.info('----------------------------------------------');
|
|
};
|
|
}
|
|
|
|
function runModuleWithProvidersMigration(tree: Tree, tsconfigPath: string, basePath: string) {
|
|
const parsed = parseTsconfigFile(tsconfigPath, dirname(tsconfigPath));
|
|
const host = createMigrationCompilerHost(tree, parsed.options, basePath);
|
|
const failures: string[] = [];
|
|
|
|
const program = ts.createProgram(parsed.fileNames, parsed.options, host);
|
|
const typeChecker = program.getTypeChecker();
|
|
const collector = new Collector(typeChecker);
|
|
const sourceFiles = program.getSourceFiles().filter(
|
|
f => !f.isDeclarationFile && !program.isSourceFileFromExternalLibrary(f));
|
|
|
|
// Analyze source files by detecting all modules.
|
|
sourceFiles.forEach(sourceFile => collector.visitNode(sourceFile));
|
|
|
|
const {resolvedModules, resolvedNonGenerics} = collector;
|
|
const transformer = new ModuleWithProvidersTransform(typeChecker, getUpdateRecorder);
|
|
const updateRecorders = new Map<ts.SourceFile, UpdateRecorder>();
|
|
|
|
[...resolvedModules.reduce(
|
|
(failures, m) => failures.concat(transformer.migrateModule(m)), [] as AnalysisFailure[]),
|
|
...resolvedNonGenerics.reduce(
|
|
(failures, t) => failures.concat(transformer.migrateType(t)), [] as AnalysisFailure[])]
|
|
.forEach(({message, node}) => {
|
|
const nodeSourceFile = node.getSourceFile();
|
|
const relativeFilePath = relative(basePath, nodeSourceFile.fileName);
|
|
const {line, character} =
|
|
ts.getLineAndCharacterOfPosition(node.getSourceFile(), node.getStart());
|
|
failures.push(`${relativeFilePath}@${line + 1}:${character + 1}: ${message}`);
|
|
});
|
|
|
|
// Walk through each update recorder and commit the update. We need to commit the
|
|
// updates in batches per source file as there can be only one recorder per source
|
|
// file in order to avoid shift character offsets.
|
|
updateRecorders.forEach(recorder => tree.commitUpdate(recorder));
|
|
|
|
return failures;
|
|
|
|
/** Gets the update recorder for the specified source file. */
|
|
function getUpdateRecorder(sourceFile: ts.SourceFile): UpdateRecorder {
|
|
if (updateRecorders.has(sourceFile)) {
|
|
return updateRecorders.get(sourceFile) !;
|
|
}
|
|
const recorder = tree.beginUpdate(relative(basePath, sourceFile.fileName));
|
|
updateRecorders.set(sourceFile, recorder);
|
|
return recorder;
|
|
}
|
|
}
|