feat(ivy): ngtsc compiles @Component, @Directive, @NgModule (#24427)
This change supports compilation of components, directives, and modules within ngtsc. Support is not complete, but is enough to compile and test //packages/core/test/bundling/todo in full AOT mode. Code size benefits are not yet achieved as //packages/core itself does not get compiled, and some decorators (e.g. @Input) are not stripped, leading to unwanted code being retained by the tree-shaker. This will be improved in future commits. PR Close #24427
This commit is contained in:

committed by
Miško Hevery

parent
0f7e4fae20
commit
27bc7dcb43
@ -12,5 +12,6 @@ ts_library(
|
||||
deps = [
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||
"//packages/compiler-cli/src/ngtsc/util",
|
||||
],
|
||||
)
|
||||
|
@ -6,6 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export * from './src/api';
|
||||
export {IvyCompilation} from './src/compilation';
|
||||
export {InjectableCompilerAdapter} from './src/injectable';
|
||||
export {ivyTransformFactory} from './src/transform';
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Expression, Type} from '@angular/compiler';
|
||||
import {Expression, Statement, Type} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Decorator} from '../../metadata';
|
||||
@ -15,13 +15,13 @@ import {Decorator} from '../../metadata';
|
||||
* Provides the interface between a decorator compiler from @angular/compiler and the Typescript
|
||||
* compiler/transform.
|
||||
*
|
||||
* The decorator compilers in @angular/compiler do not depend on Typescript. The adapter is
|
||||
* The decorator compilers in @angular/compiler do not depend on Typescript. The handler is
|
||||
* responsible for extracting the information required to perform compilation from the decorators
|
||||
* and Typescript source, invoking the decorator compiler, and returning the result.
|
||||
*/
|
||||
export interface CompilerAdapter<A> {
|
||||
export interface DecoratorHandler<A> {
|
||||
/**
|
||||
* Scan a set of reflected decorators and determine if this adapter is responsible for compilation
|
||||
* Scan a set of reflected decorators and determine if this handler is responsible for compilation
|
||||
* of one of them.
|
||||
*/
|
||||
detect(decorator: Decorator[]): Decorator|undefined;
|
||||
@ -37,7 +37,7 @@ export interface CompilerAdapter<A> {
|
||||
* Generate a description of the field which should be added to the class, including any
|
||||
* initialization code to be generated.
|
||||
*/
|
||||
compile(node: ts.ClassDeclaration, analysis: A): AddStaticFieldInstruction;
|
||||
compile(node: ts.ClassDeclaration, analysis: A): CompileResult;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,8 +54,9 @@ export interface AnalysisOutput<A> {
|
||||
* A description of the static field to add to a class, including an initialization expression
|
||||
* and a type for the .d.ts file.
|
||||
*/
|
||||
export interface AddStaticFieldInstruction {
|
||||
export interface CompileResult {
|
||||
field: string;
|
||||
initializer: Expression;
|
||||
statements: Statement[];
|
||||
type: Type;
|
||||
}
|
||||
|
@ -11,17 +11,18 @@ import * as ts from 'typescript';
|
||||
|
||||
import {Decorator, reflectDecorator} from '../../metadata';
|
||||
|
||||
import {AddStaticFieldInstruction, AnalysisOutput, CompilerAdapter} from './api';
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler} from './api';
|
||||
import {DtsFileTransformer} from './declaration';
|
||||
import {ImportManager, translateType} from './translator';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Record of an adapter which decided to emit a static field, and the analysis it performed to
|
||||
* prepare for that operation.
|
||||
*/
|
||||
interface EmitFieldOperation<T> {
|
||||
adapter: CompilerAdapter<T>;
|
||||
adapter: DecoratorHandler<T>;
|
||||
analysis: AnalysisOutput<T>;
|
||||
decorator: ts.Decorator;
|
||||
}
|
||||
@ -44,7 +45,7 @@ export class IvyCompilation {
|
||||
*/
|
||||
private dtsMap = new Map<string, DtsFileTransformer>();
|
||||
|
||||
constructor(private adapters: CompilerAdapter<any>[], private checker: ts.TypeChecker) {}
|
||||
constructor(private handlers: DecoratorHandler<any>[], private checker: ts.TypeChecker) {}
|
||||
|
||||
/**
|
||||
* Analyze a source file and produce diagnostics for it (if any).
|
||||
@ -60,8 +61,8 @@ export class IvyCompilation {
|
||||
node.decorators.map(decorator => reflectDecorator(decorator, this.checker))
|
||||
.filter(decorator => decorator !== null) as Decorator[];
|
||||
|
||||
// Look through the CompilerAdapters to see if any are relevant.
|
||||
this.adapters.forEach(adapter => {
|
||||
// Look through the DecoratorHandlers to see if any are relevant.
|
||||
this.handlers.forEach(adapter => {
|
||||
// An adapter is relevant if it matches one of the decorators on the class.
|
||||
const decorator = adapter.detect(decorators);
|
||||
if (decorator === undefined) {
|
||||
@ -101,7 +102,7 @@ export class IvyCompilation {
|
||||
* Perform a compilation operation on the given class declaration and return instructions to an
|
||||
* AST transformer if any are available.
|
||||
*/
|
||||
compileIvyFieldFor(node: ts.ClassDeclaration): AddStaticFieldInstruction|undefined {
|
||||
compileIvyFieldFor(node: ts.ClassDeclaration): CompileResult|undefined {
|
||||
// Look to see whether the original node was analyzed. If not, there's nothing to do.
|
||||
const original = ts.getOriginalNode(node) as ts.ClassDeclaration;
|
||||
if (!this.analysis.has(original)) {
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {AddStaticFieldInstruction} from './api';
|
||||
import {CompileResult} from './api';
|
||||
import {ImportManager, translateType} from './translator';
|
||||
|
||||
|
||||
@ -17,15 +17,13 @@ import {ImportManager, translateType} from './translator';
|
||||
* Processes .d.ts file text and adds static field declarations, with types.
|
||||
*/
|
||||
export class DtsFileTransformer {
|
||||
private ivyFields = new Map<string, AddStaticFieldInstruction>();
|
||||
private ivyFields = new Map<string, CompileResult>();
|
||||
private imports = new ImportManager();
|
||||
|
||||
/**
|
||||
* Track that a static field was added to the code for a class.
|
||||
*/
|
||||
recordStaticField(name: string, decl: AddStaticFieldInstruction): void {
|
||||
this.ivyFields.set(name, decl);
|
||||
}
|
||||
recordStaticField(name: string, decl: CompileResult): void { this.ivyFields.set(name, decl); }
|
||||
|
||||
/**
|
||||
* Process the .d.ts text for a file and add any declarations which were recorded.
|
||||
|
@ -1,185 +0,0 @@
|
||||
/**
|
||||
* @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 {Expression, LiteralExpr, R3DependencyMetadata, R3InjectableMetadata, R3ResolvedDependencyType, WrappedNodeExpr, compileInjectable as compileIvyInjectable} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Decorator} from '../../metadata';
|
||||
import {reflectConstructorParameters, reflectImportedIdentifier, reflectObjectLiteral} from '../../metadata/src/reflector';
|
||||
|
||||
import {AddStaticFieldInstruction, AnalysisOutput, CompilerAdapter} from './api';
|
||||
|
||||
|
||||
/**
|
||||
* Adapts the `compileIvyInjectable` compiler for `@Injectable` decorators to the Ivy compiler.
|
||||
*/
|
||||
export class InjectableCompilerAdapter implements CompilerAdapter<R3InjectableMetadata> {
|
||||
constructor(private checker: ts.TypeChecker) {}
|
||||
|
||||
detect(decorator: Decorator[]): Decorator|undefined {
|
||||
return decorator.find(dec => dec.name === 'Injectable' && dec.from === '@angular/core');
|
||||
}
|
||||
|
||||
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<R3InjectableMetadata> {
|
||||
return {
|
||||
analysis: extractInjectableMetadata(node, decorator, this.checker),
|
||||
};
|
||||
}
|
||||
|
||||
compile(node: ts.ClassDeclaration, analysis: R3InjectableMetadata): AddStaticFieldInstruction {
|
||||
const res = compileIvyInjectable(analysis);
|
||||
return {
|
||||
field: 'ngInjectableDef',
|
||||
initializer: res.expression,
|
||||
type: res.type,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read metadata from the `@Injectable` decorator and produce the `IvyInjectableMetadata`, the input
|
||||
* metadata needed to run `compileIvyInjectable`.
|
||||
*/
|
||||
function extractInjectableMetadata(
|
||||
clazz: ts.ClassDeclaration, decorator: Decorator,
|
||||
checker: ts.TypeChecker): R3InjectableMetadata {
|
||||
if (clazz.name === undefined) {
|
||||
throw new Error(`@Injectables must have names`);
|
||||
}
|
||||
const name = clazz.name.text;
|
||||
const type = new WrappedNodeExpr(clazz.name);
|
||||
if (decorator.args.length === 0) {
|
||||
return {
|
||||
name,
|
||||
type,
|
||||
providedIn: new LiteralExpr(null),
|
||||
deps: getConstructorDependencies(clazz, checker),
|
||||
};
|
||||
} else if (decorator.args.length === 1) {
|
||||
const metaNode = decorator.args[0];
|
||||
// Firstly make sure the decorator argument is an inline literal - if not, it's illegal to
|
||||
// transport references from one location to another. This is the problem that lowering
|
||||
// used to solve - if this restriction proves too undesirable we can re-implement lowering.
|
||||
if (!ts.isObjectLiteralExpression(metaNode)) {
|
||||
throw new Error(`In Ivy, decorator metadata must be inline.`);
|
||||
}
|
||||
|
||||
// Resolve the fields of the literal into a map of field name to expression.
|
||||
const meta = reflectObjectLiteral(metaNode);
|
||||
let providedIn: Expression = new LiteralExpr(null);
|
||||
if (meta.has('providedIn')) {
|
||||
providedIn = new WrappedNodeExpr(meta.get('providedIn') !);
|
||||
}
|
||||
if (meta.has('useValue')) {
|
||||
return {name, type, providedIn, useValue: new WrappedNodeExpr(meta.get('useValue') !)};
|
||||
} else if (meta.has('useExisting')) {
|
||||
return {name, type, providedIn, useExisting: new WrappedNodeExpr(meta.get('useExisting') !)};
|
||||
} else if (meta.has('useClass')) {
|
||||
return {name, type, providedIn, useClass: new WrappedNodeExpr(meta.get('useClass') !)};
|
||||
} else if (meta.has('useFactory')) {
|
||||
// useFactory is special - the 'deps' property must be analyzed.
|
||||
const factory = new WrappedNodeExpr(meta.get('useFactory') !);
|
||||
const deps: R3DependencyMetadata[] = [];
|
||||
if (meta.has('deps')) {
|
||||
const depsExpr = meta.get('deps') !;
|
||||
if (!ts.isArrayLiteralExpression(depsExpr)) {
|
||||
throw new Error(`In Ivy, deps metadata must be inline.`);
|
||||
}
|
||||
if (depsExpr.elements.length > 0) {
|
||||
throw new Error(`deps not yet supported`);
|
||||
}
|
||||
deps.push(...depsExpr.elements.map(dep => getDep(dep, checker)));
|
||||
}
|
||||
return {name, type, providedIn, useFactory: factory, deps};
|
||||
} else {
|
||||
const deps = getConstructorDependencies(clazz, checker);
|
||||
return {name, type, providedIn, deps};
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Too many arguments to @Injectable`);
|
||||
}
|
||||
}
|
||||
|
||||
function getConstructorDependencies(
|
||||
clazz: ts.ClassDeclaration, checker: ts.TypeChecker): R3DependencyMetadata[] {
|
||||
const useType: R3DependencyMetadata[] = [];
|
||||
const ctorParams = (reflectConstructorParameters(clazz, checker) || []);
|
||||
ctorParams.forEach(param => {
|
||||
let tokenExpr = param.typeValueExpr;
|
||||
let optional = false, self = false, skipSelf = false;
|
||||
param.decorators.filter(dec => dec.from === '@angular/core').forEach(dec => {
|
||||
if (dec.name === 'Inject') {
|
||||
if (dec.args.length !== 1) {
|
||||
throw new Error(`Unexpected number of arguments to @Inject().`);
|
||||
}
|
||||
tokenExpr = dec.args[0];
|
||||
} else if (dec.name === 'Optional') {
|
||||
optional = true;
|
||||
} else if (dec.name === 'SkipSelf') {
|
||||
skipSelf = true;
|
||||
} else if (dec.name === 'Self') {
|
||||
self = true;
|
||||
} else {
|
||||
throw new Error(`Unexpected decorator ${dec.name} on parameter.`);
|
||||
}
|
||||
if (tokenExpr === null) {
|
||||
throw new Error(`No suitable token for parameter!`);
|
||||
}
|
||||
});
|
||||
const token = new WrappedNodeExpr(tokenExpr);
|
||||
useType.push(
|
||||
{token, optional, self, skipSelf, host: false, resolved: R3ResolvedDependencyType.Token});
|
||||
});
|
||||
return useType;
|
||||
}
|
||||
|
||||
function getDep(dep: ts.Expression, checker: ts.TypeChecker): R3DependencyMetadata {
|
||||
const meta: R3DependencyMetadata = {
|
||||
token: new WrappedNodeExpr(dep),
|
||||
host: false,
|
||||
resolved: R3ResolvedDependencyType.Token,
|
||||
optional: false,
|
||||
self: false,
|
||||
skipSelf: false,
|
||||
};
|
||||
|
||||
function maybeUpdateDecorator(dec: ts.Identifier, token?: ts.Expression): void {
|
||||
const source = reflectImportedIdentifier(dec, checker);
|
||||
if (source === null || source.from !== '@angular/core') {
|
||||
return;
|
||||
}
|
||||
switch (source.name) {
|
||||
case 'Inject':
|
||||
if (token !== undefined) {
|
||||
meta.token = new WrappedNodeExpr(token);
|
||||
}
|
||||
break;
|
||||
case 'Optional':
|
||||
meta.optional = true;
|
||||
break;
|
||||
case 'SkipSelf':
|
||||
meta.skipSelf = true;
|
||||
break;
|
||||
case 'Self':
|
||||
meta.self = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ts.isArrayLiteralExpression(dep)) {
|
||||
dep.elements.forEach(el => {
|
||||
if (ts.isIdentifier(el)) {
|
||||
maybeUpdateDecorator(el);
|
||||
} else if (ts.isNewExpression(el) && ts.isIdentifier(el.expression)) {
|
||||
const token = el.arguments && el.arguments.length > 0 && el.arguments[0] || undefined;
|
||||
maybeUpdateDecorator(el.expression, token);
|
||||
}
|
||||
});
|
||||
}
|
||||
return meta;
|
||||
}
|
@ -9,8 +9,10 @@
|
||||
import {WrappedNodeExpr} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {VisitListEntryResult, Visitor, visit} from '../../util/src/visitor';
|
||||
|
||||
import {IvyCompilation} from './compilation';
|
||||
import {ImportManager, translateExpression} from './translator';
|
||||
import {ImportManager, translateExpression, translateStatement} from './translator';
|
||||
|
||||
export function ivyTransformFactory(compilation: IvyCompilation):
|
||||
ts.TransformerFactory<ts.SourceFile> {
|
||||
@ -21,6 +23,40 @@ export function ivyTransformFactory(compilation: IvyCompilation):
|
||||
};
|
||||
}
|
||||
|
||||
class IvyVisitor extends Visitor {
|
||||
constructor(private compilation: IvyCompilation, private importManager: ImportManager) {
|
||||
super();
|
||||
}
|
||||
|
||||
visitClassDeclaration(node: ts.ClassDeclaration):
|
||||
VisitListEntryResult<ts.Statement, ts.ClassDeclaration> {
|
||||
// Determine if this class has an Ivy field that needs to be added, and compile the field
|
||||
// to an expression if so.
|
||||
const res = this.compilation.compileIvyFieldFor(node);
|
||||
if (res !== undefined) {
|
||||
// There is a field to add. Translate the initializer for the field into TS nodes.
|
||||
const exprNode = translateExpression(res.initializer, this.importManager);
|
||||
|
||||
// Create a static property declaration for the new field.
|
||||
const property = ts.createProperty(
|
||||
undefined, [ts.createToken(ts.SyntaxKind.StaticKeyword)], res.field, undefined, undefined,
|
||||
exprNode);
|
||||
|
||||
// Replace the class declaration with an updated version.
|
||||
node = ts.updateClassDeclaration(
|
||||
node,
|
||||
// Remove the decorator which triggered this compilation, leaving the others alone.
|
||||
maybeFilterDecorator(node.decorators, this.compilation.ivyDecoratorFor(node) !),
|
||||
node.modifiers, node.name, node.typeParameters, node.heritageClauses || [],
|
||||
[...node.members, property]);
|
||||
const statements = res.statements.map(stmt => translateStatement(stmt, this.importManager));
|
||||
return {node, before: statements};
|
||||
}
|
||||
|
||||
return {node};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A transformer which operates on ts.SourceFiles and applies changes from an `IvyCompilation`.
|
||||
*/
|
||||
@ -30,7 +66,7 @@ function transformIvySourceFile(
|
||||
const importManager = new ImportManager();
|
||||
|
||||
// Recursively scan through the AST and perform any updates requested by the IvyCompilation.
|
||||
const sf = visitNode(file);
|
||||
const sf = visit(file, new IvyVisitor(compilation, importManager), context);
|
||||
|
||||
// Generate the import statements to prepend.
|
||||
const imports = importManager.getAllImports().map(
|
||||
@ -44,44 +80,8 @@ function transformIvySourceFile(
|
||||
sf.statements = ts.createNodeArray([...imports, ...sf.statements]);
|
||||
}
|
||||
return sf;
|
||||
|
||||
// Helper function to process a class declaration.
|
||||
function visitClassDeclaration(node: ts.ClassDeclaration): ts.ClassDeclaration {
|
||||
// Determine if this class has an Ivy field that needs to be added, and compile the field
|
||||
// to an expression if so.
|
||||
const res = compilation.compileIvyFieldFor(node);
|
||||
if (res !== undefined) {
|
||||
// There is a field to add. Translate the initializer for the field into TS nodes.
|
||||
const exprNode = translateExpression(res.initializer, importManager);
|
||||
|
||||
// Create a static property declaration for the new field.
|
||||
const property = ts.createProperty(
|
||||
undefined, [ts.createToken(ts.SyntaxKind.StaticKeyword)], res.field, undefined, undefined,
|
||||
exprNode);
|
||||
|
||||
// Replace the class declaration with an updated version.
|
||||
node = ts.updateClassDeclaration(
|
||||
node,
|
||||
// Remove the decorator which triggered this compilation, leaving the others alone.
|
||||
maybeFilterDecorator(node.decorators, compilation.ivyDecoratorFor(node) !),
|
||||
node.modifiers, node.name, node.typeParameters, node.heritageClauses || [],
|
||||
[...node.members, property]);
|
||||
}
|
||||
|
||||
// Recurse into the class declaration in case there are nested class declarations.
|
||||
return ts.visitEachChild(node, child => visitNode(child), context);
|
||||
}
|
||||
|
||||
// Helper function that recurses through the nodes and processes each one.
|
||||
function visitNode<T extends ts.Node>(node: T): T;
|
||||
function visitNode(node: ts.Node): ts.Node {
|
||||
if (ts.isClassDeclaration(node)) {
|
||||
return visitClassDeclaration(node);
|
||||
} else {
|
||||
return ts.visitEachChild(node, child => visitNode(child), context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function maybeFilterDecorator(
|
||||
decorators: ts.NodeArray<ts.Decorator>| undefined,
|
||||
toRemove: ts.Decorator): ts.NodeArray<ts.Decorator>|undefined {
|
||||
|
@ -6,9 +6,28 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ArrayType, AssertNotNull, BinaryOperatorExpr, BuiltinType, BuiltinTypeName, CastExpr, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, Expression, ExpressionStatement, ExpressionType, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, JSDocCommentStmt, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, MapType, NotExpr, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, StatementVisitor, ThrowStmt, TryCatchStmt, Type, TypeVisitor, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler';
|
||||
import {ArrayType, AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinType, BuiltinTypeName, CastExpr, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, Expression, ExpressionStatement, ExpressionType, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, JSDocCommentStmt, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, MapType, NotExpr, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, Statement, StatementVisitor, ThrowStmt, TryCatchStmt, Type, TypeVisitor, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
const BINARY_OPERATORS = new Map<BinaryOperator, ts.BinaryOperator>([
|
||||
[BinaryOperator.And, ts.SyntaxKind.AmpersandAmpersandToken],
|
||||
[BinaryOperator.Bigger, ts.SyntaxKind.GreaterThanToken],
|
||||
[BinaryOperator.BiggerEquals, ts.SyntaxKind.GreaterThanEqualsToken],
|
||||
[BinaryOperator.BitwiseAnd, ts.SyntaxKind.AmpersandToken],
|
||||
[BinaryOperator.Divide, ts.SyntaxKind.SlashToken],
|
||||
[BinaryOperator.Equals, ts.SyntaxKind.EqualsEqualsToken],
|
||||
[BinaryOperator.Identical, ts.SyntaxKind.EqualsEqualsEqualsToken],
|
||||
[BinaryOperator.Lower, ts.SyntaxKind.LessThanToken],
|
||||
[BinaryOperator.LowerEquals, ts.SyntaxKind.LessThanEqualsToken],
|
||||
[BinaryOperator.Minus, ts.SyntaxKind.MinusToken],
|
||||
[BinaryOperator.Modulo, ts.SyntaxKind.PercentToken],
|
||||
[BinaryOperator.Multiply, ts.SyntaxKind.AsteriskToken],
|
||||
[BinaryOperator.NotEquals, ts.SyntaxKind.ExclamationEqualsToken],
|
||||
[BinaryOperator.NotIdentical, ts.SyntaxKind.ExclamationEqualsEqualsToken],
|
||||
[BinaryOperator.Or, ts.SyntaxKind.BarBarToken],
|
||||
[BinaryOperator.Plus, ts.SyntaxKind.PlusToken],
|
||||
]);
|
||||
|
||||
export class ImportManager {
|
||||
private moduleToIndex = new Map<string, string>();
|
||||
private nextIndex = 0;
|
||||
@ -32,6 +51,10 @@ export function translateExpression(expression: Expression, imports: ImportManag
|
||||
return expression.visitExpression(new ExpressionTranslatorVisitor(imports), null);
|
||||
}
|
||||
|
||||
export function translateStatement(statement: Statement, imports: ImportManager): ts.Statement {
|
||||
return statement.visitStatement(new ExpressionTranslatorVisitor(imports), null);
|
||||
}
|
||||
|
||||
export function translateType(type: Type, imports: ImportManager): string {
|
||||
return type.visitType(new TypeTranslatorVisitor(imports), null);
|
||||
}
|
||||
@ -39,16 +62,23 @@ export function translateType(type: Type, imports: ImportManager): string {
|
||||
class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor {
|
||||
constructor(private imports: ImportManager) {}
|
||||
|
||||
visitDeclareVarStmt(stmt: DeclareVarStmt, context: any) {
|
||||
throw new Error('Method not implemented.');
|
||||
visitDeclareVarStmt(stmt: DeclareVarStmt, context: any): ts.VariableStatement {
|
||||
return ts.createVariableStatement(
|
||||
undefined,
|
||||
ts.createVariableDeclarationList([ts.createVariableDeclaration(
|
||||
stmt.name, undefined, stmt.value && stmt.value.visitExpression(this, context))]));
|
||||
}
|
||||
|
||||
visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: any) {
|
||||
throw new Error('Method not implemented.');
|
||||
visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: any): ts.FunctionDeclaration {
|
||||
return ts.createFunctionDeclaration(
|
||||
undefined, undefined, undefined, stmt.name, undefined,
|
||||
stmt.params.map(param => ts.createParameter(undefined, undefined, undefined, param.name)),
|
||||
undefined,
|
||||
ts.createBlock(stmt.statements.map(child => child.visitStatement(this, context))));
|
||||
}
|
||||
|
||||
visitExpressionStmt(stmt: ExpressionStatement, context: any) {
|
||||
throw new Error('Method not implemented.');
|
||||
visitExpressionStmt(stmt: ExpressionStatement, context: any): ts.ExpressionStatement {
|
||||
return ts.createStatement(stmt.expr.visitExpression(this, context));
|
||||
}
|
||||
|
||||
visitReturnStmt(stmt: ReturnStatement, context: any): ts.ReturnStatement {
|
||||
@ -59,7 +89,14 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitIfStmt(stmt: IfStmt, context: any) { throw new Error('Method not implemented.'); }
|
||||
visitIfStmt(stmt: IfStmt, context: any): ts.IfStatement {
|
||||
return ts.createIf(
|
||||
stmt.condition.visitExpression(this, context),
|
||||
ts.createBlock(stmt.trueCase.map(child => child.visitStatement(this, context))),
|
||||
stmt.falseCase.length > 0 ?
|
||||
ts.createBlock(stmt.falseCase.map(child => child.visitStatement(this, context))) :
|
||||
undefined);
|
||||
}
|
||||
|
||||
visitTryCatchStmt(stmt: TryCatchStmt, context: any) {
|
||||
throw new Error('Method not implemented.');
|
||||
@ -89,12 +126,17 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitWritePropExpr(expr: WritePropExpr, context: any): never {
|
||||
throw new Error('Method not implemented.');
|
||||
visitWritePropExpr(expr: WritePropExpr, context: any): ts.BinaryExpression {
|
||||
return ts.createBinary(
|
||||
ts.createPropertyAccess(expr.receiver.visitExpression(this, context), expr.name),
|
||||
ts.SyntaxKind.EqualsToken, expr.value.visitExpression(this, context));
|
||||
}
|
||||
|
||||
visitInvokeMethodExpr(ast: InvokeMethodExpr, context: any): never {
|
||||
throw new Error('Method not implemented.');
|
||||
visitInvokeMethodExpr(ast: InvokeMethodExpr, context: any): ts.CallExpression {
|
||||
const target = ast.receiver.visitExpression(this, context);
|
||||
return ts.createCall(
|
||||
ast.name !== null ? ts.createPropertyAccess(target, ast.name) : target, undefined,
|
||||
ast.args.map(arg => arg.visitExpression(this, context)));
|
||||
}
|
||||
|
||||
visitInvokeFunctionExpr(ast: InvokeFunctionExpr, context: any): ts.CallExpression {
|
||||
@ -156,20 +198,26 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
|
||||
undefined, ts.createBlock(ast.statements.map(stmt => stmt.visitStatement(this, context))));
|
||||
}
|
||||
|
||||
visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: any): never {
|
||||
throw new Error('Method not implemented.');
|
||||
visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: any): ts.Expression {
|
||||
if (!BINARY_OPERATORS.has(ast.operator)) {
|
||||
throw new Error(`Unknown binary operator: ${BinaryOperator[ast.operator]}`);
|
||||
}
|
||||
const binEx = ts.createBinary(
|
||||
ast.lhs.visitExpression(this, context), BINARY_OPERATORS.get(ast.operator) !,
|
||||
ast.rhs.visitExpression(this, context));
|
||||
return ast.parens ? ts.createParen(binEx) : binEx;
|
||||
}
|
||||
|
||||
visitReadPropExpr(ast: ReadPropExpr, context: any): never {
|
||||
throw new Error('Method not implemented.');
|
||||
visitReadPropExpr(ast: ReadPropExpr, context: any): ts.PropertyAccessExpression {
|
||||
return ts.createPropertyAccess(ast.receiver.visitExpression(this, context), ast.name);
|
||||
}
|
||||
|
||||
visitReadKeyExpr(ast: ReadKeyExpr, context: any): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): never {
|
||||
throw new Error('Method not implemented.');
|
||||
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): ts.ArrayLiteralExpression {
|
||||
return ts.createArrayLiteral(ast.entries.map(expr => expr.visitExpression(this, context)));
|
||||
}
|
||||
|
||||
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): ts.ObjectLiteralExpression {
|
||||
@ -297,8 +345,9 @@ export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor {
|
||||
|
||||
visitReadKeyExpr(ast: ReadKeyExpr, context: any) { throw new Error('Method not implemented.'); }
|
||||
|
||||
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any) {
|
||||
throw new Error('Method not implemented.');
|
||||
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): string {
|
||||
const values = ast.entries.map(expr => expr.visitExpression(this, context));
|
||||
return `[${values.join(',')}]`;
|
||||
}
|
||||
|
||||
visitLiteralMapExpr(ast: LiteralMapExpr, context: any) {
|
||||
|
Reference in New Issue
Block a user