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
41 changes: 26 additions & 15 deletions packages/core/schematics/migrations/static-queries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,19 @@ async function runMigration(tree: Tree, context: SchematicContext) {
logger.info('In preparation for Ivy, developers can now explicitly specify the');
logger.info('timing of their queries. Read more about this here:');
logger.info('https://github.com/angular/angular/pull/28810');
logger.info('');

if (!buildPaths.length && !testPaths.length) {
throw new SchematicsException(
'Could not find any tsconfig file. Cannot migrate queries ' +
'to explicit timing.');
}

const analyzedFiles = new Set<string>();
const buildProjects = new Set<AnalyzedProject>();
const failures = [];

for (const tsconfigPath of buildPaths) {
const project = analyzeProject(tree, tsconfigPath, basePath);
const project = analyzeProject(tree, tsconfigPath, basePath, analyzedFiles);
if (project) {
buildProjects.add(project);
}
Expand All @@ -83,14 +83,15 @@ async function runMigration(tree: Tree, context: SchematicContext) {
// For the "test" tsconfig projects we always want to use the test strategy as
// we can't detect the proper timing within spec files.
for (const tsconfigPath of testPaths) {
const project = await analyzeProject(tree, tsconfigPath, basePath);
const project = await analyzeProject(tree, tsconfigPath, basePath, analyzedFiles);
if (project) {
failures.push(
...await runStaticQueryMigration(tree, project, SELECTED_STRATEGY.TESTS, logger));
}
}

if (failures.length) {
logger.info('');
logger.info('Some queries could not be migrated automatically. Please go');
logger.info('through those manually and apply the appropriate timing:');
failures.forEach(failure => logger.warn(`⮑ ${failure}`));
Expand All @@ -103,7 +104,8 @@ async function runMigration(tree: Tree, context: SchematicContext) {
* Analyzes the given TypeScript project by looking for queries that need to be
* migrated. In case there are no queries that can be migrated, null is returned.
*/
function analyzeProject(tree: Tree, tsconfigPath: string, basePath: string):
function analyzeProject(
tree: Tree, tsconfigPath: string, basePath: string, analyzedFiles: Set<string>):
AnalyzedProject|null {
const parsed = parseTsconfigFile(tsconfigPath, dirname(tsconfigPath));
const host = ts.createCompilerHost(parsed.options, true);
Expand All @@ -125,7 +127,16 @@ function analyzeProject(tree: Tree, tsconfigPath: string, basePath: string):

// Analyze all project source-files and collect all queries that
// need to be migrated.
sourceFiles.forEach(sourceFile => queryVisitor.visitNode(sourceFile));
sourceFiles.forEach(sourceFile => {
const relativePath = relative(basePath, sourceFile.fileName);

// Only look for queries within the current source files if the
// file has not been analyzed before.
if (!analyzedFiles.has(relativePath)) {
analyzedFiles.add(relativePath);
queryVisitor.visitNode(sourceFile);
}
});

if (queryVisitor.resolvedQueries.size === 0) {
return null;
Expand All @@ -142,7 +153,7 @@ function analyzeProject(tree: Tree, tsconfigPath: string, basePath: string):
*/
async function runStaticQueryMigration(
tree: Tree, project: AnalyzedProject, selectedStrategy: SELECTED_STRATEGY,
logger: logging.LoggerApi) {
logger: logging.LoggerApi): Promise<string[]> {
const {sourceFiles, typeChecker, host, queryVisitor, tsconfigPath, basePath} = project;
const printer = ts.createPrinter();
const failureMessages: string[] = [];
Expand Down Expand Up @@ -179,22 +190,22 @@ async function runStaticQueryMigration(
try {
strategy.setup();
} catch (e) {
// In case the strategy could not be set up properly, we just exit the
// migration. We don't want to throw an exception as this could mean
// that other migrations are interrupted.
logger.warn(
`Could not setup migration strategy for "${project.tsconfigPath}". The ` +
`following error has been reported:`);
if (selectedStrategy === SELECTED_STRATEGY.TEMPLATE) {
logger.warn(
`The template migration strategy uses the Angular compiler ` +
`internally and therefore projects that no longer build successfully after ` +
`the update cannot use the template migration strategy. Please ensure ` +
`there are no AOT compilation errors.`);
`there are no AOT compilation errors.\n`);
}
logger.error(e);
// In case the strategy could not be set up properly, we just exit the
// migration. We don't want to throw an exception as this could mean
// that other migrations are interrupted.
logger.warn(
`Could not setup migration strategy for "${project.tsconfigPath}". The ` +
`following error has been reported:\n`);
logger.error(`${e.toString()}\n`);
logger.info(
'Migration can be rerun with: "ng update @angular/core --from 7 --to 8 --migrate-only"');
'Migration can be rerun with: "ng update @angular/core --from 7 --to 8 --migrate-only"\n');
return [];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,7 @@ export class QueryTemplateStrategy implements TimingStrategy {
}

private _createDiagnosticsError(diagnostics: (ts.Diagnostic|Diagnostic)[]) {
return new Error(
`Could not create Angular AOT compiler to determine query timing.\n` +
`The following diagnostics were detected:\n` +
`${diagnostics.map(d => d.messageText).join(`\n `)}\n` +
`Please make sure that there is no AOT compilation failure.`);
return new Error(`${diagnostics.map(d => d.messageText).join(`\n `)}`);
}

private _getViewQueryUniqueKey(filePath: string, className: string, propName: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export function getTransformedQueryCallExpr(
// we create a transformation failure message that shows developers that they need
// to set the query timing manually to the determined query timing.
if (timing !== null) {
failureMessage = 'Cannot update query declaration to explicit timing. Please manually ' +
failureMessage = 'Cannot update query to set explicit timing. Please manually ' +
`set the query timing to: "{static: ${(timing === QueryTiming.STATIC).toString()}}"`;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -515,8 +515,6 @@ describe('static-queries migration with template strategy', () => {
await runMigration();

expect(errorOutput.length).toBe(1);
expect(errorOutput[0])
.toMatch(/^Error: Could not create Angular AOT compiler to determine query timing./);
expect(errorOutput[0]).toMatch(/Cannot determine the module for class MyComp/);
});

Expand Down Expand Up @@ -604,7 +602,7 @@ describe('static-queries migration with template strategy', () => {
.toContain(`@ViewChild('myRef', /* TODO: add static flag */ myOptionsVar) query: any;`);
expect(warnOutput.length).toBe(1);
expect(warnOutput[0])
.toMatch(/^⮑ {3}index.ts@8:11: Cannot update query declaration to explicit timing./);
.toMatch(/^⮑ {3}index.ts@8:11: Cannot update query to set explicit timing./);
expect(warnOutput[0]).toMatch(/Please manually set the query timing to.*static: true/);
});

Expand Down Expand Up @@ -673,15 +671,49 @@ describe('static-queries migration with template strategy', () => {
export class MyModule {}
`);

spyOn(console, 'error').and.callThrough();

await runMigration();

expect(console.error).toHaveBeenCalledTimes(0);
expect(errorOutput.length).toBe(0);
expect(tree.readContent('/src/test.ts'))
.toContain(`@ViewChild('test', /* TODO: add static flag */ {}) query: any;`);
expect(tree.readContent('/src/app.component.ts'))
.toContain(`@ViewChild('test', { static: true }) query: any;`);
});

it('should not fall back to test strategy if selected strategy fails', async() => {
writeFile('/src/tsconfig.spec.json', JSON.stringify({
compilerOptions: {
experimentalDecorators: true,
lib: ['es2015'],
},
files: [
'test.ts',
],
}));

writeFile('/src/test.ts', `import * as mod from './app.module';`);
writeFile('/src/app.component.ts', `
import {Component, ViewChild} from '@angular/core';

@Component({template: '<span #test>Test</span>'})
export class AppComponent {
@ViewChild('test') query: any;
}
`);

writeFile('/src/app.module.ts', `
import {NgModule} from '@angular/core';
import {AppComponent} from './app.component';

@NgModule({declarations: [AppComponent, ThisCausesAnError]})
export class MyModule {}
`);

await runMigration();

expect(errorOutput.length).toBe(1);
expect(errorOutput[0]).toMatch(/Unexpected value 'undefined'/);
expect(tree.readContent('/src/app.component.ts')).toContain(`@ViewChild('test') query: any;`);
});
});
});