From 15dfd3439a79f47e4e82552748a4956a40686b10 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Mon, 21 Sep 2020 14:02:52 +0100 Subject: [PATCH] refactor(compiler-cli): split up translator file (#38775) This file contains a number of classes making it long and hard to work with. This commit splits the `ImportManager`, `Context` and `TypeTranslatorVisitor` classes, along with associated functions and types into their own files. PR Close #38775 --- .../src/ngtsc/translator/index.ts | 4 +- .../src/ngtsc/translator/src/context.ts | 24 ++ .../ngtsc/translator/src/import_manager.ts | 67 ++++ .../src/ngtsc/translator/src/translator.ts | 306 +----------------- .../ngtsc/translator/src/type_translator.ts | 244 ++++++++++++++ 5 files changed, 342 insertions(+), 303 deletions(-) create mode 100644 packages/compiler-cli/src/ngtsc/translator/src/context.ts create mode 100644 packages/compiler-cli/src/ngtsc/translator/src/import_manager.ts create mode 100644 packages/compiler-cli/src/ngtsc/translator/src/type_translator.ts diff --git a/packages/compiler-cli/src/ngtsc/translator/index.ts b/packages/compiler-cli/src/ngtsc/translator/index.ts index 64338c5488..102b3d406a 100644 --- a/packages/compiler-cli/src/ngtsc/translator/index.ts +++ b/packages/compiler-cli/src/ngtsc/translator/index.ts @@ -6,4 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -export {attachComments, Import, ImportManager, NamedImport, translateExpression, translateStatement, translateType} from './src/translator'; +export {Import, ImportManager, NamedImport} from './src/import_manager'; +export {attachComments, translateExpression, translateStatement} from './src/translator'; +export {translateType} from './src/type_translator'; \ No newline at end of file diff --git a/packages/compiler-cli/src/ngtsc/translator/src/context.ts b/packages/compiler-cli/src/ngtsc/translator/src/context.ts new file mode 100644 index 0000000000..9e606d0b76 --- /dev/null +++ b/packages/compiler-cli/src/ngtsc/translator/src/context.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright Google LLC 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 + */ + +/** + * The current context of a translator visitor as it traverses the AST tree. + * + * It tracks whether we are in the process of outputting a statement or an expression. + */ +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; + } +} diff --git a/packages/compiler-cli/src/ngtsc/translator/src/import_manager.ts b/packages/compiler-cli/src/ngtsc/translator/src/import_manager.ts new file mode 100644 index 0000000000..2674b2d297 --- /dev/null +++ b/packages/compiler-cli/src/ngtsc/translator/src/import_manager.ts @@ -0,0 +1,67 @@ +/** + * @license + * Copyright Google LLC 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 {ImportRewriter, NoopImportRewriter} from '../../imports/src/core'; + +/** + * Information about an import that has been added to a module. + */ +export interface Import { + /** The name of the module that has been imported. */ + specifier: string; + /** The alias of the imported module. */ + qualifier: string; +} + +/** + * The symbol name and import namespace of an imported symbol, + * which has been registered through the ImportManager. + */ +export interface NamedImport { + /** The import namespace containing this imported symbol. */ + moduleImport: string|null; + /** The (possibly rewritten) name of the imported symbol. */ + symbol: string; +} + +export class ImportManager { + private specifierToIdentifier = new Map(); + private nextIndex = 0; + + constructor(protected rewriter: ImportRewriter = new NoopImportRewriter(), private prefix = 'i') { + } + + generateNamedImport(moduleName: string, originalSymbol: string): NamedImport { + // First, rewrite the symbol name. + const symbol = this.rewriter.rewriteSymbol(originalSymbol, moduleName); + + // Ask the rewriter if this symbol should be imported at all. If not, it can be referenced + // directly (moduleImport: null). + if (!this.rewriter.shouldImportSymbol(symbol, moduleName)) { + // The symbol should be referenced directly. + return {moduleImport: null, symbol}; + } + + // If not, this symbol will be imported. Allocate a prefix for the imported module if needed. + + if (!this.specifierToIdentifier.has(moduleName)) { + this.specifierToIdentifier.set(moduleName, `${this.prefix}${this.nextIndex++}`); + } + const moduleImport = this.specifierToIdentifier.get(moduleName)!; + + return {moduleImport, symbol}; + } + + getAllImports(contextPath: string): Import[] { + const imports: {specifier: string, qualifier: string}[] = []; + this.specifierToIdentifier.forEach((qualifier, specifier) => { + specifier = this.rewriter.rewriteSpecifier(specifier, contextPath); + imports.push({specifier, qualifier}); + }); + return imports; + } +} diff --git a/packages/compiler-cli/src/ngtsc/translator/src/translator.ts b/packages/compiler-cli/src/ngtsc/translator/src/translator.ts index f4465c6974..c859923976 100644 --- a/packages/compiler-cli/src/ngtsc/translator/src/translator.ts +++ b/packages/compiler-cli/src/ngtsc/translator/src/translator.ts @@ -6,23 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ -import {ArrayType, AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinType, BuiltinTypeName, CastExpr, ClassStmt, CommaExpr, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, Expression, ExpressionStatement, ExpressionType, ExpressionVisitor, ExternalExpr, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, LeadingComment, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, MapType, NotExpr, ParseSourceSpan, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, Statement, StatementVisitor, StmtModifier, ThrowStmt, TryCatchStmt, Type, TypeofExpr, TypeVisitor, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler'; +import {AssertNotNull, BinaryOperator, BinaryOperatorExpr, CastExpr, ClassStmt, CommaExpr, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, Expression, ExpressionStatement, ExpressionVisitor, ExternalExpr, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, LeadingComment, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, NotExpr, ParseSourceSpan, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, Statement, StatementVisitor, StmtModifier, ThrowStmt, TryCatchStmt, TypeofExpr, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler'; import {LocalizedString, UnaryOperator, UnaryOperatorExpr} from '@angular/compiler/src/output/output_ast'; import * as ts from 'typescript'; -import {DefaultImportRecorder, ImportRewriter, NOOP_DEFAULT_IMPORT_RECORDER, NoopImportRewriter} from '../../imports'; - -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; - } -} +import {DefaultImportRecorder} from '../../imports'; +import {Context} from './context'; +import {ImportManager} from './import_manager'; const UNARY_OPERATORS = new Map([ [UnaryOperator.Minus, ts.SyntaxKind.MinusToken], @@ -48,65 +38,6 @@ const BINARY_OPERATORS = new Map([ [BinaryOperator.Plus, ts.SyntaxKind.PlusToken], ]); -/** - * Information about an import that has been added to a module. - */ -export interface Import { - /** The name of the module that has been imported. */ - specifier: string; - /** The alias of the imported module. */ - qualifier: string; -} - -/** - * The symbol name and import namespace of an imported symbol, - * which has been registered through the ImportManager. - */ -export interface NamedImport { - /** The import namespace containing this imported symbol. */ - moduleImport: string|null; - /** The (possibly rewritten) name of the imported symbol. */ - symbol: string; -} - -export class ImportManager { - private specifierToIdentifier = new Map(); - private nextIndex = 0; - - constructor(protected rewriter: ImportRewriter = new NoopImportRewriter(), private prefix = 'i') { - } - - generateNamedImport(moduleName: string, originalSymbol: string): NamedImport { - // First, rewrite the symbol name. - const symbol = this.rewriter.rewriteSymbol(originalSymbol, moduleName); - - // Ask the rewriter if this symbol should be imported at all. If not, it can be referenced - // directly (moduleImport: null). - if (!this.rewriter.shouldImportSymbol(symbol, moduleName)) { - // The symbol should be referenced directly. - return {moduleImport: null, symbol}; - } - - // If not, this symbol will be imported. Allocate a prefix for the imported module if needed. - - if (!this.specifierToIdentifier.has(moduleName)) { - this.specifierToIdentifier.set(moduleName, `${this.prefix}${this.nextIndex++}`); - } - const moduleImport = this.specifierToIdentifier.get(moduleName)!; - - return {moduleImport, symbol}; - } - - getAllImports(contextPath: string): Import[] { - const imports: {specifier: string, qualifier: string}[] = []; - this.specifierToIdentifier.forEach((qualifier, specifier) => { - specifier = this.rewriter.rewriteSpecifier(specifier, contextPath); - imports.push({specifier, qualifier}); - }); - return imports; - } -} - export function translateExpression( expression: Expression, imports: ImportManager, defaultImportRecorder: DefaultImportRecorder, scriptTarget: Exclude): ts.Expression { @@ -123,9 +54,6 @@ export function translateStatement( new Context(true)); } -export function translateType(type: Type, imports: ImportManager): ts.TypeNode { - return type.visitType(new TypeTranslatorVisitor(imports), new Context(false)); -} class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor { private externalSourceFiles = new Map(); @@ -544,232 +472,6 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor } } -export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor { - constructor(private imports: ImportManager) {} - - visitBuiltinType(type: BuiltinType, context: Context): ts.KeywordTypeNode { - switch (type.name) { - case BuiltinTypeName.Bool: - return ts.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword); - case BuiltinTypeName.Dynamic: - return ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword); - case BuiltinTypeName.Int: - case BuiltinTypeName.Number: - return ts.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword); - case BuiltinTypeName.String: - return ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword); - case BuiltinTypeName.None: - return ts.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword); - default: - throw new Error(`Unsupported builtin type: ${BuiltinTypeName[type.name]}`); - } - } - - visitExpressionType(type: ExpressionType, context: Context): ts.TypeNode { - const typeNode = this.translateExpression(type.value, context); - if (type.typeParams === null) { - return typeNode; - } - - if (!ts.isTypeReferenceNode(typeNode)) { - throw new Error( - 'An ExpressionType with type arguments must translate into a TypeReferenceNode'); - } else if (typeNode.typeArguments !== undefined) { - throw new Error( - `An ExpressionType with type arguments cannot have multiple levels of type arguments`); - } - - const typeArgs = type.typeParams.map(param => this.translateType(param, context)); - return ts.createTypeReferenceNode(typeNode.typeName, typeArgs); - } - - visitArrayType(type: ArrayType, context: Context): ts.ArrayTypeNode { - return ts.createArrayTypeNode(this.translateType(type.of, context)); - } - - visitMapType(type: MapType, context: Context): ts.TypeLiteralNode { - const parameter = ts.createParameter( - undefined, undefined, undefined, 'key', undefined, - ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)); - const typeArgs = type.valueType !== null ? - this.translateType(type.valueType, context) : - ts.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword); - const indexSignature = ts.createIndexSignature(undefined, undefined, [parameter], typeArgs); - return ts.createTypeLiteralNode([indexSignature]); - } - - visitReadVarExpr(ast: ReadVarExpr, context: Context): ts.TypeQueryNode { - if (ast.name === null) { - throw new Error(`ReadVarExpr with no variable name in type`); - } - return ts.createTypeQueryNode(ts.createIdentifier(ast.name)); - } - - visitWriteVarExpr(expr: WriteVarExpr, context: Context): never { - throw new Error('Method not implemented.'); - } - - visitWriteKeyExpr(expr: WriteKeyExpr, context: Context): never { - throw new Error('Method not implemented.'); - } - - visitWritePropExpr(expr: WritePropExpr, context: Context): never { - throw new Error('Method not implemented.'); - } - - visitInvokeMethodExpr(ast: InvokeMethodExpr, context: Context): never { - throw new Error('Method not implemented.'); - } - - visitInvokeFunctionExpr(ast: InvokeFunctionExpr, context: Context): never { - throw new Error('Method not implemented.'); - } - - visitInstantiateExpr(ast: InstantiateExpr, context: Context): never { - throw new Error('Method not implemented.'); - } - - visitLiteralExpr(ast: LiteralExpr, context: Context): ts.TypeNode { - if (ast.value === null) { - // TODO(alan-agius4): Remove when we no longer support TS 3.9 - // Use: return ts.createLiteralTypeNode(ts.createNull()) directly. - return ts.versionMajorMinor.charAt(0) === '4' ? - ts.createLiteralTypeNode(ts.createNull() as any) : - ts.createKeywordTypeNode(ts.SyntaxKind.NullKeyword as any); - } else if (ast.value === undefined) { - return ts.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword); - } else if (typeof ast.value === 'boolean') { - return ts.createLiteralTypeNode(ts.createLiteral(ast.value)); - } else if (typeof ast.value === 'number') { - return ts.createLiteralTypeNode(ts.createLiteral(ast.value)); - } else { - return ts.createLiteralTypeNode(ts.createLiteral(ast.value)); - } - } - - visitLocalizedString(ast: LocalizedString, context: Context): never { - throw new Error('Method not implemented.'); - } - - visitExternalExpr(ast: ExternalExpr, context: Context): ts.EntityName|ts.TypeReferenceNode { - if (ast.value.moduleName === null || ast.value.name === null) { - throw new Error(`Import unknown module or symbol`); - } - const {moduleImport, symbol} = - this.imports.generateNamedImport(ast.value.moduleName, ast.value.name); - const symbolIdentifier = ts.createIdentifier(symbol); - - const typeName = moduleImport ? - ts.createQualifiedName(ts.createIdentifier(moduleImport), symbolIdentifier) : - symbolIdentifier; - - const typeArguments = ast.typeParams !== null ? - ast.typeParams.map(type => this.translateType(type, context)) : - undefined; - return ts.createTypeReferenceNode(typeName, typeArguments); - } - - visitConditionalExpr(ast: ConditionalExpr, context: Context) { - throw new Error('Method not implemented.'); - } - - visitNotExpr(ast: NotExpr, context: Context) { - throw new Error('Method not implemented.'); - } - - visitAssertNotNullExpr(ast: AssertNotNull, context: Context) { - throw new Error('Method not implemented.'); - } - - visitCastExpr(ast: CastExpr, context: Context) { - throw new Error('Method not implemented.'); - } - - visitFunctionExpr(ast: FunctionExpr, context: Context) { - throw new Error('Method not implemented.'); - } - - visitUnaryOperatorExpr(ast: UnaryOperatorExpr, context: Context) { - throw new Error('Method not implemented.'); - } - - visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: Context) { - throw new Error('Method not implemented.'); - } - - visitReadPropExpr(ast: ReadPropExpr, context: Context) { - throw new Error('Method not implemented.'); - } - - visitReadKeyExpr(ast: ReadKeyExpr, context: Context) { - throw new Error('Method not implemented.'); - } - - visitLiteralArrayExpr(ast: LiteralArrayExpr, context: Context): ts.TupleTypeNode { - const values = ast.entries.map(expr => this.translateExpression(expr, context)); - return ts.createTupleTypeNode(values); - } - - visitLiteralMapExpr(ast: LiteralMapExpr, context: Context): ts.TypeLiteralNode { - const entries = ast.entries.map(entry => { - const {key, quoted} = entry; - const type = this.translateExpression(entry.value, context); - return ts.createPropertySignature( - /* modifiers */ undefined, - /* name */ quoted ? ts.createStringLiteral(key) : key, - /* questionToken */ undefined, - /* type */ type, - /* initializer */ undefined); - }); - return ts.createTypeLiteralNode(entries); - } - - visitCommaExpr(ast: CommaExpr, context: Context) { - throw new Error('Method not implemented.'); - } - - visitWrappedNodeExpr(ast: WrappedNodeExpr, context: Context): ts.TypeNode { - const node: ts.Node = ast.node; - if (ts.isEntityName(node)) { - return ts.createTypeReferenceNode(node, /* typeArguments */ undefined); - } else if (ts.isTypeNode(node)) { - return node; - } else if (ts.isLiteralExpression(node)) { - return ts.createLiteralTypeNode(node); - } else { - throw new Error( - `Unsupported WrappedNodeExpr in TypeTranslatorVisitor: ${ts.SyntaxKind[node.kind]}`); - } - } - - visitTypeofExpr(ast: TypeofExpr, context: Context): ts.TypeQueryNode { - const typeNode = this.translateExpression(ast.expr, context); - if (!ts.isTypeReferenceNode(typeNode)) { - throw new Error(`The target of a typeof expression must be a type reference, but it was - ${ts.SyntaxKind[typeNode.kind]}`); - } - return ts.createTypeQueryNode(typeNode.typeName); - } - - private translateType(type: Type, context: Context): ts.TypeNode { - const typeNode = type.visitType(this, context); - if (!ts.isTypeNode(typeNode)) { - throw new Error( - `A Type must translate to a TypeNode, but was ${ts.SyntaxKind[typeNode.kind]}`); - } - return typeNode; - } - - private translateExpression(expr: Expression, context: Context): ts.TypeNode { - const typeNode = expr.visitExpression(this, context); - if (!ts.isTypeNode(typeNode)) { - throw new Error( - `An Expression must translate to a TypeNode, but was ${ts.SyntaxKind[typeNode.kind]}`); - } - return typeNode; - } -} - // HACK: Use this in place of `ts.createTemplateMiddle()`. // Revert once https://github.com/microsoft/TypeScript/issues/35374 is fixed function createTemplateMiddle(cooked: string, raw: string): ts.TemplateMiddle { diff --git a/packages/compiler-cli/src/ngtsc/translator/src/type_translator.ts b/packages/compiler-cli/src/ngtsc/translator/src/type_translator.ts new file mode 100644 index 0000000000..6f364d4cf1 --- /dev/null +++ b/packages/compiler-cli/src/ngtsc/translator/src/type_translator.ts @@ -0,0 +1,244 @@ +/** + * @license + * Copyright Google LLC 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 {ArrayType, AssertNotNull, BinaryOperatorExpr, BuiltinType, BuiltinTypeName, CastExpr, CommaExpr, ConditionalExpr, Expression, ExpressionType, ExpressionVisitor, ExternalExpr, FunctionExpr, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, LocalizedString, MapType, NotExpr, ReadKeyExpr, ReadPropExpr, ReadVarExpr, Type, TypeofExpr, TypeVisitor, UnaryOperatorExpr, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler'; +import * as ts from 'typescript'; + +import {Context} from './context'; +import {ImportManager} from './import_manager'; + + +export function translateType(type: Type, imports: ImportManager): ts.TypeNode { + return type.visitType(new TypeTranslatorVisitor(imports), new Context(false)); +} + +export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor { + constructor(private imports: ImportManager) {} + + visitBuiltinType(type: BuiltinType, context: Context): ts.KeywordTypeNode { + switch (type.name) { + case BuiltinTypeName.Bool: + return ts.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword); + case BuiltinTypeName.Dynamic: + return ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword); + case BuiltinTypeName.Int: + case BuiltinTypeName.Number: + return ts.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword); + case BuiltinTypeName.String: + return ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword); + case BuiltinTypeName.None: + return ts.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword); + default: + throw new Error(`Unsupported builtin type: ${BuiltinTypeName[type.name]}`); + } + } + + visitExpressionType(type: ExpressionType, context: Context): ts.TypeNode { + const typeNode = this.translateExpression(type.value, context); + if (type.typeParams === null) { + return typeNode; + } + + if (!ts.isTypeReferenceNode(typeNode)) { + throw new Error( + 'An ExpressionType with type arguments must translate into a TypeReferenceNode'); + } else if (typeNode.typeArguments !== undefined) { + throw new Error( + `An ExpressionType with type arguments cannot have multiple levels of type arguments`); + } + + const typeArgs = type.typeParams.map(param => this.translateType(param, context)); + return ts.createTypeReferenceNode(typeNode.typeName, typeArgs); + } + + visitArrayType(type: ArrayType, context: Context): ts.ArrayTypeNode { + return ts.createArrayTypeNode(this.translateType(type.of, context)); + } + + visitMapType(type: MapType, context: Context): ts.TypeLiteralNode { + const parameter = ts.createParameter( + undefined, undefined, undefined, 'key', undefined, + ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)); + const typeArgs = type.valueType !== null ? + this.translateType(type.valueType, context) : + ts.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword); + const indexSignature = ts.createIndexSignature(undefined, undefined, [parameter], typeArgs); + return ts.createTypeLiteralNode([indexSignature]); + } + + visitReadVarExpr(ast: ReadVarExpr, context: Context): ts.TypeQueryNode { + if (ast.name === null) { + throw new Error(`ReadVarExpr with no variable name in type`); + } + return ts.createTypeQueryNode(ts.createIdentifier(ast.name)); + } + + visitWriteVarExpr(expr: WriteVarExpr, context: Context): never { + throw new Error('Method not implemented.'); + } + + visitWriteKeyExpr(expr: WriteKeyExpr, context: Context): never { + throw new Error('Method not implemented.'); + } + + visitWritePropExpr(expr: WritePropExpr, context: Context): never { + throw new Error('Method not implemented.'); + } + + visitInvokeMethodExpr(ast: InvokeMethodExpr, context: Context): never { + throw new Error('Method not implemented.'); + } + + visitInvokeFunctionExpr(ast: InvokeFunctionExpr, context: Context): never { + throw new Error('Method not implemented.'); + } + + visitInstantiateExpr(ast: InstantiateExpr, context: Context): never { + throw new Error('Method not implemented.'); + } + + visitLiteralExpr(ast: LiteralExpr, context: Context): ts.TypeNode { + if (ast.value === null) { + // TODO(alan-agius4): Remove when we no longer support TS 3.9 + // Use: return ts.createLiteralTypeNode(ts.createNull()) directly. + return ts.versionMajorMinor.charAt(0) === '4' ? + ts.createLiteralTypeNode(ts.createNull() as any) : + ts.createKeywordTypeNode(ts.SyntaxKind.NullKeyword as any); + } else if (ast.value === undefined) { + return ts.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword); + } else if (typeof ast.value === 'boolean') { + return ts.createLiteralTypeNode(ts.createLiteral(ast.value)); + } else if (typeof ast.value === 'number') { + return ts.createLiteralTypeNode(ts.createLiteral(ast.value)); + } else { + return ts.createLiteralTypeNode(ts.createLiteral(ast.value)); + } + } + + visitLocalizedString(ast: LocalizedString, context: Context): never { + throw new Error('Method not implemented.'); + } + + visitExternalExpr(ast: ExternalExpr, context: Context): ts.EntityName|ts.TypeReferenceNode { + if (ast.value.moduleName === null || ast.value.name === null) { + throw new Error(`Import unknown module or symbol`); + } + const {moduleImport, symbol} = + this.imports.generateNamedImport(ast.value.moduleName, ast.value.name); + const symbolIdentifier = ts.createIdentifier(symbol); + + const typeName = moduleImport ? + ts.createQualifiedName(ts.createIdentifier(moduleImport), symbolIdentifier) : + symbolIdentifier; + + const typeArguments = ast.typeParams !== null ? + ast.typeParams.map(type => this.translateType(type, context)) : + undefined; + return ts.createTypeReferenceNode(typeName, typeArguments); + } + + visitConditionalExpr(ast: ConditionalExpr, context: Context) { + throw new Error('Method not implemented.'); + } + + visitNotExpr(ast: NotExpr, context: Context) { + throw new Error('Method not implemented.'); + } + + visitAssertNotNullExpr(ast: AssertNotNull, context: Context) { + throw new Error('Method not implemented.'); + } + + visitCastExpr(ast: CastExpr, context: Context) { + throw new Error('Method not implemented.'); + } + + visitFunctionExpr(ast: FunctionExpr, context: Context) { + throw new Error('Method not implemented.'); + } + + visitUnaryOperatorExpr(ast: UnaryOperatorExpr, context: Context) { + throw new Error('Method not implemented.'); + } + + visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: Context) { + throw new Error('Method not implemented.'); + } + + visitReadPropExpr(ast: ReadPropExpr, context: Context) { + throw new Error('Method not implemented.'); + } + + visitReadKeyExpr(ast: ReadKeyExpr, context: Context) { + throw new Error('Method not implemented.'); + } + + visitLiteralArrayExpr(ast: LiteralArrayExpr, context: Context): ts.TupleTypeNode { + const values = ast.entries.map(expr => this.translateExpression(expr, context)); + return ts.createTupleTypeNode(values); + } + + visitLiteralMapExpr(ast: LiteralMapExpr, context: Context): ts.TypeLiteralNode { + const entries = ast.entries.map(entry => { + const {key, quoted} = entry; + const type = this.translateExpression(entry.value, context); + return ts.createPropertySignature( + /* modifiers */ undefined, + /* name */ quoted ? ts.createStringLiteral(key) : key, + /* questionToken */ undefined, + /* type */ type, + /* initializer */ undefined); + }); + return ts.createTypeLiteralNode(entries); + } + + visitCommaExpr(ast: CommaExpr, context: Context) { + throw new Error('Method not implemented.'); + } + + visitWrappedNodeExpr(ast: WrappedNodeExpr, context: Context): ts.TypeNode { + const node: ts.Node = ast.node; + if (ts.isEntityName(node)) { + return ts.createTypeReferenceNode(node, /* typeArguments */ undefined); + } else if (ts.isTypeNode(node)) { + return node; + } else if (ts.isLiteralExpression(node)) { + return ts.createLiteralTypeNode(node); + } else { + throw new Error( + `Unsupported WrappedNodeExpr in TypeTranslatorVisitor: ${ts.SyntaxKind[node.kind]}`); + } + } + + visitTypeofExpr(ast: TypeofExpr, context: Context): ts.TypeQueryNode { + const typeNode = this.translateExpression(ast.expr, context); + if (!ts.isTypeReferenceNode(typeNode)) { + throw new Error(`The target of a typeof expression must be a type reference, but it was + ${ts.SyntaxKind[typeNode.kind]}`); + } + return ts.createTypeQueryNode(typeNode.typeName); + } + + private translateType(type: Type, context: Context): ts.TypeNode { + const typeNode = type.visitType(this, context); + if (!ts.isTypeNode(typeNode)) { + throw new Error( + `A Type must translate to a TypeNode, but was ${ts.SyntaxKind[typeNode.kind]}`); + } + return typeNode; + } + + private translateExpression(expr: Expression, context: Context): ts.TypeNode { + const typeNode = expr.visitExpression(this, context); + if (!ts.isTypeNode(typeNode)) { + throw new Error( + `An Expression must translate to a TypeNode, but was ${ts.SyntaxKind[typeNode.kind]}`); + } + return typeNode; + } +}