Skip to content

Commit

Permalink
refactor(compiler-cli): make the output AST translator generic
Browse files Browse the repository at this point in the history
This commit refactors the `ExpressionTranslatorVisitor` so that it
is not tied directly to the TypeScript AST. Instead it uses generic
`TExpression` and `TStatement` types that are then converted
to concrete types by the `TypeScriptAstFactory`.

This paves the way for a `BabelAstFactory` that can be used to
generate Babel AST nodes instead of TypeScript, which will be
part of the new linker tool.
  • Loading branch information
petebacondarwin committed Sep 16, 2020
1 parent c64dd74 commit 1a03a00
Show file tree
Hide file tree
Showing 20 changed files with 1,336 additions and 714 deletions.
Expand Up @@ -55,7 +55,7 @@ export class CommonJsRenderingFormatter extends Esm5RenderingFormatter {
const namedImport = entryPointBasePath !== basePath ?
importManager.generateNamedImport(relativePath, e.identifier) :
{symbol: e.identifier, moduleImport: null};
const importNamespace = namedImport.moduleImport ? `${namedImport.moduleImport}.` : '';
const importNamespace = namedImport.moduleImport ? `${namedImport.moduleImport.text}.` : '';
const exportStr = `\nexports.${e.identifier} = ${importNamespace}${namedImport.symbol};`;
output.append(exportStr);
});
Expand All @@ -66,7 +66,7 @@ export class CommonJsRenderingFormatter extends Esm5RenderingFormatter {
file: ts.SourceFile): void {
for (const e of exports) {
const namedImport = importManager.generateNamedImport(e.fromModule, e.symbolName);
const importNamespace = namedImport.moduleImport ? `${namedImport.moduleImport}.` : '';
const importNamespace = namedImport.moduleImport ? `${namedImport.moduleImport.text}.` : '';
const exportStr = `\nexports.${e.asAlias} = ${importNamespace}${namedImport.symbol};`;
output.append(exportStr);
}
Expand Down
Expand Up @@ -9,8 +9,7 @@ import {Statement} from '@angular/compiler';
import MagicString from 'magic-string';
import * as ts from 'typescript';

import {NOOP_DEFAULT_IMPORT_RECORDER} from '../../../src/ngtsc/imports';
import {ImportManager, translateStatement} from '../../../src/ngtsc/translator';
import {ImportManager, translateStatement, TypeScriptAstFactory} from '../../../src/ngtsc/translator';
import {CompiledClass} from '../analysis/types';
import {getContainingStatement} from '../host/esm2015_host';

Expand Down Expand Up @@ -65,8 +64,9 @@ export class Esm5RenderingFormatter extends EsmRenderingFormatter {
* @return The JavaScript code corresponding to `stmt` (in the appropriate format).
*/
printStatement(stmt: Statement, sourceFile: ts.SourceFile, importManager: ImportManager): string {
const node =
translateStatement(stmt, importManager, NOOP_DEFAULT_IMPORT_RECORDER, ts.ScriptTarget.ES5);
const node = translateStatement(
stmt, new TypeScriptAstFactory(ts.ScriptTarget.ES5), importManager,
{downlevelLocalizedStrings: true});
const code = this.printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);

return code;
Expand Down
Expand Up @@ -10,8 +10,9 @@ import MagicString from 'magic-string';
import * as ts from 'typescript';

import {absoluteFromSourceFile, AbsoluteFsPath, dirname, relative, toRelativeImport} from '../../../src/ngtsc/file_system';
import {NOOP_DEFAULT_IMPORT_RECORDER, Reexport} from '../../../src/ngtsc/imports';
import {Reexport} from '../../../src/ngtsc/imports';
import {Import, ImportManager, translateStatement} from '../../../src/ngtsc/translator';
import {TypeScriptAstFactory} from '../../../src/ngtsc/translator/src/typescript_ast_factory';
import {isDtsPath} from '../../../src/ngtsc/util/src/typescript';
import {ModuleWithProvidersInfo} from '../analysis/module_with_providers_analyzer';
import {ExportInfo} from '../analysis/private_declarations_analyzer';
Expand Down Expand Up @@ -247,8 +248,8 @@ export class EsmRenderingFormatter implements RenderingFormatter {
* @return The JavaScript code corresponding to `stmt` (in the appropriate format).
*/
printStatement(stmt: Statement, sourceFile: ts.SourceFile, importManager: ImportManager): string {
const node = translateStatement(
stmt, importManager, NOOP_DEFAULT_IMPORT_RECORDER, ts.ScriptTarget.ES2015);
const node =
translateStatement(stmt, new TypeScriptAstFactory(ts.ScriptTarget.ES2015), importManager);
const code = this.printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);

return code;
Expand All @@ -264,8 +265,6 @@ export class EsmRenderingFormatter implements RenderingFormatter {
return 0;
}



/**
* Check whether the given type is the core Angular `ModuleWithProviders` interface.
* @param typeName The type to check.
Expand All @@ -292,7 +291,8 @@ function findStatement(node: ts.Node): ts.Statement|undefined {
function generateImportString(
importManager: ImportManager, importPath: string|null, importName: string) {
const importAs = importPath ? importManager.generateNamedImport(importPath, importName) : null;
return importAs ? `${importAs.moduleImport}.${importAs.symbol}` : `${importName}`;
return importAs && importAs.moduleImport ? `${importAs.moduleImport.text}.${importAs.symbol}` :
`${importName}`;
}

function getNextSiblingInArray<T extends ts.Node>(node: T, array: ts.NodeArray<T>): T|null {
Expand Down
Expand Up @@ -91,7 +91,7 @@ export class UmdRenderingFormatter extends Esm5RenderingFormatter {
const namedImport = entryPointBasePath !== basePath ?
importManager.generateNamedImport(relativePath, e.identifier) :
{symbol: e.identifier, moduleImport: null};
const importNamespace = namedImport.moduleImport ? `${namedImport.moduleImport}.` : '';
const importNamespace = namedImport.moduleImport ? `${namedImport.moduleImport.text}.` : '';
const exportStr = `\nexports.${e.identifier} = ${importNamespace}${namedImport.symbol};`;
output.appendRight(insertionPoint, exportStr);
});
Expand All @@ -111,7 +111,7 @@ export class UmdRenderingFormatter extends Esm5RenderingFormatter {
lastStatement ? lastStatement.getEnd() : factoryFunction.body.getEnd() - 1;
for (const e of exports) {
const namedImport = importManager.generateNamedImport(e.fromModule, e.symbolName);
const importNamespace = namedImport.moduleImport ? `${namedImport.moduleImport}.` : '';
const importNamespace = namedImport.moduleImport ? `${namedImport.moduleImport.text}.` : '';
const exportStr = `\nexports.${e.asAlias} = ${importNamespace}${namedImport.symbol};`;
output.appendRight(insertionPoint, exportStr);
}
Expand Down
8 changes: 4 additions & 4 deletions packages/compiler-cli/ngcc/test/rendering/renderer_spec.ts
Expand Up @@ -13,9 +13,9 @@ import * as ts from 'typescript';

import {absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system';
import {runInEachFileSystem, TestFile} from '../../../src/ngtsc/file_system/testing';
import {NOOP_DEFAULT_IMPORT_RECORDER, Reexport} from '../../../src/ngtsc/imports';
import {Reexport} from '../../../src/ngtsc/imports';
import {MockLogger} from '../../../src/ngtsc/logging/testing';
import {Import, ImportManager, translateStatement} from '../../../src/ngtsc/translator';
import {Import, ImportManager, translateStatement, TypeScriptAstFactory} from '../../../src/ngtsc/translator';
import {loadTestFiles} from '../../../test/helpers';
import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
import {ModuleWithProvidersInfo} from '../../src/analysis/module_with_providers_analyzer';
Expand Down Expand Up @@ -62,8 +62,8 @@ class TestRenderingFormatter implements RenderingFormatter {
output.prepend('\n// ADD MODUlE WITH PROVIDERS PARAMS\n');
}
printStatement(stmt: Statement, sourceFile: ts.SourceFile, importManager: ImportManager): string {
const node = translateStatement(
stmt, importManager, NOOP_DEFAULT_IMPORT_RECORDER, ts.ScriptTarget.ES2015);
const node =
translateStatement(stmt, new TypeScriptAstFactory(ts.ScriptTarget.ES2015), importManager);
const code = this.printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);

return `// TRANSPILED\n${code}`;
Expand Down
Expand Up @@ -12,7 +12,7 @@ import * as ts from 'typescript';
import {ErrorCode, FatalDiagnosticError, makeDiagnostic, makeRelatedInformation} from '../../diagnostics';
import {DefaultImportRecorder, Reference, ReferenceEmitter} from '../../imports';
import {InjectableClassRegistry, MetadataReader, MetadataRegistry} from '../../metadata';
import {PartialEvaluator, ResolvedValue, ResolvedValueArray} from '../../partial_evaluator';
import {PartialEvaluator, ResolvedValue} from '../../partial_evaluator';
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral, typeNodeToValueExpr} from '../../reflection';
import {NgModuleRouteAnalyzer} from '../../routing';
import {LocalModuleScopeRegistry, ScopeData} from '../../scope';
Expand Down
Expand Up @@ -12,7 +12,7 @@ import {runInEachFileSystem, TestFile} from '../../file_system/testing';
import {NOOP_DEFAULT_IMPORT_RECORDER, NoopImportRewriter} from '../../imports';
import {TypeScriptReflectionHost} from '../../reflection';
import {getDeclaration, makeProgram} from '../../testing';
import {ImportManager, translateStatement} from '../../translator';
import {ImportManager, translateStatement, TypeScriptAstFactory} from '../../translator';
import {generateSetClassMetadataCall} from '../src/metadata';

runInEachFileSystem(() => {
Expand Down Expand Up @@ -134,7 +134,7 @@ runInEachFileSystem(() => {
const sf = getSourceFileOrError(program, _('/index.ts'));
const im = new ImportManager(new NoopImportRewriter(), 'i');
const tsStatement =
translateStatement(call, im, NOOP_DEFAULT_IMPORT_RECORDER, ts.ScriptTarget.ES2015);
translateStatement(call, new TypeScriptAstFactory(ts.ScriptTarget.ES2015), im);
const res = ts.createPrinter().printNode(ts.EmitHint.Unspecified, tsStatement, sf);
return res.replace(/\s+/g, ' ');
}
Expand Down
34 changes: 24 additions & 10 deletions packages/compiler-cli/src/ngtsc/transform/src/transform.ts
Expand Up @@ -11,7 +11,7 @@ import * as ts from 'typescript';

import {DefaultImportRecorder, ImportRewriter} from '../../imports';
import {Decorator, ReflectionHost} from '../../reflection';
import {ImportManager, translateExpression, translateStatement} from '../../translator';
import {ImportManager, RecordWrappedNodeExprFn, translateExpression, translateStatement, TypeScriptAstFactory} from '../../translator';
import {visit, VisitListEntryResult, Visitor} from '../../util/src/visitor';

import {CompileResult} from './api';
Expand All @@ -35,11 +35,12 @@ export function ivyTransformFactory(
compilation: TraitCompiler, reflector: ReflectionHost, importRewriter: ImportRewriter,
defaultImportRecorder: DefaultImportRecorder, isCore: boolean,
isClosureCompilerEnabled: boolean): ts.TransformerFactory<ts.SourceFile> {
const recordWrappedNodeExpr = recorderFnFactory(defaultImportRecorder);
return (context: ts.TransformationContext): ts.Transformer<ts.SourceFile> => {
return (file: ts.SourceFile): ts.SourceFile => {
return transformIvySourceFile(
compilation, context, reflector, importRewriter, file, isCore, isClosureCompilerEnabled,
defaultImportRecorder);
recordWrappedNodeExpr);
};
};
}
Expand Down Expand Up @@ -77,7 +78,7 @@ class IvyTransformationVisitor extends Visitor {
private compilation: TraitCompiler,
private classCompilationMap: Map<ts.ClassDeclaration, CompileResult[]>,
private reflector: ReflectionHost, private importManager: ImportManager,
private defaultImportRecorder: DefaultImportRecorder,
private recordWrappedNodeExpr: RecordWrappedNodeExprFn<ts.Expression>,
private isClosureCompilerEnabled: boolean, private isCore: boolean) {
super();
}
Expand All @@ -97,8 +98,8 @@ class IvyTransformationVisitor extends Visitor {
for (const field of this.classCompilationMap.get(node)!) {
// Translate the initializer for the field into TS nodes.
const exprNode = translateExpression(
field.initializer, this.importManager, this.defaultImportRecorder,
ts.ScriptTarget.ES2015);
field.initializer, new TypeScriptAstFactory(ts.ScriptTarget.ES2015), this.importManager,
{recordWrappedNodeExpr: this.recordWrappedNodeExpr});

// Create a static property declaration for the new field.
const property = ts.createProperty(
Expand All @@ -118,7 +119,8 @@ class IvyTransformationVisitor extends Visitor {
field.statements
.map(
stmt => translateStatement(
stmt, this.importManager, this.defaultImportRecorder, ts.ScriptTarget.ES2015))
stmt, new TypeScriptAstFactory(ts.ScriptTarget.ES2015), this.importManager,
{recordWrappedNodeExpr: this.recordWrappedNodeExpr}))
.forEach(stmt => statements.push(stmt));

members.push(property);
Expand Down Expand Up @@ -248,7 +250,7 @@ function transformIvySourceFile(
compilation: TraitCompiler, context: ts.TransformationContext, reflector: ReflectionHost,
importRewriter: ImportRewriter, file: ts.SourceFile, isCore: boolean,
isClosureCompilerEnabled: boolean,
defaultImportRecorder: DefaultImportRecorder): ts.SourceFile {
recordWrappedNodeExpr: RecordWrappedNodeExprFn<ts.Expression>): ts.SourceFile {
const constantPool = new ConstantPool(isClosureCompilerEnabled);
const importManager = new ImportManager(importRewriter);

Expand All @@ -270,14 +272,17 @@ function transformIvySourceFile(
// results obtained at Step 1.
const transformationVisitor = new IvyTransformationVisitor(
compilation, compilationVisitor.classCompilationMap, reflector, importManager,
defaultImportRecorder, isClosureCompilerEnabled, isCore);
recordWrappedNodeExpr, isClosureCompilerEnabled, isCore);
let sf = visit(file, transformationVisitor, context);

// Generate the constant statements first, as they may involve adding additional imports
// to the ImportManager.
const compilationTarget = getLocalizeCompileTarget(context);
const constants = constantPool.statements.map(
stmt => translateStatement(
stmt, importManager, defaultImportRecorder, getLocalizeCompileTarget(context)));
stmt => translateStatement(stmt, new TypeScriptAstFactory(compilationTarget), importManager, {
recordWrappedNodeExpr,
downlevelLocalizedStrings: compilationTarget < ts.ScriptTarget.ES2015
}));

// Preserve @fileoverview comments required by Closure, since the location might change as a
// result of adding extra imports and constant pool statements.
Expand Down Expand Up @@ -360,3 +365,12 @@ function maybeFilterDecorator(
function isFromAngularCore(decorator: Decorator): boolean {
return decorator.import !== null && decorator.import.from === '@angular/core';
}

function recorderFnFactory(defaultImportRecorder: DefaultImportRecorder): (expr: ts.Expression) =>
void {
return (expr: ts.Expression) => {
if (ts.isIdentifier(expr)) {
defaultImportRecorder.recordUsedIdentifier(expr);
}
};
}
Expand Up @@ -9,7 +9,7 @@ import {absoluteFrom} from '../../file_system';
import {runInEachFileSystem} from '../../file_system/testing';
import {NOOP_INCREMENTAL_BUILD} from '../../incremental';
import {NOOP_PERF_RECORDER} from '../../perf';
import {ClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
import {TypeScriptReflectionHost} from '../../reflection';
import {makeProgram} from '../../testing';
import {DtsTransformRegistry, TraitCompiler} from '../../transform';
import {AnalysisOutput, CompileResult, DecoratorHandler, HandlerPrecedence} from '../src/api';
Expand Down
7 changes: 6 additions & 1 deletion packages/compiler-cli/src/ngtsc/translator/index.ts
Expand Up @@ -6,4 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/

export {attachComments, Import, ImportManager, NamedImport, translateExpression, translateStatement, translateType} from './src/translator';
export {AstFactory, BinaryOperators, LeadingComment, ObjectLiteralProperty, PropertyAssignment, SourceMapLocation, SourceMapRange, TemplateElement, TemplateLiteral, UnaryOperators} from './src/api/ast_factory';
export {Import, ImportGenerator, NamedImport} from './src/api/import_generator';
export {ImportManager} from './src/import_manager';
export {RecordWrappedNodeExprFn, translateExpression, translateStatement} from './src/translator';
export {translateType} from './src/type_translator';
export {attachComments, TypeScriptAstFactory} from './src/typescript_ast_factory';

0 comments on commit 1a03a00

Please sign in to comment.