fix(core): migration error if program contains files outside of the project (#39790)

Currently all of our migrations are set up to find the tsconfig paths within a project,
create a `Program` out of each and migrate the files inside of the `Program`. The
problem is that the `Program` can include files outside of the project and the CLI
APIs that we use to interact with the file system assume that all files are within
the project.

These changes consolidate the logic, that determines whether a file can be migrated,
in a single place and add an extra check to exclude files outside of the root.

Fixes #39778.

PR Close #39790
This commit is contained in:
Kristiyan Kostadinov 2020-11-20 20:25:30 +01:00 committed by Andrew Kushnir
parent b1ebfb1cab
commit 1a26f6da6e
19 changed files with 72 additions and 64 deletions

View File

@ -10,7 +10,7 @@ import {Rule, SchematicsException, Tree} from '@angular-devkit/schematics';
import {relative} from 'path';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {createMigrationProgram} from '../../utils/typescript/compiler_host';
import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host';
import {findParentAccesses} from './util';
@ -36,8 +36,8 @@ function runNativeAbstractControlParentMigration(
tree: Tree, tsconfigPath: string, basePath: string) {
const {program} = createMigrationProgram(tree, tsconfigPath, basePath);
const typeChecker = program.getTypeChecker();
const sourceFiles = program.getSourceFiles().filter(
f => !f.isDeclarationFile && !program.isSourceFileFromExternalLibrary(f));
const sourceFiles =
program.getSourceFiles().filter(sourceFile => canMigrateFile(basePath, sourceFile, program));
sourceFiles.forEach(sourceFile => {
// We sort the nodes based on their position in the file and we offset the positions by one

View File

@ -11,7 +11,7 @@ import {relative} from 'path';
import * as ts from 'typescript';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {createMigrationProgram} from '../../utils/typescript/compiler_host';
import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host';
import {identifyDynamicQueryNodes, removeOptionsParameter, removeStaticFlag} from './util';
@ -39,8 +39,8 @@ export default function(): Rule {
function runDynamicQueryMigration(tree: Tree, tsconfigPath: string, basePath: string) {
const {program} = createMigrationProgram(tree, tsconfigPath, basePath);
const typeChecker = program.getTypeChecker();
const sourceFiles = program.getSourceFiles().filter(
f => !f.isDeclarationFile && !program.isSourceFileFromExternalLibrary(f));
const sourceFiles =
program.getSourceFiles().filter(sourceFile => canMigrateFile(basePath, sourceFile, program));
const printer = ts.createPrinter();
sourceFiles.forEach(sourceFile => {

View File

@ -31,7 +31,7 @@ export class Rule extends Rules.TypedRule {
sourceFiles.forEach(sourceFile => initialNavigationCollector.visitNode(sourceFile));
const {assignments} = initialNavigationCollector;
const transformer = new InitialNavigationTransform(typeChecker, getUpdateRecorder);
const transformer = new InitialNavigationTransform(getUpdateRecorder);
const updateRecorders = new Map<ts.SourceFile, TslintUpdateRecorder>();
transformer.migrateInitialNavigationAssignments(Array.from(assignments));

View File

@ -10,7 +10,7 @@ import {Rule, SchematicsException, Tree} from '@angular-devkit/schematics';
import {relative} from 'path';
import * as ts from 'typescript';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {createMigrationProgram} from '../../utils/typescript/compiler_host';
import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host';
import {InitialNavigationCollector} from './collector';
import {InitialNavigationTransform} from './transform';
import {UpdateRecorder} from './update_recorder';
@ -36,14 +36,14 @@ function runInitialNavigationMigration(tree: Tree, tsconfigPath: string, basePat
const {program} = createMigrationProgram(tree, tsconfigPath, basePath);
const typeChecker = program.getTypeChecker();
const initialNavigationCollector = new InitialNavigationCollector(typeChecker);
const sourceFiles = program.getSourceFiles().filter(
f => !f.isDeclarationFile && !program.isSourceFileFromExternalLibrary(f));
const sourceFiles =
program.getSourceFiles().filter(sourceFile => canMigrateFile(basePath, sourceFile, program));
// Analyze source files by detecting all modules.
sourceFiles.forEach(sourceFile => initialNavigationCollector.visitNode(sourceFile));
const {assignments} = initialNavigationCollector;
const transformer = new InitialNavigationTransform(typeChecker, getUpdateRecorder);
const transformer = new InitialNavigationTransform(getUpdateRecorder);
const updateRecorders = new Map<ts.SourceFile, UpdateRecorder>();
transformer.migrateInitialNavigationAssignments(Array.from(assignments));

View File

@ -13,9 +13,7 @@ import {UpdateRecorder} from './update_recorder';
export class InitialNavigationTransform {
private printer = ts.createPrinter();
constructor(
private typeChecker: ts.TypeChecker,
private getUpdateRecorder: (sf: ts.SourceFile) => UpdateRecorder) {}
constructor(private getUpdateRecorder: (sf: ts.SourceFile) => UpdateRecorder) {}
/** Migrate the ExtraOptions#InitialNavigation property assignments. */
migrateInitialNavigationAssignments(literals: ts.PropertyAssignment[]) {
@ -62,14 +60,3 @@ function getUpdatedInitialNavigationValue(initializer: ts.Expression): ts.Expres
return !!newText ? ts.createIdentifier(`'${newText}'`) : null;
}
/**
* Check whether the value assigned to an `initialNavigation` assignment
* conforms to the expected types for ExtraOptions#InitialNavigation
* @param node the property assignment to check
*/
function isValidInitialNavigationValue(node: ts.PropertyAssignment): boolean {
return ts.isStringLiteralLike(node.initializer) ||
node.initializer.kind === ts.SyntaxKind.FalseKeyword ||
node.initializer.kind === ts.SyntaxKind.TrueKeyword;
}

View File

@ -10,7 +10,7 @@ import {Rule, SchematicContext, SchematicsException, Tree} from '@angular-devkit
import {relative} from 'path';
import * as ts from 'typescript';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {createMigrationProgram} from '../../utils/typescript/compiler_host';
import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host';
import {NgDefinitionCollector} from './definition_collector';
import {MissingInjectableTransform} from './transform';
import {UpdateRecorder} from './update_recorder';
@ -46,8 +46,8 @@ function runMissingInjectableMigration(
const failures: string[] = [];
const typeChecker = program.getTypeChecker();
const definitionCollector = new NgDefinitionCollector(typeChecker);
const sourceFiles = program.getSourceFiles().filter(
f => !f.isDeclarationFile && !program.isSourceFileFromExternalLibrary(f));
const sourceFiles =
program.getSourceFiles().filter(sourceFile => canMigrateFile(basePath, sourceFile, program));
// Analyze source files by detecting all modules, directives and components.
sourceFiles.forEach(sourceFile => definitionCollector.visitNode(sourceFile));

View File

@ -11,7 +11,7 @@ import {relative} from 'path';
import * as ts from 'typescript';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {createMigrationProgram} from '../../utils/typescript/compiler_host';
import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host';
import {Collector} from './collector';
import {AnalysisFailure, ModuleWithProvidersTransform} from './transform';
@ -49,8 +49,8 @@ function runModuleWithProvidersMigration(tree: Tree, tsconfigPath: string, baseP
const failures: string[] = [];
const typeChecker = program.getTypeChecker();
const collector = new Collector(typeChecker);
const sourceFiles = program.getSourceFiles().filter(
f => !f.isDeclarationFile && !program.isSourceFileFromExternalLibrary(f));
const sourceFiles =
program.getSourceFiles().filter(sourceFile => canMigrateFile(basePath, sourceFile, program));
// Analyze source files by detecting all modules.
sourceFiles.forEach(sourceFile => collector.visitNode(sourceFile));

View File

@ -11,7 +11,7 @@ import {relative} from 'path';
import * as ts from 'typescript';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {createMigrationProgram} from '../../utils/typescript/compiler_host';
import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host';
import {COMMON_IMPORT, DOCUMENT_TOKEN_NAME, DocumentImportVisitor, ResolvedDocumentImport} from './document_import_visitor';
import {addToImport, createImport, removeFromImport} from './move-import';
@ -43,8 +43,8 @@ function runMoveDocumentMigration(tree: Tree, tsconfigPath: string, basePath: st
const {program} = createMigrationProgram(tree, tsconfigPath, basePath);
const typeChecker = program.getTypeChecker();
const visitor = new DocumentImportVisitor(typeChecker);
const sourceFiles = program.getSourceFiles().filter(
f => !f.isDeclarationFile && !program.isSourceFileFromExternalLibrary(f));
const sourceFiles =
program.getSourceFiles().filter(sourceFile => canMigrateFile(basePath, sourceFile, program));
// Analyze source files by finding imports.
sourceFiles.forEach(sourceFile => visitor.visitNode(sourceFile));

View File

@ -10,7 +10,7 @@ import {Rule, SchematicsException, Tree} from '@angular-devkit/schematics';
import {relative} from 'path';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {createMigrationProgram} from '../../utils/typescript/compiler_host';
import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host';
import {findNativeEncapsulationNodes} from './util';
@ -35,8 +35,8 @@ export default function(): Rule {
function runNativeViewEncapsulationMigration(tree: Tree, tsconfigPath: string, basePath: string) {
const {program} = createMigrationProgram(tree, tsconfigPath, basePath);
const typeChecker = program.getTypeChecker();
const sourceFiles = program.getSourceFiles().filter(
f => !f.isDeclarationFile && !program.isSourceFileFromExternalLibrary(f));
const sourceFiles =
program.getSourceFiles().filter(sourceFile => canMigrateFile(basePath, sourceFile, program));
sourceFiles.forEach(sourceFile => {
const update = tree.beginUpdate(relative(basePath, sourceFile.fileName));

View File

@ -11,7 +11,7 @@ import {relative} from 'path';
import * as ts from 'typescript';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {createMigrationProgram} from '../../utils/typescript/compiler_host';
import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host';
import {findLiteralsToMigrate, migrateLiteral} from './util';
@ -38,8 +38,8 @@ function runNavigationExtrasOmissionsMigration(tree: Tree, tsconfigPath: string,
const {program} = createMigrationProgram(tree, tsconfigPath, basePath);
const typeChecker = program.getTypeChecker();
const printer = ts.createPrinter();
const sourceFiles = program.getSourceFiles().filter(
f => !f.isDeclarationFile && !program.isSourceFileFromExternalLibrary(f));
const sourceFiles =
program.getSourceFiles().filter(sourceFile => canMigrateFile(basePath, sourceFile, program));
sourceFiles.forEach(sourceFile => {
const literalsToMigrate = findLiteralsToMigrate(sourceFile, typeChecker);

View File

@ -10,7 +10,7 @@ import {Rule, SchematicsException, Tree} from '@angular-devkit/schematics';
import {relative} from 'path';
import * as ts from 'typescript';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {createMigrationProgram} from '../../utils/typescript/compiler_host';
import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host';
import {RelativeLinkResolutionCollector} from './collector';
import {RelativeLinkResolutionTransform} from './transform';
import {UpdateRecorder} from './update_recorder';
@ -36,8 +36,8 @@ function runRelativeLinkResolutionMigration(tree: Tree, tsconfigPath: string, ba
const {program} = createMigrationProgram(tree, tsconfigPath, basePath);
const typeChecker = program.getTypeChecker();
const relativeLinkResolutionCollector = new RelativeLinkResolutionCollector(typeChecker);
const sourceFiles = program.getSourceFiles().filter(
f => !f.isDeclarationFile && !program.isSourceFileFromExternalLibrary(f));
const sourceFiles =
program.getSourceFiles().filter(sourceFile => canMigrateFile(basePath, sourceFile, program));
// Analyze source files by detecting all modules.
sourceFiles.forEach(sourceFile => relativeLinkResolutionCollector.visitNode(sourceFile));

View File

@ -11,7 +11,7 @@ import {relative} from 'path';
import * as ts from 'typescript';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {createMigrationProgram} from '../../utils/typescript/compiler_host';
import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host';
import {getImportSpecifier, replaceImport} from '../../utils/typescript/imports';
import {closestNode} from '../../utils/typescript/nodes';
@ -59,8 +59,8 @@ function runRendererToRenderer2Migration(tree: Tree, tsconfigPath: string, baseP
}, [MODULE_AUGMENTATION_FILENAME]);
const typeChecker = program.getTypeChecker();
const printer = ts.createPrinter();
const sourceFiles = program.getSourceFiles().filter(
f => !f.isDeclarationFile && !program.isSourceFileFromExternalLibrary(f));
const sourceFiles =
program.getSourceFiles().filter(sourceFile => canMigrateFile(basePath, sourceFile, program));
sourceFiles.forEach(sourceFile => {
const rendererImportSpecifier = getImportSpecifier(sourceFile, '@angular/core', 'Renderer');

View File

@ -11,7 +11,7 @@ import {relative} from 'path';
import * as ts from 'typescript';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {createMigrationProgram} from '../../utils/typescript/compiler_host';
import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host';
import {findLiteralsToMigrate, migrateLiteral} from './util';
@ -41,8 +41,8 @@ function runPreserveQueryParamsMigration(tree: Tree, tsconfigPath: string, baseP
const {program} = createMigrationProgram(tree, tsconfigPath, basePath);
const typeChecker = program.getTypeChecker();
const printer = ts.createPrinter();
const sourceFiles = program.getSourceFiles().filter(
f => !f.isDeclarationFile && !program.isSourceFileFromExternalLibrary(f));
const sourceFiles =
program.getSourceFiles().filter(sourceFile => canMigrateFile(basePath, sourceFile, program));
sourceFiles.forEach(sourceFile => {
const literalsToMigrate = findLiteralsToMigrate(sourceFile, typeChecker);

View File

@ -14,7 +14,7 @@ import * as ts from 'typescript';
import {NgComponentTemplateVisitor} from '../../utils/ng_component_template';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {createMigrationProgram} from '../../utils/typescript/compiler_host';
import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host';
import {NgQueryResolveVisitor} from './angular/ng_query_visitor';
import {QueryTemplateStrategy} from './strategies/template_strategy/template_strategy';
@ -123,8 +123,8 @@ function analyzeProject(
}
const typeChecker = program.getTypeChecker();
const sourceFiles = program.getSourceFiles().filter(
f => !f.isDeclarationFile && !program.isSourceFileFromExternalLibrary(f));
const sourceFiles =
program.getSourceFiles().filter(sourceFile => canMigrateFile(basePath, sourceFile, program));
const queryVisitor = new NgQueryResolveVisitor(typeChecker);
// Analyze all project source-files and collect all queries that

View File

@ -12,7 +12,7 @@ import {relative} from 'path';
import {NgComponentTemplateVisitor} from '../../utils/ng_component_template';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {createMigrationProgram} from '../../utils/typescript/compiler_host';
import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host';
import {analyzeResolvedTemplate} from './analyze_template';
@ -48,8 +48,8 @@ function runTemplateVariableAssignmentCheck(
const {program} = createMigrationProgram(tree, tsconfigPath, basePath);
const typeChecker = program.getTypeChecker();
const templateVisitor = new NgComponentTemplateVisitor(typeChecker);
const sourceFiles = program.getSourceFiles().filter(
f => !f.isDeclarationFile && !program.isSourceFileFromExternalLibrary(f));
const sourceFiles =
program.getSourceFiles().filter(sourceFile => canMigrateFile(basePath, sourceFile, program));
// Analyze source files by detecting HTML templates.
sourceFiles.forEach(sourceFile => templateVisitor.visitNode(sourceFile));

View File

@ -11,7 +11,7 @@ import {relative} from 'path';
import * as ts from 'typescript';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {createMigrationProgram} from '../../utils/typescript/compiler_host';
import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host';
import {UndecoratedClassesWithDecoratedFieldsTransform} from './transform';
import {UpdateRecorder} from './update_recorder';
@ -49,8 +49,8 @@ function runUndecoratedClassesMigration(
const failures: string[] = [];
const {program} = createMigrationProgram(tree, tsconfigPath, basePath);
const typeChecker = program.getTypeChecker();
const sourceFiles = program.getSourceFiles().filter(
file => !file.isDeclarationFile && !program.isSourceFileFromExternalLibrary(file));
const sourceFiles =
program.getSourceFiles().filter(sourceFile => canMigrateFile(basePath, sourceFile, program));
const updateRecorders = new Map<ts.SourceFile, UpdateRecorder>();
const transform =
new UndecoratedClassesWithDecoratedFieldsTransform(typeChecker, getUpdateRecorder);

View File

@ -16,7 +16,7 @@ import {relative} from 'path';
import * as ts from 'typescript';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {createMigrationCompilerHost} from '../../utils/typescript/compiler_host';
import {canMigrateFile, createMigrationCompilerHost} from '../../utils/typescript/compiler_host';
import {createNgcProgram} from './create_ngc_program';
import {NgDeclarationCollector} from './ng_declaration_collector';
@ -85,8 +85,8 @@ function runUndecoratedClassesMigration(
const partialEvaluator = new PartialEvaluator(
new TypeScriptReflectionHost(typeChecker), typeChecker, /* dependencyTracker */ null);
const declarationCollector = new NgDeclarationCollector(typeChecker, partialEvaluator);
const sourceFiles = program.getSourceFiles().filter(
s => !s.isDeclarationFile && !program.isSourceFileFromExternalLibrary(s));
const sourceFiles =
program.getSourceFiles().filter(sourceFile => canMigrateFile(basePath, sourceFile, program));
// Analyze source files by detecting all directives, components and providers.
sourceFiles.forEach(sourceFile => declarationCollector.visitNode(sourceFile));

View File

@ -11,7 +11,7 @@ import {relative} from 'path';
import * as ts from 'typescript';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {createMigrationProgram} from '../../utils/typescript/compiler_host';
import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host';
import {getImportSpecifier, replaceImport} from '../../utils/typescript/imports';
import {closestNode} from '../../utils/typescript/nodes';
@ -54,8 +54,8 @@ function runWaitForAsyncMigration(tree: Tree, tsconfigPath: string, basePath: st
}, [MODULE_AUGMENTATION_FILENAME]);
const typeChecker = program.getTypeChecker();
const printer = ts.createPrinter();
const sourceFiles = program.getSourceFiles().filter(
f => !f.isDeclarationFile && !program.isSourceFileFromExternalLibrary(f));
const sourceFiles =
program.getSourceFiles().filter(sourceFile => canMigrateFile(basePath, sourceFile, program));
const deprecatedFunction = 'async';
const newFunction = 'waitForAsync';

View File

@ -57,3 +57,24 @@ export function createMigrationCompilerHost(
return host;
}
/**
* Checks whether a file can be migrate by our automated migrations.
* @param basePath Absolute path to the project.
* @param sourceFile File being checked.
* @param program Program that includes the source file.
*/
export function canMigrateFile(
basePath: string, sourceFile: ts.SourceFile, program: ts.Program): boolean {
// We shouldn't migrate .d.ts files or files from an external library.
if (sourceFile.isDeclarationFile || program.isSourceFileFromExternalLibrary(sourceFile)) {
return false;
}
// Our migrations are set up to create a `Program` from the project's tsconfig and to migrate all
// the files within the program. This can include files that are outside of the Angular CLI
// project. We can't migrate files outside of the project, because our file system interactions
// go through the CLI's `Tree` which assumes that all files are within the project. See:
// https://github.com/angular/angular-cli/blob/0b0961c9c233a825b6e4bb59ab7f0790f9b14676/packages/angular_devkit/schematics/src/tree/host-tree.ts#L131
return !relative(basePath, sourceFile.fileName).startsWith('..');
}