diff --git a/packages/compiler-cli/src/ngtsc/transform/src/translator.ts b/packages/compiler-cli/src/ngtsc/transform/src/translator.ts index 339778938f..2fd42200be 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/translator.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/translator.ts @@ -6,10 +6,19 @@ * found in the LICENSE file at https://angular.io/license */ -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 {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, StmtModifier, ThrowStmt, TryCatchStmt, Type, TypeVisitor, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler'; import * as ts from 'typescript'; + import {relativePathBetween} from '../../util/src/path'; +export class Context { + constructor(readonly isStatement: boolean) {} + + get withExpressionMode(): Context { return this.isStatement ? new Context(false) : this; } + + get withStatementMode(): Context { return this.isStatement ? new Context(true) : this; } +} + const BINARY_OPERATORS = new Map([ [BinaryOperator.And, ts.SyntaxKind.AmpersandAmpersandToken], [BinaryOperator.Bigger, ts.SyntaxKind.GreaterThanToken], @@ -73,110 +82,120 @@ export class ImportManager { } export function translateExpression(expression: Expression, imports: ImportManager): ts.Expression { - return expression.visitExpression(new ExpressionTranslatorVisitor(imports), null); + return expression.visitExpression(new ExpressionTranslatorVisitor(imports), new Context(false)); } export function translateStatement(statement: Statement, imports: ImportManager): ts.Statement { - return statement.visitStatement(new ExpressionTranslatorVisitor(imports), null); + return statement.visitStatement(new ExpressionTranslatorVisitor(imports), new Context(true)); } export function translateType(type: Type, imports: ImportManager): string { - return type.visitType(new TypeTranslatorVisitor(imports), null); + return type.visitType(new TypeTranslatorVisitor(imports), new Context(false)); } class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor { constructor(private imports: ImportManager) {} - visitDeclareVarStmt(stmt: DeclareVarStmt, context: any): ts.VariableStatement { + visitDeclareVarStmt(stmt: DeclareVarStmt, context: Context): ts.VariableStatement { + const nodeFlags = stmt.hasModifier(StmtModifier.Final) ? ts.NodeFlags.Const : ts.NodeFlags.None; return ts.createVariableStatement( - undefined, - ts.createVariableDeclarationList([ts.createVariableDeclaration( - stmt.name, undefined, stmt.value && stmt.value.visitExpression(this, context))])); + undefined, ts.createVariableDeclarationList( + [ts.createVariableDeclaration( + stmt.name, undefined, stmt.value && + stmt.value.visitExpression(this, context.withExpressionMode))], + nodeFlags)); } - visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: any): ts.FunctionDeclaration { + visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: Context): 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)))); + undefined, ts.createBlock(stmt.statements.map( + child => child.visitStatement(this, context.withStatementMode)))); } - visitExpressionStmt(stmt: ExpressionStatement, context: any): ts.ExpressionStatement { - return ts.createStatement(stmt.expr.visitExpression(this, context)); + visitExpressionStmt(stmt: ExpressionStatement, context: Context): ts.ExpressionStatement { + return ts.createStatement(stmt.expr.visitExpression(this, context.withStatementMode)); } - visitReturnStmt(stmt: ReturnStatement, context: any): ts.ReturnStatement { - return ts.createReturn(stmt.value.visitExpression(this, context)); + visitReturnStmt(stmt: ReturnStatement, context: Context): ts.ReturnStatement { + return ts.createReturn(stmt.value.visitExpression(this, context.withExpressionMode)); } - visitDeclareClassStmt(stmt: ClassStmt, context: any) { + visitDeclareClassStmt(stmt: ClassStmt, context: Context) { throw new Error('Method not implemented.'); } - visitIfStmt(stmt: IfStmt, context: any): ts.IfStatement { + visitIfStmt(stmt: IfStmt, context: Context): ts.IfStatement { return ts.createIf( stmt.condition.visitExpression(this, context), - ts.createBlock(stmt.trueCase.map(child => child.visitStatement(this, context))), + ts.createBlock( + stmt.trueCase.map(child => child.visitStatement(this, context.withStatementMode))), stmt.falseCase.length > 0 ? - ts.createBlock(stmt.falseCase.map(child => child.visitStatement(this, context))) : + ts.createBlock(stmt.falseCase.map( + child => child.visitStatement(this, context.withStatementMode))) : undefined); } - visitTryCatchStmt(stmt: TryCatchStmt, context: any) { + visitTryCatchStmt(stmt: TryCatchStmt, context: Context) { throw new Error('Method not implemented.'); } - visitThrowStmt(stmt: ThrowStmt, context: any) { throw new Error('Method not implemented.'); } + visitThrowStmt(stmt: ThrowStmt, context: Context) { throw new Error('Method not implemented.'); } - visitCommentStmt(stmt: CommentStmt, context: any): never { + visitCommentStmt(stmt: CommentStmt, context: Context): never { throw new Error('Method not implemented.'); } - visitJSDocCommentStmt(stmt: JSDocCommentStmt, context: any): never { - throw new Error('Method not implemented.'); + visitJSDocCommentStmt(stmt: JSDocCommentStmt, context: Context): ts.NotEmittedStatement { + const commentStmt = ts.createNotEmittedStatement(ts.createLiteral('')); + const text = stmt.toString(); + const kind = ts.SyntaxKind.MultiLineCommentTrivia; + ts.setSyntheticLeadingComments(commentStmt, [{kind, text, pos: -1, end: -1}]); + return commentStmt; } - visitReadVarExpr(ast: ReadVarExpr, context: any): ts.Identifier { + visitReadVarExpr(ast: ReadVarExpr, context: Context): ts.Identifier { return ts.createIdentifier(ast.name !); } - visitWriteVarExpr(expr: WriteVarExpr, context: any): ts.BinaryExpression { - return ts.createBinary( + visitWriteVarExpr(expr: WriteVarExpr, context: Context): ts.Expression { + const result: ts.Expression = ts.createBinary( ts.createIdentifier(expr.name), ts.SyntaxKind.EqualsToken, expr.value.visitExpression(this, context)); + return context.isStatement ? result : ts.createParen(result); } - visitWriteKeyExpr(expr: WriteKeyExpr, context: any): never { + visitWriteKeyExpr(expr: WriteKeyExpr, context: Context): never { throw new Error('Method not implemented.'); } - visitWritePropExpr(expr: WritePropExpr, context: any): ts.BinaryExpression { + visitWritePropExpr(expr: WritePropExpr, context: Context): 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): ts.CallExpression { + visitInvokeMethodExpr(ast: InvokeMethodExpr, context: Context): 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 { + visitInvokeFunctionExpr(ast: InvokeFunctionExpr, context: Context): ts.CallExpression { return ts.createCall( ast.fn.visitExpression(this, context), undefined, ast.args.map(arg => arg.visitExpression(this, context))); } - visitInstantiateExpr(ast: InstantiateExpr, context: any): ts.NewExpression { + visitInstantiateExpr(ast: InstantiateExpr, context: Context): ts.NewExpression { return ts.createNew( ast.classExpr.visitExpression(this, context), undefined, ast.args.map(arg => arg.visitExpression(this, context))); } - visitLiteralExpr(ast: LiteralExpr, context: any): ts.Expression { + visitLiteralExpr(ast: LiteralExpr, context: Context): ts.Expression { if (ast.value === undefined) { return ts.createIdentifier('undefined'); } else if (ast.value === null) { @@ -186,7 +205,7 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor } } - visitExternalExpr(ast: ExternalExpr, context: any): ts.PropertyAccessExpression { + visitExternalExpr(ast: ExternalExpr, context: Context): ts.PropertyAccessExpression { if (ast.value.moduleName === null || ast.value.name === null) { throw new Error(`Import unknown module or symbol ${ast.value}`); } @@ -195,26 +214,26 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor ts.createIdentifier(ast.value.name)); } - visitConditionalExpr(ast: ConditionalExpr, context: any): ts.ParenthesizedExpression { + visitConditionalExpr(ast: ConditionalExpr, context: Context): ts.ParenthesizedExpression { return ts.createParen(ts.createConditional( ast.condition.visitExpression(this, context), ast.trueCase.visitExpression(this, context), ast.falseCase !.visitExpression(this, context))); } - visitNotExpr(ast: NotExpr, context: any): ts.PrefixUnaryExpression { + visitNotExpr(ast: NotExpr, context: Context): ts.PrefixUnaryExpression { return ts.createPrefix( ts.SyntaxKind.ExclamationToken, ast.condition.visitExpression(this, context)); } - visitAssertNotNullExpr(ast: AssertNotNull, context: any): ts.NonNullExpression { + visitAssertNotNullExpr(ast: AssertNotNull, context: Context): ts.NonNullExpression { return ts.createNonNullExpression(ast.condition.visitExpression(this, context)); } - visitCastExpr(ast: CastExpr, context: any): ts.Expression { + visitCastExpr(ast: CastExpr, context: Context): ts.Expression { return ast.value.visitExpression(this, context); } - visitFunctionExpr(ast: FunctionExpr, context: any): ts.FunctionExpression { + visitFunctionExpr(ast: FunctionExpr, context: Context): ts.FunctionExpression { return ts.createFunctionExpression( undefined, undefined, ast.name || undefined, undefined, ast.params.map( @@ -223,7 +242,7 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor undefined, ts.createBlock(ast.statements.map(stmt => stmt.visitStatement(this, context)))); } - visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: any): ts.Expression { + visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: Context): ts.Expression { if (!BINARY_OPERATORS.has(ast.operator)) { throw new Error(`Unknown binary operator: ${BinaryOperator[ast.operator]}`); } @@ -233,20 +252,20 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor return ast.parens ? ts.createParen(binEx) : binEx; } - visitReadPropExpr(ast: ReadPropExpr, context: any): ts.PropertyAccessExpression { + visitReadPropExpr(ast: ReadPropExpr, context: Context): ts.PropertyAccessExpression { return ts.createPropertyAccess(ast.receiver.visitExpression(this, context), ast.name); } - visitReadKeyExpr(ast: ReadKeyExpr, context: any): ts.ElementAccessExpression { + visitReadKeyExpr(ast: ReadKeyExpr, context: Context): ts.ElementAccessExpression { return ts.createElementAccess( ast.receiver.visitExpression(this, context), ast.index.visitExpression(this, context)); } - visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): ts.ArrayLiteralExpression { + visitLiteralArrayExpr(ast: LiteralArrayExpr, context: Context): ts.ArrayLiteralExpression { return ts.createArrayLiteral(ast.entries.map(expr => expr.visitExpression(this, context))); } - visitLiteralMapExpr(ast: LiteralMapExpr, context: any): ts.ObjectLiteralExpression { + visitLiteralMapExpr(ast: LiteralMapExpr, context: Context): ts.ObjectLiteralExpression { const entries = ast.entries.map( entry => ts.createPropertyAssignment( entry.quoted ? ts.createLiteral(entry.key) : ts.createIdentifier(entry.key), @@ -254,17 +273,17 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor return ts.createObjectLiteral(entries); } - visitCommaExpr(ast: CommaExpr, context: any): never { + visitCommaExpr(ast: CommaExpr, context: Context): never { throw new Error('Method not implemented.'); } - visitWrappedNodeExpr(ast: WrappedNodeExpr, context: any): any { return ast.node; } + visitWrappedNodeExpr(ast: WrappedNodeExpr, context: Context): any { return ast.node; } } export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor { constructor(private imports: ImportManager) {} - visitBuiltinType(type: BuiltinType, context: any): string { + visitBuiltinType(type: BuiltinType, context: Context): string { switch (type.name) { case BuiltinTypeName.Bool: return 'boolean'; @@ -280,15 +299,15 @@ export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor { } } - visitExpressionType(type: ExpressionType, context: any): any { + visitExpressionType(type: ExpressionType, context: Context): any { return type.value.visitExpression(this, context); } - visitArrayType(type: ArrayType, context: any): string { + visitArrayType(type: ArrayType, context: Context): string { return `Array<${type.visitType(this, context)}>`; } - visitMapType(type: MapType, context: any): string { + visitMapType(type: MapType, context: Context): string { if (type.valueType !== null) { return `{[key: string]: ${type.valueType.visitType(this, context)}}`; } else { @@ -296,38 +315,38 @@ export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor { } } - visitReadVarExpr(ast: ReadVarExpr, context: any): string { + visitReadVarExpr(ast: ReadVarExpr, context: Context): string { if (ast.name === null) { throw new Error(`ReadVarExpr with no variable name in type`); } return ast.name; } - visitWriteVarExpr(expr: WriteVarExpr, context: any): never { + visitWriteVarExpr(expr: WriteVarExpr, context: Context): never { throw new Error('Method not implemented.'); } - visitWriteKeyExpr(expr: WriteKeyExpr, context: any): never { + visitWriteKeyExpr(expr: WriteKeyExpr, context: Context): never { throw new Error('Method not implemented.'); } - visitWritePropExpr(expr: WritePropExpr, context: any): never { + visitWritePropExpr(expr: WritePropExpr, context: Context): never { throw new Error('Method not implemented.'); } - visitInvokeMethodExpr(ast: InvokeMethodExpr, context: any): never { + visitInvokeMethodExpr(ast: InvokeMethodExpr, context: Context): never { throw new Error('Method not implemented.'); } - visitInvokeFunctionExpr(ast: InvokeFunctionExpr, context: any): never { + visitInvokeFunctionExpr(ast: InvokeFunctionExpr, context: Context): never { throw new Error('Method not implemented.'); } - visitInstantiateExpr(ast: InstantiateExpr, context: any): never { + visitInstantiateExpr(ast: InstantiateExpr, context: Context): never { throw new Error('Method not implemented.'); } - visitLiteralExpr(ast: LiteralExpr, context: any): string { + visitLiteralExpr(ast: LiteralExpr, context: Context): string { if (typeof ast.value === 'string') { const escaped = ast.value.replace(/\'/g, '\\\''); return `'${escaped}'`; @@ -336,7 +355,7 @@ export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor { } } - visitExternalExpr(ast: ExternalExpr, context: any): string { + visitExternalExpr(ast: ExternalExpr, context: Context): string { if (ast.value.moduleName === null || ast.value.name === null) { throw new Error(`Import unknown module or symbol`); } @@ -350,40 +369,46 @@ export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor { } } - visitConditionalExpr(ast: ConditionalExpr, context: any) { + visitConditionalExpr(ast: ConditionalExpr, context: Context) { throw new Error('Method not implemented.'); } - visitNotExpr(ast: NotExpr, context: any) { throw new Error('Method not implemented.'); } + visitNotExpr(ast: NotExpr, context: Context) { throw new Error('Method not implemented.'); } - visitAssertNotNullExpr(ast: AssertNotNull, context: any) { + visitAssertNotNullExpr(ast: AssertNotNull, context: Context) { throw new Error('Method not implemented.'); } - visitCastExpr(ast: CastExpr, context: any) { throw new Error('Method not implemented.'); } + visitCastExpr(ast: CastExpr, context: Context) { throw new Error('Method not implemented.'); } - visitFunctionExpr(ast: FunctionExpr, context: any) { throw new Error('Method not implemented.'); } - - visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: any) { + visitFunctionExpr(ast: FunctionExpr, context: Context) { throw new Error('Method not implemented.'); } - visitReadPropExpr(ast: ReadPropExpr, context: any) { throw new Error('Method not implemented.'); } + visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: Context) { + throw new Error('Method not implemented.'); + } - visitReadKeyExpr(ast: ReadKeyExpr, context: any) { throw new Error('Method not implemented.'); } + visitReadPropExpr(ast: ReadPropExpr, context: Context) { + throw new Error('Method not implemented.'); + } - visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): string { + visitReadKeyExpr(ast: ReadKeyExpr, context: Context) { + throw new Error('Method not implemented.'); + } + + visitLiteralArrayExpr(ast: LiteralArrayExpr, context: Context): string { const values = ast.entries.map(expr => expr.visitExpression(this, context)); return `[${values.join(',')}]`; } - visitLiteralMapExpr(ast: LiteralMapExpr, context: any) { + visitLiteralMapExpr(ast: LiteralMapExpr, context: Context) { throw new Error('Method not implemented.'); } - visitCommaExpr(ast: CommaExpr, context: any) { throw new Error('Method not implemented.'); } + visitCommaExpr(ast: CommaExpr, context: Context) { throw new Error('Method not implemented.'); } - visitWrappedNodeExpr(ast: WrappedNodeExpr, context: any) { + visitWrappedNodeExpr(ast: WrappedNodeExpr, context: Context) { const node: ts.Node = ast.node; if (ts.isIdentifier(node)) { return node.text;