refactor(core): better error message for undecorated-classes-with-di migration (#33315)

Currently if one of the project targets could not be analyzed
due to AOT compiler program failures, we gracefully proceed
with the migration. This is expected, but we should not
print a message at the end of the migration that the migration
was _successful_. The migration was only done partially, hence
it's potentially incomplete and we should make it clear that once
the failures are resolved, the migration should be re-run.

PR Close #33315
This commit is contained in:
Paul Gschwendtner 2019-10-22 11:24:51 +02:00 committed by Andrew Kushnir
parent 7e135f6d3a
commit f197191a5f
2 changed files with 36 additions and 7 deletions

View File

@ -35,6 +35,7 @@ export default function(): Rule {
const {buildPaths} = getProjectTsConfigPaths(tree); const {buildPaths} = getProjectTsConfigPaths(tree);
const basePath = process.cwd(); const basePath = process.cwd();
const failures: string[] = []; const failures: string[] = [];
let programError = false;
if (!buildPaths.length) { if (!buildPaths.length) {
throw new SchematicsException( throw new SchematicsException(
@ -43,10 +44,23 @@ export default function(): Rule {
} }
for (const tsconfigPath of buildPaths) { for (const tsconfigPath of buildPaths) {
failures.push(...runUndecoratedClassesMigration(tree, tsconfigPath, basePath, ctx.logger)); const result = runUndecoratedClassesMigration(tree, tsconfigPath, basePath, ctx.logger);
failures.push(...result.failures);
programError = programError || !!result.programError;
} }
if (failures.length) { if (programError) {
ctx.logger.info('Could not migrate all undecorated classes that use dependency');
ctx.logger.info('injection. Some project targets could not be analyzed due to');
ctx.logger.info('TypeScript program failures.\n');
ctx.logger.info(`${MIGRATION_RERUN_MESSAGE}\n`);
if (failures.length) {
ctx.logger.info('Please manually fix the following failures and re-run the');
ctx.logger.info('migration once the TypeScript program failures are resolved.');
failures.forEach(message => ctx.logger.warn(`${message}`));
}
} else if (failures.length) {
ctx.logger.info('Could not migrate all undecorated classes that use dependency'); ctx.logger.info('Could not migrate all undecorated classes that use dependency');
ctx.logger.info('injection. Please manually fix the following failures:'); ctx.logger.info('injection. Please manually fix the following failures:');
failures.forEach(message => ctx.logger.warn(`${message}`)); failures.forEach(message => ctx.logger.warn(`${message}`));
@ -55,13 +69,14 @@ export default function(): Rule {
} }
function runUndecoratedClassesMigration( function runUndecoratedClassesMigration(
tree: Tree, tsconfigPath: string, basePath: string, logger: logging.LoggerApi): string[] { tree: Tree, tsconfigPath: string, basePath: string,
logger: logging.LoggerApi): {failures: string[], programError?: boolean} {
const failures: string[] = []; const failures: string[] = [];
const programData = gracefullyCreateProgram(tree, basePath, tsconfigPath, logger); const programData = gracefullyCreateProgram(tree, basePath, tsconfigPath, logger);
// Gracefully exit if the program could not be created. // Gracefully exit if the program could not be created.
if (programData === null) { if (programData === null) {
return []; return {failures: [], programError: true};
} }
const {program, compiler} = programData; const {program, compiler} = programData;
@ -101,7 +116,7 @@ function runUndecoratedClassesMigration(
// file in order to avoid shifted character offsets. // file in order to avoid shifted character offsets.
updateRecorders.forEach(recorder => recorder.commitUpdate()); updateRecorders.forEach(recorder => recorder.commitUpdate());
return failures; return {failures};
/** Gets the update recorder for the specified source file. */ /** Gets the update recorder for the specified source file. */
function getUpdateRecorder(sourceFile: ts.SourceFile): UpdateRecorder { function getUpdateRecorder(sourceFile: ts.SourceFile): UpdateRecorder {
@ -153,7 +168,6 @@ function gracefullyCreateProgram(
`\nTypeScript project "${tsconfigPath}" has syntactical errors which could cause ` + `\nTypeScript project "${tsconfigPath}" has syntactical errors which could cause ` +
`an incomplete migration. Please fix the following failures and rerun the migration:`); `an incomplete migration. Please fix the following failures and rerun the migration:`);
logger.error(ts.formatDiagnostics(syntacticDiagnostics, host)); logger.error(ts.formatDiagnostics(syntacticDiagnostics, host));
logger.info(MIGRATION_RERUN_MESSAGE);
return null; return null;
} }
@ -165,7 +179,6 @@ function gracefullyCreateProgram(
} catch (e) { } catch (e) {
logger.warn(`\n${MIGRATION_AOT_FAILURE}. The following project failed: ${tsconfigPath}\n`); logger.warn(`\n${MIGRATION_AOT_FAILURE}. The following project failed: ${tsconfigPath}\n`);
logger.error(`${e.toString()}\n`); logger.error(`${e.toString()}\n`);
logger.info(MIGRATION_RERUN_MESSAGE);
return null; return null;
} }
} }

View File

@ -21,6 +21,7 @@ describe('Undecorated classes with DI migration', () => {
let previousWorkingDir: string; let previousWorkingDir: string;
let warnOutput: string[]; let warnOutput: string[];
let errorOutput: string[]; let errorOutput: string[];
let infoOutput: string[];
beforeEach(() => { beforeEach(() => {
runner = new SchematicTestRunner('test', require.resolve('../migrations.json')); runner = new SchematicTestRunner('test', require.resolve('../migrations.json'));
@ -39,11 +40,14 @@ describe('Undecorated classes with DI migration', () => {
warnOutput = []; warnOutput = [];
errorOutput = []; errorOutput = [];
infoOutput = [];
runner.logger.subscribe(logEntry => { runner.logger.subscribe(logEntry => {
if (logEntry.level === 'warn') { if (logEntry.level === 'warn') {
warnOutput.push(logEntry.message); warnOutput.push(logEntry.message);
} else if (logEntry.level === 'error') { } else if (logEntry.level === 'error') {
errorOutput.push(logEntry.message); errorOutput.push(logEntry.message);
} else if (logEntry.level === 'info') {
infoOutput.push(logEntry.message);
} }
}); });
@ -112,6 +116,10 @@ describe('Undecorated classes with DI migration', () => {
expect(errorOutput.length).toBe(0); expect(errorOutput.length).toBe(0);
expect(warnOutput.length).toBe(1); expect(warnOutput.length).toBe(1);
expect(warnOutput[0]).toMatch(/Class needs to declare an explicit constructor./); expect(warnOutput[0]).toMatch(/Class needs to declare an explicit constructor./);
expect(infoOutput.join(' '))
.toContain(
'Could not migrate all undecorated classes that use ' +
'dependency injection. Please manually fix the following failures');
}); });
it('should add @Directive() decorator to extended base class', async() => { it('should add @Directive() decorator to extended base class', async() => {
@ -1442,6 +1450,10 @@ describe('Undecorated classes with DI migration', () => {
/ensure there are no AOT compilation errors and rerun the migration.*project failed: tsconfig\.json/); /ensure there are no AOT compilation errors and rerun the migration.*project failed: tsconfig\.json/);
expect(errorOutput.length).toBe(1); expect(errorOutput.length).toBe(1);
expect(errorOutput[0]).toMatch(/Cannot determine the module for class TestComp/); expect(errorOutput[0]).toMatch(/Cannot determine the module for class TestComp/);
expect(infoOutput.join(' '))
.toContain(
'Some project targets could not be analyzed due to ' +
'TypeScript program failures');
}); });
it('should gracefully exit migration if project fails with syntactical diagnostic', async() => { it('should gracefully exit migration if project fails with syntactical diagnostic', async() => {
@ -1456,6 +1468,10 @@ describe('Undecorated classes with DI migration', () => {
.toMatch(/project "tsconfig.json" has syntactical errors which could cause/); .toMatch(/project "tsconfig.json" has syntactical errors which could cause/);
expect(errorOutput.length).toBe(1); expect(errorOutput.length).toBe(1);
expect(errorOutput[0]).toMatch(/error TS1005: 'from' expected/); expect(errorOutput[0]).toMatch(/error TS1005: 'from' expected/);
expect(infoOutput.join(' '))
.toContain(
'Some project targets could not be analyzed due to ' +
'TypeScript program failures');
}); });
it('should not throw if resources could not be read', async() => { it('should not throw if resources could not be read', async() => {