refactor(ivy): ngcc - `Transformer` delegates to `Analyzer`s and `Renderer` (#26082)

PR Close #26082
This commit is contained in:
Pete Bacon Darwin 2018-09-27 18:24:00 +01:00 committed by Miško Hevery
parent 632f66a461
commit 34b6d5fff9
1 changed files with 29 additions and 78 deletions

View File

@ -5,14 +5,13 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {dirname, relative, resolve} from 'canonical-path'; import {dirname, resolve} from 'canonical-path';
import {existsSync, lstatSync, readFileSync, readdirSync, writeFileSync} from 'fs'; import {existsSync, lstatSync, readdirSync, writeFileSync} from 'fs';
import {mkdir, mv} from 'shelljs'; import {mkdir, mv} from 'shelljs';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {DtsFileTransformer} from '../../../ngtsc/transform'; import {DecorationAnalyzer} from '../analysis/decoration_analyzer';
import {DecorationAnalysis, DecorationAnalyzer} from '../analysis/decoration_analyzer'; import {SwitchMarkerAnalyzer} from '../analysis/switch_marker_analyzer';
import {IMPORT_PREFIX} from '../constants';
import {DtsMapper} from '../host/dts_mapper'; import {DtsMapper} from '../host/dts_mapper';
import {Esm2015ReflectionHost} from '../host/esm2015_host'; import {Esm2015ReflectionHost} from '../host/esm2015_host';
import {Esm5ReflectionHost} from '../host/esm5_host'; import {Esm5ReflectionHost} from '../host/esm5_host';
@ -20,6 +19,7 @@ import {Fesm2015ReflectionHost} from '../host/fesm2015_host';
import {NgccReflectionHost} from '../host/ngcc_host'; import {NgccReflectionHost} from '../host/ngcc_host';
import {Esm2015Renderer} from '../rendering/esm2015_renderer'; import {Esm2015Renderer} from '../rendering/esm2015_renderer';
import {Esm5Renderer} from '../rendering/esm5_renderer'; import {Esm5Renderer} from '../rendering/esm5_renderer';
import {Fesm2015Renderer} from '../rendering/fesm2015_renderer';
import {FileInfo, Renderer} from '../rendering/renderer'; import {FileInfo, Renderer} from '../rendering/renderer';
import {checkMarkerFile, writeMarkerFile} from './build_marker'; import {checkMarkerFile, writeMarkerFile} from './build_marker';
@ -52,7 +52,6 @@ export class Transformer {
return; return;
} }
const outputFiles: FileInfo[] = [];
const options: ts.CompilerOptions = { const options: ts.CompilerOptions = {
allowJs: true, allowJs: true,
maxNodeModuleJsDepth: Infinity, maxNodeModuleJsDepth: Infinity,
@ -72,35 +71,22 @@ export class Transformer {
const r3SymbolsPath = isCore ? this.findR3SymbolsPath(dirname(entryPointFilePath)) : null; const r3SymbolsPath = isCore ? this.findR3SymbolsPath(dirname(entryPointFilePath)) : null;
const rootPaths = r3SymbolsPath ? [entryPointFilePath, r3SymbolsPath] : [entryPointFilePath]; const rootPaths = r3SymbolsPath ? [entryPointFilePath, r3SymbolsPath] : [entryPointFilePath];
const packageProgram = ts.createProgram(rootPaths, options, host); const packageProgram = ts.createProgram(rootPaths, options, host);
const typeChecker = packageProgram.getTypeChecker();
const dtsMapper = new DtsMapper(dirname(entryPointFilePath), dirname(entryPoint.typings)); const dtsMapper = new DtsMapper(dirname(entryPointFilePath), dirname(entryPoint.typings));
const reflectionHost = this.getHost(isCore, format, packageProgram, dtsMapper); const reflectionHost = this.getHost(isCore, format, packageProgram, dtsMapper);
const r3SymbolsFile = r3SymbolsPath && packageProgram.getSourceFile(r3SymbolsPath) || null; const r3SymbolsFile = r3SymbolsPath && packageProgram.getSourceFile(r3SymbolsPath) || null;
const analyzer = new DecorationAnalyzer(typeChecker, reflectionHost, rootDirs, isCore);
const renderer =
this.getRenderer(format, packageProgram, reflectionHost, isCore, r3SymbolsFile);
// Parse and analyze the files. // Parse and analyze the files.
const entryPointFile = packageProgram.getSourceFile(entryPointFilePath) !; const {decorationAnalyses, switchMarkerAnalyses} =
const decoratedFiles = reflectionHost.findDecoratedFiles(entryPointFile); this.analyzeProgram(packageProgram, reflectionHost, rootDirs, isCore);
const analyzedFiles = Array.from(decoratedFiles.values())
.map(decoratedFile => analyzer.analyzeFile(decoratedFile));
// Transform the source files and source maps. // Transform the source files and source maps.
outputFiles.push( const renderer =
...this.transformSourceFiles(analyzedFiles, this.sourcePath, this.targetPath, renderer)); this.getRenderer(format, packageProgram, reflectionHost, isCore, r3SymbolsFile, dtsMapper);
const renderedFiles =
// Transform the `.d.ts` files (if necessary). renderer.renderProgram(packageProgram, decorationAnalyses, switchMarkerAnalyses);
// TODO(gkalpak): What about `.d.ts` source maps? (See
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-9.html#new---declarationmap.)
if (format === 'esm2015') {
outputFiles.push(
...this.transformDtsFiles(analyzedFiles, this.sourcePath, this.targetPath, dtsMapper));
}
// Write out all the transformed files. // Write out all the transformed files.
outputFiles.forEach(file => this.writeFile(file)); renderedFiles.forEach(file => this.writeFile(file));
// Write the built-with-ngcc marker // Write the built-with-ngcc marker
writeMarkerFile(entryPoint, format); writeMarkerFile(entryPoint, format);
@ -133,68 +119,33 @@ export class Transformer {
getRenderer( getRenderer(
format: string, program: ts.Program, host: NgccReflectionHost, isCore: boolean, format: string, program: ts.Program, host: NgccReflectionHost, isCore: boolean,
rewriteCoreImportsTo: ts.SourceFile|null): Renderer { rewriteCoreImportsTo: ts.SourceFile|null, dtsMapper: DtsMapper): Renderer {
switch (format) { switch (format) {
case 'esm2015': case 'esm2015':
return new Esm2015Renderer(
host, isCore, rewriteCoreImportsTo, this.sourcePath, this.targetPath, dtsMapper);
case 'fesm2015': case 'fesm2015':
return new Esm2015Renderer(host, isCore, rewriteCoreImportsTo); return new Fesm2015Renderer(
host, isCore, rewriteCoreImportsTo, this.sourcePath, this.targetPath);
case 'esm5': case 'esm5':
case 'fesm5': case 'fesm5':
return new Esm5Renderer(host, isCore, rewriteCoreImportsTo); return new Esm5Renderer(
host, isCore, rewriteCoreImportsTo, this.sourcePath, this.targetPath);
default: default:
throw new Error(`Renderer for "${format}" not yet implemented.`); throw new Error(`Renderer for "${format}" not yet implemented.`);
} }
} }
transformDtsFiles( analyzeProgram(
analyzedFiles: DecorationAnalysis[], sourceNodeModules: string, targetNodeModules: string, program: ts.Program, reflectionHost: NgccReflectionHost, rootDirs: string[],
dtsMapper: DtsMapper): FileInfo[] { isCore: boolean) {
const outputFiles: FileInfo[] = []; const decorationAnalyzer =
new DecorationAnalyzer(program.getTypeChecker(), reflectionHost, rootDirs, isCore);
analyzedFiles.forEach(analyzedFile => { const switchMarkerAnalyzer = new SwitchMarkerAnalyzer(reflectionHost);
// Create a `DtsFileTransformer` for the source file and record the generated fields, which return {
// will allow the corresponding `.d.ts` file to be transformed later. decorationAnalyses: decorationAnalyzer.analyzeProgram(program),
const dtsTransformer = new DtsFileTransformer(null, IMPORT_PREFIX); switchMarkerAnalyses: switchMarkerAnalyzer.analyzeProgram(program),
analyzedFile.analyzedClasses.forEach( };
analyzedClass =>
dtsTransformer.recordStaticField(analyzedClass.name, analyzedClass.compilation));
// Find the corresponding `.d.ts` file.
const sourceFileName = analyzedFile.sourceFile.fileName;
const originalDtsFileName = dtsMapper.getDtsFileNameFor(sourceFileName);
const originalDtsContents = readFileSync(originalDtsFileName, 'utf8');
// Transform the `.d.ts` file based on the recorded source file changes.
const transformedDtsFileName =
resolve(targetNodeModules, relative(sourceNodeModules, originalDtsFileName));
const transformedDtsContents = dtsTransformer.transform(originalDtsContents, sourceFileName);
// Add the transformed `.d.ts` file to the list of output files.
outputFiles.push({path: transformedDtsFileName, contents: transformedDtsContents});
});
return outputFiles;
}
transformSourceFiles(
analyzedFiles: DecorationAnalysis[], sourceNodeModules: string, targetNodeModules: string,
renderer: Renderer): FileInfo[] {
const outputFiles: FileInfo[] = [];
analyzedFiles.forEach(analyzedFile => {
// Transform the source file based on the recorded changes.
const targetPath =
resolve(targetNodeModules, relative(sourceNodeModules, analyzedFile.sourceFile.fileName));
const {source, map} = renderer.renderFile(analyzedFile, targetPath);
// Add the transformed file (and source map, if available) to the list of output files.
outputFiles.push(source);
if (map) {
outputFiles.push(map);
}
});
return outputFiles;
} }
writeFile(file: FileInfo): void { writeFile(file: FileInfo): void {