Paul Gschwendtner d43c30688a fix(core): avoid migration error when non-existent symbol is imported (#36367)
In rare cases a project with configured `rootDirs` that has imports to
non-existent identifiers could fail in the migration.

This happens because based on the application code, the migration could
end up trying to resolve the `ts.Symbol` of such non-existent
identifiers. This isn't a problem usually, but due to a upstream bug
in the TypeScript compiler, a runtime error is thrown.

This is because TypeScript is unable to compute a relative path from the
originating source file to the imported source file which _should_
provide the non-existent identifier. An issue for this has been reported
upstream: https://github.com/microsoft/TypeScript/issues/37731. The
issue only surfaces since our migrations don't provide an absolute base
path that is used for resolving the root directories.

To fix this, we ensure that we never use relative paths when parsing
tsconfig files. More details can be found in the TS issue.

Fixes #36346.

PR Close #36367
2020-04-06 13:21:54 -07:00

70 lines
2.4 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, 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 {identifyDynamicQueryNodes, removeOptionsParameter, removeStaticFlag} from './util';
/**
* Runs the dynamic queries migration for all TypeScript projects in the current CLI workspace.
*/
export default function(): Rule {
return (tree: Tree) => {
const {buildPaths, testPaths} = getProjectTsConfigPaths(tree);
const basePath = process.cwd();
const allPaths = [...buildPaths, ...testPaths];
if (!allPaths.length) {
throw new SchematicsException(
'Could not find any tsconfig file. Cannot migrate dynamic queries.');
}
for (const tsconfigPath of allPaths) {
runDynamicQueryMigration(tree, tsconfigPath, basePath);
}
};
}
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 printer = ts.createPrinter();
sourceFiles.forEach(sourceFile => {
const result = identifyDynamicQueryNodes(typeChecker, sourceFile);
if (result.removeProperty.length || result.removeParameter.length) {
const update = tree.beginUpdate(relative(basePath, sourceFile.fileName));
result.removeProperty.forEach(node => {
update.remove(node.getStart(), node.getWidth());
update.insertRight(
node.getStart(),
printer.printNode(ts.EmitHint.Unspecified, removeStaticFlag(node), sourceFile));
});
result.removeParameter.forEach(node => {
update.remove(node.getStart(), node.getWidth());
update.insertRight(
node.getStart(),
printer.printNode(ts.EmitHint.Unspecified, removeOptionsParameter(node), sourceFile));
});
tree.commitUpdate(update);
}
});
}