refactor(compiler-cli): return TS nodes from TypeTranslatorVisitor (#28342)

The TypeTranslatorVisitor visitor returned strings because before it wasn't possible to transform declaration files directly through the TypeScript custom transformer API.

Now that's possible though, so it should return nodes instead.

PR Close #28342
This commit is contained in:
Filipe Silva 2019-01-25 12:12:57 +00:00 committed by Jason Aden
parent d45d3a3ef9
commit bcf17bc91c
4 changed files with 63 additions and 60 deletions

View File

@ -169,6 +169,7 @@ export abstract class Renderer {
renderDtsFile(dtsFile: ts.SourceFile, renderInfo: DtsRenderInfo): FileInfo[] {
const input = this.extractSourceMap(dtsFile);
const outputText = new MagicString(input.source);
const printer = ts.createPrinter();
const importManager = new ImportManager(
this.getImportRewriter(this.bundle.dts !.r3SymbolsFile, false), IMPORT_PREFIX);
@ -176,7 +177,8 @@ export abstract class Renderer {
const endOfClass = dtsClass.dtsDeclaration.getEnd();
dtsClass.compilation.forEach(declaration => {
const type = translateType(declaration.type, importManager);
const newStatement = ` static ${declaration.name}: ${type};\n`;
const typeStr = printer.printNode(ts.EmitHint.Unspecified, type, dtsFile);
const newStatement = ` static ${declaration.name}: ${typeStr};\n`;
outputText.appendRight(endOfClass - 1, newStatement);
});
});

View File

@ -57,8 +57,7 @@ export class DtsFileTransformer {
const decls = this.ivyFields.get(node.name.text) !;
const newMembers = decls.map(decl => {
const modifiers = [ts.createModifier(ts.SyntaxKind.StaticKeyword)];
const type = translateType(decl.type, this.imports);
const typeRef = ts.createTypeReferenceNode(ts.createIdentifier(type), undefined);
const typeRef = translateType(decl.type, this.imports);
return ts.createProperty(undefined, modifiers, decl.name, undefined, typeRef, undefined);
});

View File

@ -86,7 +86,7 @@ export function translateStatement(statement: Statement, imports: ImportManager)
return statement.visitStatement(new ExpressionTranslatorVisitor(imports), new Context(true));
}
export function translateType(type: Type, imports: ImportManager): string {
export function translateType(type: Type, imports: ImportManager): ts.TypeNode {
return type.visitType(new TypeTranslatorVisitor(imports), new Context(false));
}
@ -294,44 +294,44 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor {
constructor(private imports: ImportManager) {}
visitBuiltinType(type: BuiltinType, context: Context): string {
visitBuiltinType(type: BuiltinType, context: Context): ts.KeywordTypeNode {
switch (type.name) {
case BuiltinTypeName.Bool:
return 'boolean';
return ts.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
case BuiltinTypeName.Dynamic:
return 'any';
return ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword);
case BuiltinTypeName.Int:
case BuiltinTypeName.Number:
return 'number';
return ts.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
case BuiltinTypeName.String:
return 'string';
return ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
case BuiltinTypeName.None:
return 'never';
return ts.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword);
default:
throw new Error(`Unsupported builtin type: ${BuiltinTypeName[type.name]}`);
}
}
visitExpressionType(type: ExpressionType, context: Context): string {
const exprStr = type.value.visitExpression(this, context);
if (type.typeParams !== null) {
const typeSegments = type.typeParams.map(param => param.visitType(this, context));
return `${exprStr}<${typeSegments.join(', ')}>`;
} else {
return exprStr;
}
visitExpressionType(type: ExpressionType, context: Context): ts.TypeReferenceType {
const expr: ts.Identifier|ts.QualifiedName = type.value.visitExpression(this, context);
const typeArgs = type.typeParams !== null ?
type.typeParams.map(param => param.visitType(this, context)) :
undefined;
return ts.createTypeReferenceNode(expr, typeArgs);
}
visitArrayType(type: ArrayType, context: Context): string {
return `Array<${type.visitType(this, context)}>`;
visitArrayType(type: ArrayType, context: Context): ts.ArrayTypeNode {
return ts.createArrayTypeNode(type.visitType(this, context));
}
visitMapType(type: MapType, context: Context): string {
if (type.valueType !== null) {
return `{[key: string]: ${type.valueType.visitType(this, context)}}`;
} else {
return '{[key: string]: any}';
}
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 ? type.valueType.visitType(this, context) : undefined;
const indexSignature = ts.createIndexSignature(undefined, undefined, [parameter], typeArgs);
return ts.createTypeLiteralNode([indexSignature]);
}
visitReadVarExpr(ast: ReadVarExpr, context: Context): string {
@ -365,28 +365,26 @@ export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor {
throw new Error('Method not implemented.');
}
visitLiteralExpr(ast: LiteralExpr, context: Context): string {
if (typeof ast.value === 'string') {
const escaped = ast.value.replace(/\'/g, '\\\'');
return `'${escaped}'`;
} else {
return `${ast.value}`;
}
visitLiteralExpr(ast: LiteralExpr, context: Context): ts.LiteralExpression {
return ts.createLiteral(ast.value as string);
}
visitExternalExpr(ast: ExternalExpr, context: Context): string {
visitExternalExpr(ast: ExternalExpr, context: Context): ts.TypeNode {
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 base = moduleImport ? `${moduleImport}.${symbol}` : symbol;
if (ast.typeParams !== null) {
const generics = ast.typeParams.map(type => type.visitType(this, context)).join(', ');
return `${base}<${generics}>`;
} else {
return base;
}
const symbolIdentifier = ts.createIdentifier(symbol);
const typeName = moduleImport ?
ts.createPropertyAccess(ts.createIdentifier(moduleImport), symbolIdentifier) :
symbolIdentifier;
const typeArguments =
ast.typeParams ? ast.typeParams.map(type => type.visitType(this, context)) : undefined;
return ts.createExpressionWithTypeArguments(typeArguments, typeName);
}
visitConditionalExpr(ast: ConditionalExpr, context: Context) {
@ -417,37 +415,43 @@ export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor {
throw new Error('Method not implemented.');
}
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: Context): string {
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: Context): ts.TupleTypeNode {
const values = ast.entries.map(expr => expr.visitExpression(this, context));
return `[${values.join(', ')}]`;
return ts.createTupleTypeNode(values);
}
visitLiteralMapExpr(ast: LiteralMapExpr, context: Context) {
visitLiteralMapExpr(ast: LiteralMapExpr, context: Context): ts.ObjectLiteralExpression {
const entries = ast.entries.map(entry => {
const {key, quoted} = entry;
const value = entry.value.visitExpression(this, context);
if (quoted) {
return `'${key}': ${value}`;
} else {
return `${key}: ${value}`;
}
return ts.createPropertyAssignment(quoted ? `'${key}'` : key, value);
});
return `{${entries.join(', ')}}`;
return ts.createObjectLiteral(entries);
}
visitCommaExpr(ast: CommaExpr, context: Context) { throw new Error('Method not implemented.'); }
visitWrappedNodeExpr(ast: WrappedNodeExpr<any>, context: Context) {
visitWrappedNodeExpr(ast: WrappedNodeExpr<any>, context: Context): ts.Identifier {
const node: ts.Node = ast.node;
if (ts.isIdentifier(node)) {
return node.text;
return node;
} else {
throw new Error(
`Unsupported WrappedNodeExpr in TypeTranslatorVisitor: ${ts.SyntaxKind[node.kind]}`);
}
}
visitTypeofExpr(ast: TypeofExpr, context: Context): string {
return `typeof ${ast.expr.visitExpression(this, context)}`;
visitTypeofExpr(ast: TypeofExpr, context: Context): ts.TypeQueryNode {
let expr = translateExpression(ast.expr, this.imports);
return ts.createTypeQueryNode(expr as ts.Identifier);
}
}
function entityNameToExpr(entity: ts.EntityName): ts.Expression {
if (ts.isIdentifier(entity)) {
return entity;
}
const {left, right} = entity;
const leftExpr = ts.isIdentifier(left) ? left : entityNameToExpr(left);
return ts.createPropertyAccess(leftExpr, right);
}

View File

@ -181,7 +181,7 @@ describe('ngtsc behavioral tests', () => {
const dtsContents = env.getContents('test.d.ts');
expect(dtsContents)
.toContain(
'static ngComponentDef: i0.ɵComponentDefWithMeta<TestCmp, \'test-cmp\', never, {}, {}, never>');
'static ngComponentDef: i0.ɵComponentDefWithMeta<TestCmp, "test-cmp", never, {}, {}, never>');
});
it('should compile Components without errors', () => {
@ -292,7 +292,7 @@ describe('ngtsc behavioral tests', () => {
const dtsContents = env.getContents('test.d.ts');
expect(dtsContents)
.toContain(
'static ngComponentDef: i0.ɵComponentDefWithMeta<TestCmp, \'test-cmp\', never, {}, {}, never>');
'static ngComponentDef: i0.ɵComponentDefWithMeta<TestCmp, "test-cmp", never, {}, {}, never>');
expect(dtsContents)
.toContain(
'static ngModuleDef: i0.ɵNgModuleDefWithMeta<TestModule, [typeof TestCmp], never, never>');
@ -502,8 +502,7 @@ describe('ngtsc behavioral tests', () => {
.toContain(
'TestPipe.ngPipeDef = i0.ɵdefinePipe({ name: "test-pipe", type: TestPipe, ' +
'factory: function TestPipe_Factory(t) { return new (t || TestPipe)(); }, pure: false })');
expect(dtsContents)
.toContain('static ngPipeDef: i0.ɵPipeDefWithMeta<TestPipe, \'test-pipe\'>;');
expect(dtsContents).toContain('static ngPipeDef: i0.ɵPipeDefWithMeta<TestPipe, "test-pipe">;');
});
it('should compile pure Pipes without errors', () => {
@ -526,8 +525,7 @@ describe('ngtsc behavioral tests', () => {
.toContain(
'TestPipe.ngPipeDef = i0.ɵdefinePipe({ name: "test-pipe", type: TestPipe, ' +
'factory: function TestPipe_Factory(t) { return new (t || TestPipe)(); }, pure: true })');
expect(dtsContents)
.toContain('static ngPipeDef: i0.ɵPipeDefWithMeta<TestPipe, \'test-pipe\'>;');
expect(dtsContents).toContain('static ngPipeDef: i0.ɵPipeDefWithMeta<TestPipe, "test-pipe">;');
});
it('should compile Pipes with dependencies', () => {