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
This commit is contained in:
parent
123bff7cb6
commit
15dfd3439a
|
@ -6,4 +6,6 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* 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';
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<string, string>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,23 +6,13 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* 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 {LocalizedString, UnaryOperator, UnaryOperatorExpr} from '@angular/compiler/src/output/output_ast';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {DefaultImportRecorder, ImportRewriter, NOOP_DEFAULT_IMPORT_RECORDER, NoopImportRewriter} from '../../imports';
|
import {DefaultImportRecorder} from '../../imports';
|
||||||
|
import {Context} from './context';
|
||||||
export class Context {
|
import {ImportManager} from './import_manager';
|
||||||
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 UNARY_OPERATORS = new Map<UnaryOperator, ts.PrefixUnaryOperator>([
|
const UNARY_OPERATORS = new Map<UnaryOperator, ts.PrefixUnaryOperator>([
|
||||||
[UnaryOperator.Minus, ts.SyntaxKind.MinusToken],
|
[UnaryOperator.Minus, ts.SyntaxKind.MinusToken],
|
||||||
|
@ -48,65 +38,6 @@ const BINARY_OPERATORS = new Map<BinaryOperator, ts.BinaryOperator>([
|
||||||
[BinaryOperator.Plus, ts.SyntaxKind.PlusToken],
|
[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<string, string>();
|
|
||||||
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(
|
export function translateExpression(
|
||||||
expression: Expression, imports: ImportManager, defaultImportRecorder: DefaultImportRecorder,
|
expression: Expression, imports: ImportManager, defaultImportRecorder: DefaultImportRecorder,
|
||||||
scriptTarget: Exclude<ts.ScriptTarget, ts.ScriptTarget.JSON>): ts.Expression {
|
scriptTarget: Exclude<ts.ScriptTarget, ts.ScriptTarget.JSON>): ts.Expression {
|
||||||
|
@ -123,9 +54,6 @@ export function translateStatement(
|
||||||
new Context(true));
|
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 {
|
class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor {
|
||||||
private externalSourceFiles = new Map<string, ts.SourceMapSource>();
|
private externalSourceFiles = new Map<string, ts.SourceMapSource>();
|
||||||
|
@ -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<any>, 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()`.
|
// HACK: Use this in place of `ts.createTemplateMiddle()`.
|
||||||
// Revert once https://github.com/microsoft/TypeScript/issues/35374 is fixed
|
// Revert once https://github.com/microsoft/TypeScript/issues/35374 is fixed
|
||||||
function createTemplateMiddle(cooked: string, raw: string): ts.TemplateMiddle {
|
function createTemplateMiddle(cooked: string, raw: string): ts.TemplateMiddle {
|
||||||
|
|
|
@ -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<any>, 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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue