refactor(core): static-query migration should not fail for test files (#30034)
Currently when someone runs `ng update` with the static-query migration, the migration can fail with an error saying that the `AOT` compiler could not be created. This can happen if the CLI project contains a test `tsconfig.json` that is picked up by the schematic. Due to the fact that spec tsconfig files cannot be ran with NGC (e.g. test components are not part of a module; not all source files are guaranteed to be included), test `tsconfig` projects will now use a new `test` migration strategy where all queries within tests are left untouched and a TODO is added. PR Close #30034
This commit is contained in:
parent
00ce9aab5b
commit
364250e7a6
|
@ -19,15 +19,15 @@ import {addToImport, createImport, removeFromImport} from './move-import';
|
||||||
/** Entry point for the V8 move-document migration. */
|
/** Entry point for the V8 move-document migration. */
|
||||||
export default function(): Rule {
|
export default function(): Rule {
|
||||||
return (tree: Tree) => {
|
return (tree: Tree) => {
|
||||||
const projectTsConfigPaths = getProjectTsConfigPaths(tree);
|
const {buildPaths, testPaths} = getProjectTsConfigPaths(tree);
|
||||||
const basePath = process.cwd();
|
const basePath = process.cwd();
|
||||||
|
|
||||||
if (!projectTsConfigPaths.length) {
|
if (!buildPaths.length && !testPaths.length) {
|
||||||
throw new SchematicsException(`Could not find any tsconfig file. Cannot migrate DOCUMENT
|
throw new SchematicsException(`Could not find any tsconfig file. Cannot migrate DOCUMENT
|
||||||
to new import source.`);
|
to new import source.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const tsconfigPath of projectTsConfigPaths) {
|
for (const tsconfigPath of [...buildPaths, ...testPaths]) {
|
||||||
runMoveDocumentMigration(tree, tsconfigPath, basePath);
|
runMoveDocumentMigration(tree, tsconfigPath, basePath);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,10 +19,17 @@ import {TypeScriptVisitor, visitAllNodes} from '../../utils/typescript/visit_nod
|
||||||
|
|
||||||
import {NgQueryResolveVisitor} from './angular/ng_query_visitor';
|
import {NgQueryResolveVisitor} from './angular/ng_query_visitor';
|
||||||
import {QueryTemplateStrategy} from './strategies/template_strategy/template_strategy';
|
import {QueryTemplateStrategy} from './strategies/template_strategy/template_strategy';
|
||||||
|
import {QueryTestStrategy} from './strategies/test_strategy/test_strategy';
|
||||||
import {TimingStrategy} from './strategies/timing-strategy';
|
import {TimingStrategy} from './strategies/timing-strategy';
|
||||||
import {QueryUsageStrategy} from './strategies/usage_strategy/usage_strategy';
|
import {QueryUsageStrategy} from './strategies/usage_strategy/usage_strategy';
|
||||||
import {getTransformedQueryCallExpr} from './transform';
|
import {getTransformedQueryCallExpr} from './transform';
|
||||||
|
|
||||||
|
export enum SELECTED_STRATEGY {
|
||||||
|
TEMPLATE,
|
||||||
|
USAGE,
|
||||||
|
TESTS,
|
||||||
|
}
|
||||||
|
|
||||||
/** Entry point for the V8 static-query migration. */
|
/** Entry point for the V8 static-query migration. */
|
||||||
export default function(): Rule {
|
export default function(): Rule {
|
||||||
return (tree: Tree, context: SchematicContext) => {
|
return (tree: Tree, context: SchematicContext) => {
|
||||||
|
@ -34,7 +41,7 @@ export default function(): Rule {
|
||||||
|
|
||||||
/** Runs the V8 migration static-query migration for all determined TypeScript projects. */
|
/** Runs the V8 migration static-query migration for all determined TypeScript projects. */
|
||||||
async function runMigration(tree: Tree, context: SchematicContext) {
|
async function runMigration(tree: Tree, context: SchematicContext) {
|
||||||
const projectTsConfigPaths = getProjectTsConfigPaths(tree);
|
const {buildPaths, testPaths} = getProjectTsConfigPaths(tree);
|
||||||
const basePath = process.cwd();
|
const basePath = process.cwd();
|
||||||
const logger = context.logger;
|
const logger = context.logger;
|
||||||
|
|
||||||
|
@ -44,7 +51,7 @@ async function runMigration(tree: Tree, context: SchematicContext) {
|
||||||
logger.info('https://github.com/angular/angular/pull/28810');
|
logger.info('https://github.com/angular/angular/pull/28810');
|
||||||
logger.info('');
|
logger.info('');
|
||||||
|
|
||||||
if (!projectTsConfigPaths.length) {
|
if (!buildPaths.length && !testPaths.length) {
|
||||||
throw new SchematicsException(
|
throw new SchematicsException(
|
||||||
'Could not find any tsconfig file. Cannot migrate queries ' +
|
'Could not find any tsconfig file. Cannot migrate queries ' +
|
||||||
'to explicit timing.');
|
'to explicit timing.');
|
||||||
|
@ -52,7 +59,7 @@ async function runMigration(tree: Tree, context: SchematicContext) {
|
||||||
|
|
||||||
// In case prompts are supported, determine the desired migration strategy
|
// In case prompts are supported, determine the desired migration strategy
|
||||||
// by creating a choice prompt. By default the template strategy is used.
|
// by creating a choice prompt. By default the template strategy is used.
|
||||||
let isUsageStrategy = false;
|
let selectedStrategy: SELECTED_STRATEGY = SELECTED_STRATEGY.TEMPLATE;
|
||||||
if (supportsPrompt()) {
|
if (supportsPrompt()) {
|
||||||
logger.info('There are two available migration strategies that can be selected:');
|
logger.info('There are two available migration strategies that can be selected:');
|
||||||
logger.info(' • Template strategy - migration tool (short-term gains, rare corrections)');
|
logger.info(' • Template strategy - migration tool (short-term gains, rare corrections)');
|
||||||
|
@ -70,17 +77,26 @@ async function runMigration(tree: Tree, context: SchematicContext) {
|
||||||
default: 'template',
|
default: 'template',
|
||||||
});
|
});
|
||||||
logger.info('');
|
logger.info('');
|
||||||
isUsageStrategy = strategyName === 'usage';
|
selectedStrategy =
|
||||||
|
strategyName === 'usage' ? SELECTED_STRATEGY.USAGE : SELECTED_STRATEGY.TEMPLATE;
|
||||||
} else {
|
} else {
|
||||||
// In case prompts are not supported, we still want to allow developers to opt
|
// In case prompts are not supported, we still want to allow developers to opt
|
||||||
// into the usage strategy by specifying an environment variable. The tests also
|
// into the usage strategy by specifying an environment variable. The tests also
|
||||||
// use the environment variable as there is no headless way to select via prompt.
|
// use the environment variable as there is no headless way to select via prompt.
|
||||||
isUsageStrategy = !!process.env['NG_STATIC_QUERY_USAGE_STRATEGY'];
|
selectedStrategy = !!process.env['NG_STATIC_QUERY_USAGE_STRATEGY'] ? SELECTED_STRATEGY.USAGE :
|
||||||
|
SELECTED_STRATEGY.TEMPLATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
const failures = [];
|
const failures = [];
|
||||||
for (const tsconfigPath of projectTsConfigPaths) {
|
|
||||||
failures.push(...await runStaticQueryMigration(tree, tsconfigPath, basePath, isUsageStrategy));
|
for (const tsconfigPath of buildPaths) {
|
||||||
|
failures.push(...await runStaticQueryMigration(tree, tsconfigPath, basePath, selectedStrategy));
|
||||||
|
}
|
||||||
|
// 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) {
|
||||||
|
failures.push(
|
||||||
|
...await runStaticQueryMigration(tree, tsconfigPath, basePath, SELECTED_STRATEGY.TESTS));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (failures.length) {
|
if (failures.length) {
|
||||||
|
@ -99,7 +115,7 @@ async function runMigration(tree: Tree, context: SchematicContext) {
|
||||||
* lifecycle hook does not need to be static and can be set up with "static: false".
|
* lifecycle hook does not need to be static and can be set up with "static: false".
|
||||||
*/
|
*/
|
||||||
async function runStaticQueryMigration(
|
async function runStaticQueryMigration(
|
||||||
tree: Tree, tsconfigPath: string, basePath: string, isUsageStrategy: boolean) {
|
tree: Tree, tsconfigPath: string, basePath: string, selectedStrategy: SELECTED_STRATEGY) {
|
||||||
const parsed = parseTsconfigFile(tsconfigPath, dirname(tsconfigPath));
|
const parsed = parseTsconfigFile(tsconfigPath, dirname(tsconfigPath));
|
||||||
const host = ts.createCompilerHost(parsed.options, true);
|
const host = ts.createCompilerHost(parsed.options, true);
|
||||||
|
|
||||||
|
@ -119,10 +135,11 @@ async function runStaticQueryMigration(
|
||||||
const rootSourceFiles = program.getRootFileNames().map(f => program.getSourceFile(f) !);
|
const rootSourceFiles = program.getRootFileNames().map(f => program.getSourceFile(f) !);
|
||||||
const printer = ts.createPrinter();
|
const printer = ts.createPrinter();
|
||||||
const analysisVisitors: TypeScriptVisitor[] = [queryVisitor];
|
const analysisVisitors: TypeScriptVisitor[] = [queryVisitor];
|
||||||
|
const failureMessages: string[] = [];
|
||||||
|
|
||||||
// If the "usage" strategy is selected, we also need to add the query visitor
|
// If the "usage" strategy is selected, we also need to add the query visitor
|
||||||
// to the analysis visitors so that query usage in templates can be also checked.
|
// to the analysis visitors so that query usage in templates can be also checked.
|
||||||
if (isUsageStrategy) {
|
if (selectedStrategy === SELECTED_STRATEGY.USAGE) {
|
||||||
analysisVisitors.push(templateVisitor);
|
analysisVisitors.push(templateVisitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +153,7 @@ async function runStaticQueryMigration(
|
||||||
const {resolvedQueries, classMetadata} = queryVisitor;
|
const {resolvedQueries, classMetadata} = queryVisitor;
|
||||||
const {resolvedTemplates} = templateVisitor;
|
const {resolvedTemplates} = templateVisitor;
|
||||||
|
|
||||||
if (isUsageStrategy) {
|
if (selectedStrategy === SELECTED_STRATEGY.USAGE) {
|
||||||
// Add all resolved templates to the class metadata if the usage strategy is used. This
|
// Add all resolved templates to the class metadata if the usage strategy is used. This
|
||||||
// is necessary in order to be able to check component templates for static query usage.
|
// is necessary in order to be able to check component templates for static query usage.
|
||||||
resolvedTemplates.forEach(template => {
|
resolvedTemplates.forEach(template => {
|
||||||
|
@ -146,10 +163,14 @@ async function runStaticQueryMigration(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const strategy: TimingStrategy = isUsageStrategy ?
|
let strategy: TimingStrategy;
|
||||||
new QueryUsageStrategy(classMetadata, typeChecker) :
|
if (selectedStrategy === SELECTED_STRATEGY.USAGE) {
|
||||||
new QueryTemplateStrategy(tsconfigPath, classMetadata, host);
|
strategy = new QueryUsageStrategy(classMetadata, typeChecker);
|
||||||
const failureMessages: string[] = [];
|
} else if (selectedStrategy === SELECTED_STRATEGY.TESTS) {
|
||||||
|
strategy = new QueryTestStrategy();
|
||||||
|
} else {
|
||||||
|
strategy = new QueryTemplateStrategy(tsconfigPath, classMetadata, host);
|
||||||
|
}
|
||||||
|
|
||||||
// In case the strategy could not be set up properly, we just exit the
|
// 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
|
// migration. We don't want to throw an exception as this could mean
|
||||||
|
|
|
@ -184,6 +184,8 @@ export class QueryTemplateStrategy implements TimingStrategy {
|
||||||
console.error('Could not create Angular AOT compiler to determine query timing.');
|
console.error('Could not create Angular AOT compiler to determine query timing.');
|
||||||
console.error('The following diagnostics were detected:\n');
|
console.error('The following diagnostics were detected:\n');
|
||||||
console.error(diagnostics.map(d => d.messageText).join(`\n`));
|
console.error(diagnostics.map(d => d.messageText).join(`\n`));
|
||||||
|
console.error('Please make sure that there is no compilation failure. The migration');
|
||||||
|
console.error('can be rerun with: "ng update @angular/core --from 7 --to 8 --migrate-only"');
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getViewQueryUniqueKey(filePath: string, className: string, propName: string) {
|
private _getViewQueryUniqueKey(filePath: string, className: string, propName: string) {
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/**
|
||||||
|
* @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 {NgQueryDefinition} from '../../angular/query-definition';
|
||||||
|
import {TimingResult, TimingStrategy} from '../timing-strategy';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query timing strategy that is used for queries used within test files. The query
|
||||||
|
* timing is not analyzed for test files as the template strategy cannot work within
|
||||||
|
* spec files (due to missing component modules) and the usage strategy is not capable
|
||||||
|
* of detecting the timing of queries based on how they are used in tests.
|
||||||
|
*/
|
||||||
|
export class QueryTestStrategy implements TimingStrategy {
|
||||||
|
setup() { return true; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects the timing for a given query. For queries within tests, we always
|
||||||
|
* add a TODO and print a message saying that the timing can't be detected for tests.
|
||||||
|
*/
|
||||||
|
detectTiming(query: NgQueryDefinition): TimingResult {
|
||||||
|
return {timing: null, message: 'Timing within tests cannot be detected.'};
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,8 @@ import * as ts from 'typescript';
|
||||||
import {getPropertyNameText} from '../../utils/typescript/property_name';
|
import {getPropertyNameText} from '../../utils/typescript/property_name';
|
||||||
import {NgQueryDefinition, QueryTiming} from './angular/query-definition';
|
import {NgQueryDefinition, QueryTiming} from './angular/query-definition';
|
||||||
|
|
||||||
|
const TODO_COMMENT = 'TODO: add static flag';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms the given query decorator by explicitly specifying the timing based on the
|
* Transforms the given query decorator by explicitly specifying the timing based on the
|
||||||
* determined timing. The updated decorator call expression node will be returned.
|
* determined timing. The updated decorator call expression node will be returned.
|
||||||
|
@ -39,7 +41,9 @@ export function getTransformedQueryCallExpr(
|
||||||
const updatedOptions = ts.updateObjectLiteral(
|
const updatedOptions = ts.updateObjectLiteral(
|
||||||
existingOptions, existingOptions.properties.concat(queryPropertyAssignments));
|
existingOptions, existingOptions.properties.concat(queryPropertyAssignments));
|
||||||
|
|
||||||
if (createTodo) {
|
// In case we want to add a todo and the options do not have the todo
|
||||||
|
// yet, we add the query timing todo as synthetic multi-line comment.
|
||||||
|
if (createTodo && !existingOptions.getFullText().includes(TODO_COMMENT)) {
|
||||||
addQueryTimingTodoToNode(updatedOptions);
|
addQueryTimingTodoToNode(updatedOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,6 +72,6 @@ function addQueryTimingTodoToNode(node: ts.Node) {
|
||||||
end: -1,
|
end: -1,
|
||||||
hasTrailingNewLine: false,
|
hasTrailingNewLine: false,
|
||||||
kind: ts.SyntaxKind.MultiLineCommentTrivia,
|
kind: ts.SyntaxKind.MultiLineCommentTrivia,
|
||||||
text: ' TODO: add static flag '
|
text: ` ${TODO_COMMENT} `
|
||||||
}]);
|
}]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,16 +27,16 @@ const FAILURE_MESSAGE = `Found assignment to template variable.`;
|
||||||
/** Entry point for the V8 template variable assignment schematic. */
|
/** Entry point for the V8 template variable assignment schematic. */
|
||||||
export default function(): Rule {
|
export default function(): Rule {
|
||||||
return (tree: Tree, context: SchematicContext) => {
|
return (tree: Tree, context: SchematicContext) => {
|
||||||
const projectTsConfigPaths = getProjectTsConfigPaths(tree);
|
const {buildPaths, testPaths} = getProjectTsConfigPaths(tree);
|
||||||
const basePath = process.cwd();
|
const basePath = process.cwd();
|
||||||
|
|
||||||
if (!projectTsConfigPaths.length) {
|
if (!buildPaths.length && !testPaths.length) {
|
||||||
throw new SchematicsException(
|
throw new SchematicsException(
|
||||||
'Could not find any tsconfig file. Cannot check templates for template variable ' +
|
'Could not find any tsconfig file. Cannot check templates for template variable ' +
|
||||||
'assignments.');
|
'assignments.');
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const tsconfigPath of projectTsConfigPaths) {
|
for (const tsconfigPath of [...buildPaths, ...testPaths]) {
|
||||||
runTemplateVariableAssignmentCheck(tree, tsconfigPath, basePath, context.logger);
|
runTemplateVariableAssignmentCheck(tree, tsconfigPath, basePath, context.logger);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -29,6 +29,9 @@ describe('move-document migration', () => {
|
||||||
lib: ['es2015'],
|
lib: ['es2015'],
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
writeFile('/angular.json', JSON.stringify({
|
||||||
|
projects: {t: {architect: {build: {options: {tsConfig: './tsconfig.json'}}}}}
|
||||||
|
}));
|
||||||
|
|
||||||
previousWorkingDir = shx.pwd();
|
previousWorkingDir = shx.pwd();
|
||||||
tmpDirPath = getSystemPath(host.root);
|
tmpDirPath = getSystemPath(host.root);
|
||||||
|
|
|
@ -22,7 +22,16 @@ describe('project tsconfig paths', () => {
|
||||||
{my_name: {architect: {build: {options: {tsConfig: './my-custom-config.json'}}}}}
|
{my_name: {architect: {build: {options: {tsConfig: './my-custom-config.json'}}}}}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
expect(getProjectTsConfigPaths(testTree)).toEqual(['my-custom-config.json']);
|
expect(getProjectTsConfigPaths(testTree).buildPaths).toEqual(['my-custom-config.json']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect test tsconfig path inside of angular.json file', () => {
|
||||||
|
testTree.create('/my-test-config.json', '');
|
||||||
|
testTree.create('/angular.json', JSON.stringify({
|
||||||
|
projects: {my_name: {architect: {test: {options: {tsConfig: './my-test-config.json'}}}}}
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(getProjectTsConfigPaths(testTree).testPaths).toEqual(['my-test-config.json']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should detect test tsconfig path inside of .angular.json file', () => {
|
it('should detect test tsconfig path inside of .angular.json file', () => {
|
||||||
|
@ -32,25 +41,23 @@ describe('project tsconfig paths', () => {
|
||||||
{with_tests: {architect: {test: {options: {tsConfig: './my-test-config.json'}}}}}
|
{with_tests: {architect: {test: {options: {tsConfig: './my-test-config.json'}}}}}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
expect(getProjectTsConfigPaths(testTree)).toEqual(['my-test-config.json']);
|
expect(getProjectTsConfigPaths(testTree).testPaths).toEqual(['my-test-config.json']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should detect common tsconfigs if no workspace config could be found', () => {
|
it('should detect common tsconfigs if no workspace config could be found', () => {
|
||||||
testTree.create('/tsconfig.json', '');
|
|
||||||
testTree.create('/src/tsconfig.json', '');
|
|
||||||
testTree.create('/src/tsconfig.app.json', '');
|
testTree.create('/src/tsconfig.app.json', '');
|
||||||
|
testTree.create('/src/tsconfig.spec.json', '');
|
||||||
|
|
||||||
expect(getProjectTsConfigPaths(testTree)).toEqual([
|
expect(getProjectTsConfigPaths(testTree).buildPaths).toEqual(['src/tsconfig.app.json']);
|
||||||
'tsconfig.json', 'src/tsconfig.json', 'src/tsconfig.app.json'
|
expect(getProjectTsConfigPaths(testTree).testPaths).toEqual(['src/tsconfig.spec.json']);
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not return duplicate tsconfig files', () => {
|
it('should not return duplicate tsconfig files', () => {
|
||||||
testTree.create('/tsconfig.json', '');
|
testTree.create('/tsconfig.json', '');
|
||||||
testTree.create('/.angular.json', JSON.stringify({
|
testTree.create('/.angular.json', JSON.stringify({
|
||||||
projects: {app: {architect: {test: {options: {tsConfig: 'tsconfig.json'}}}}}
|
projects: {app: {architect: {build: {options: {tsConfig: 'tsconfig.json'}}}}}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
expect(getProjectTsConfigPaths(testTree)).toEqual(['tsconfig.json']);
|
expect(getProjectTsConfigPaths(testTree).buildPaths).toEqual(['tsconfig.json']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -31,6 +31,9 @@ describe('static-queries migration with template strategy', () => {
|
||||||
lib: ['es2015'],
|
lib: ['es2015'],
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
writeFile('/angular.json', JSON.stringify({
|
||||||
|
projects: {t: {architect: {build: {options: {tsConfig: './tsconfig.json'}}}}}
|
||||||
|
}));
|
||||||
|
|
||||||
warnOutput = [];
|
warnOutput = [];
|
||||||
runner.logger.subscribe(logEntry => {
|
runner.logger.subscribe(logEntry => {
|
||||||
|
@ -515,5 +518,54 @@ describe('static-queries migration with template strategy', () => {
|
||||||
|
|
||||||
expect(console.error).toHaveBeenCalledTimes(0);
|
expect(console.error).toHaveBeenCalledTimes(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should always use the test migration strategy for test tsconfig files', async() => {
|
||||||
|
writeFile('/src/tsconfig.spec.json', JSON.stringify({
|
||||||
|
compilerOptions: {
|
||||||
|
experimentalDecorators: true,
|
||||||
|
lib: ['es2015'],
|
||||||
|
},
|
||||||
|
files: [
|
||||||
|
'test.ts',
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
|
||||||
|
writeFile('/src/test.ts', `
|
||||||
|
import {ViewChild} from '@angular/core';
|
||||||
|
import {AppComponent} from './app.component';
|
||||||
|
|
||||||
|
@Component({template: '<span #test>Test</span>'})
|
||||||
|
class MyTestComponent {
|
||||||
|
@ViewChild('test') query: any;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
writeFile('/src/app.component.ts', `
|
||||||
|
import {Component, ViewChild} from '@angular/core';
|
||||||
|
|
||||||
|
@Component({template: '<span #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]})
|
||||||
|
export class MyModule {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
spyOn(console, 'error').and.callThrough();
|
||||||
|
|
||||||
|
await runMigration();
|
||||||
|
|
||||||
|
expect(console.error).toHaveBeenCalledTimes(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;`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -35,6 +35,9 @@ describe('static-queries migration with usage strategy', () => {
|
||||||
lib: ['es2015'],
|
lib: ['es2015'],
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
writeFile('/angular.json', JSON.stringify({
|
||||||
|
projects: {t: {architect: {build: {options: {tsConfig: './tsconfig.json'}}}}}
|
||||||
|
}));
|
||||||
|
|
||||||
previousWorkingDir = shx.pwd();
|
previousWorkingDir = shx.pwd();
|
||||||
tmpDirPath = getSystemPath(host.root);
|
tmpDirPath = getSystemPath(host.root);
|
||||||
|
@ -598,6 +601,9 @@ describe('static-queries migration with usage strategy', () => {
|
||||||
// Move the tsconfig into a subdirectory. This ensures that the update is properly
|
// Move the tsconfig into a subdirectory. This ensures that the update is properly
|
||||||
// recorded for TypeScript projects not at the schematic tree root.
|
// recorded for TypeScript projects not at the schematic tree root.
|
||||||
host.sync.rename(normalize('/tsconfig.json'), normalize('/src/tsconfig.json'));
|
host.sync.rename(normalize('/tsconfig.json'), normalize('/src/tsconfig.json'));
|
||||||
|
writeFile('/angular.json', JSON.stringify({
|
||||||
|
projects: {t: {architect: {build: {options: {tsConfig: './src/tsconfig.json'}}}}}
|
||||||
|
}));
|
||||||
|
|
||||||
await runMigration();
|
await runMigration();
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,9 @@ describe('template variable assignment migration', () => {
|
||||||
lib: ['es2015'],
|
lib: ['es2015'],
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
writeFile('/angular.json', JSON.stringify({
|
||||||
|
projects: {t: {architect: {build: {options: {tsConfig: './tsconfig.json'}}}}}
|
||||||
|
}));
|
||||||
|
|
||||||
warnOutput = [];
|
warnOutput = [];
|
||||||
runner.logger.subscribe(logEntry => {
|
runner.logger.subscribe(logEntry => {
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
import {normalize} from '@angular-devkit/core';
|
import {normalize} from '@angular-devkit/core';
|
||||||
import {Tree} from '@angular-devkit/schematics';
|
import {Tree} from '@angular-devkit/schematics';
|
||||||
|
import {WorkspaceProject} from '@schematics/angular/utility/workspace-models';
|
||||||
|
|
||||||
/** Name of the default Angular CLI workspace configuration files. */
|
/** Name of the default Angular CLI workspace configuration files. */
|
||||||
const defaultWorkspaceConfigPaths = ['/angular.json', '/.angular.json'];
|
const defaultWorkspaceConfigPaths = ['/angular.json', '/.angular.json'];
|
||||||
|
@ -16,13 +17,11 @@ const defaultWorkspaceConfigPaths = ['/angular.json', '/.angular.json'];
|
||||||
* Gets all tsconfig paths from a CLI project by reading the workspace configuration
|
* Gets all tsconfig paths from a CLI project by reading the workspace configuration
|
||||||
* and looking for common tsconfig locations.
|
* and looking for common tsconfig locations.
|
||||||
*/
|
*/
|
||||||
export function getProjectTsConfigPaths(tree: Tree): string[] {
|
export function getProjectTsConfigPaths(tree: Tree): {buildPaths: string[], testPaths: string[]} {
|
||||||
// Start with some tsconfig paths that are generally used within CLI projects.
|
// Start with some tsconfig paths that are generally used within CLI projects. Note
|
||||||
const tsconfigPaths = new Set<string>([
|
// that we are not interested in IDE-specific tsconfig files (e.g. /tsconfig.json)
|
||||||
'tsconfig.json',
|
const buildPaths = new Set<string>(['src/tsconfig.app.json']);
|
||||||
'src/tsconfig.json',
|
const testPaths = new Set<string>(['src/tsconfig.spec.json']);
|
||||||
'src/tsconfig.app.json',
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Add any tsconfig directly referenced in a build or test task of the angular.json workspace.
|
// Add any tsconfig directly referenced in a build or test task of the angular.json workspace.
|
||||||
const workspace = getWorkspaceConfigGracefully(tree);
|
const workspace = getWorkspaceConfigGracefully(tree);
|
||||||
|
@ -30,23 +29,38 @@ export function getProjectTsConfigPaths(tree: Tree): string[] {
|
||||||
if (workspace) {
|
if (workspace) {
|
||||||
const projects = Object.keys(workspace.projects).map(name => workspace.projects[name]);
|
const projects = Object.keys(workspace.projects).map(name => workspace.projects[name]);
|
||||||
for (const project of projects) {
|
for (const project of projects) {
|
||||||
['build', 'test'].forEach(targetName => {
|
const buildPath = getTargetTsconfigPath(project, 'build');
|
||||||
if (project.targets && project.targets[targetName] && project.targets[targetName].options &&
|
const testPath = getTargetTsconfigPath(project, 'test');
|
||||||
project.targets[targetName].options.tsConfig) {
|
|
||||||
tsconfigPaths.add(normalize(project.targets[targetName].options.tsConfig));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (project.architect && project.architect[targetName] &&
|
if (buildPath) {
|
||||||
project.architect[targetName].options &&
|
buildPaths.add(buildPath);
|
||||||
project.architect[targetName].options.tsConfig) {
|
}
|
||||||
tsconfigPaths.add(normalize(project.architect[targetName].options.tsConfig));
|
|
||||||
}
|
if (testPath) {
|
||||||
});
|
testPaths.add(testPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter out tsconfig files that don't exist in the CLI project.
|
// Filter out tsconfig files that don't exist in the CLI project.
|
||||||
return Array.from(tsconfigPaths).filter(p => tree.exists(p));
|
return {
|
||||||
|
buildPaths: Array.from(buildPaths).filter(p => tree.exists(p)),
|
||||||
|
testPaths: Array.from(testPaths).filter(p => tree.exists(p)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the tsconfig path from the given target within the specified project. */
|
||||||
|
function getTargetTsconfigPath(project: WorkspaceProject, targetName: string): string|null {
|
||||||
|
if (project.targets && project.targets[targetName] && project.targets[targetName].options &&
|
||||||
|
project.targets[targetName].options.tsConfig) {
|
||||||
|
return normalize(project.targets[targetName].options.tsConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (project.architect && project.architect[targetName] && project.architect[targetName].options &&
|
||||||
|
project.architect[targetName].options.tsConfig) {
|
||||||
|
return normalize(project.architect[targetName].options.tsConfig);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue