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[] { renderDtsFile(dtsFile: ts.SourceFile, renderInfo: DtsRenderInfo): FileInfo[] {
const input = this.extractSourceMap(dtsFile); const input = this.extractSourceMap(dtsFile);
const outputText = new MagicString(input.source); const outputText = new MagicString(input.source);
const printer = ts.createPrinter();
const importManager = new ImportManager( const importManager = new ImportManager(
this.getImportRewriter(this.bundle.dts !.r3SymbolsFile, false), IMPORT_PREFIX); this.getImportRewriter(this.bundle.dts !.r3SymbolsFile, false), IMPORT_PREFIX);
@ -176,7 +177,8 @@ export abstract class Renderer {
const endOfClass = dtsClass.dtsDeclaration.getEnd(); const endOfClass = dtsClass.dtsDeclaration.getEnd();
dtsClass.compilation.forEach(declaration => { dtsClass.compilation.forEach(declaration => {
const type = translateType(declaration.type, importManager); 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); outputText.appendRight(endOfClass - 1, newStatement);
}); });
}); });

View File

@ -57,8 +57,7 @@ export class DtsFileTransformer {
const decls = this.ivyFields.get(node.name.text) !; const decls = this.ivyFields.get(node.name.text) !;
const newMembers = decls.map(decl => { const newMembers = decls.map(decl => {
const modifiers = [ts.createModifier(ts.SyntaxKind.StaticKeyword)]; const modifiers = [ts.createModifier(ts.SyntaxKind.StaticKeyword)];
const type = translateType(decl.type, this.imports); const typeRef = translateType(decl.type, this.imports);
const typeRef = ts.createTypeReferenceNode(ts.createIdentifier(type), undefined);
return ts.createProperty(undefined, modifiers, decl.name, undefined, typeRef, undefined); 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)); 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)); return type.visitType(new TypeTranslatorVisitor(imports), new Context(false));
} }
@ -294,44 +294,44 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor { export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor {
constructor(private imports: ImportManager) {} constructor(private imports: ImportManager) {}
visitBuiltinType(type: BuiltinType, context: Context): string { visitBuiltinType(type: BuiltinType, context: Context): ts.KeywordTypeNode {
switch (type.name) { switch (type.name) {
case BuiltinTypeName.Bool: case BuiltinTypeName.Bool:
return 'boolean'; return ts.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
case BuiltinTypeName.Dynamic: case BuiltinTypeName.Dynamic:
return 'any'; return ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword);
case BuiltinTypeName.Int: case BuiltinTypeName.Int:
case BuiltinTypeName.Number: case BuiltinTypeName.Number:
return 'number'; return ts.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
case BuiltinTypeName.String: case BuiltinTypeName.String:
return 'string'; return ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
case BuiltinTypeName.None: case BuiltinTypeName.None:
return 'never'; return ts.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword);
default: default:
throw new Error(`Unsupported builtin type: ${BuiltinTypeName[type.name]}`); throw new Error(`Unsupported builtin type: ${BuiltinTypeName[type.name]}`);
} }
} }
visitExpressionType(type: ExpressionType, context: Context): string { visitExpressionType(type: ExpressionType, context: Context): ts.TypeReferenceType {
const exprStr = type.value.visitExpression(this, context); const expr: ts.Identifier|ts.QualifiedName = type.value.visitExpression(this, context);
if (type.typeParams !== null) { const typeArgs = type.typeParams !== null ?
const typeSegments = type.typeParams.map(param => param.visitType(this, context)); type.typeParams.map(param => param.visitType(this, context)) :
return `${exprStr}<${typeSegments.join(', ')}>`; undefined;
} else {
return exprStr; return ts.createTypeReferenceNode(expr, typeArgs);
}
} }
visitArrayType(type: ArrayType, context: Context): string { visitArrayType(type: ArrayType, context: Context): ts.ArrayTypeNode {
return `Array<${type.visitType(this, context)}>`; return ts.createArrayTypeNode(type.visitType(this, context));
} }
visitMapType(type: MapType, context: Context): string { visitMapType(type: MapType, context: Context): ts.TypeLiteralNode {
if (type.valueType !== null) { const parameter = ts.createParameter(
return `{[key: string]: ${type.valueType.visitType(this, context)}}`; undefined, undefined, undefined, 'key', undefined,
} else { ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword));
return '{[key: string]: any}'; 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 { visitReadVarExpr(ast: ReadVarExpr, context: Context): string {
@ -365,28 +365,26 @@ export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
visitLiteralExpr(ast: LiteralExpr, context: Context): string { visitLiteralExpr(ast: LiteralExpr, context: Context): ts.LiteralExpression {
if (typeof ast.value === 'string') { return ts.createLiteral(ast.value as string);
const escaped = ast.value.replace(/\'/g, '\\\'');
return `'${escaped}'`;
} else {
return `${ast.value}`;
}
} }
visitExternalExpr(ast: ExternalExpr, context: Context): string { visitExternalExpr(ast: ExternalExpr, context: Context): ts.TypeNode {
if (ast.value.moduleName === null || ast.value.name === null) { if (ast.value.moduleName === null || ast.value.name === null) {
throw new Error(`Import unknown module or symbol`); throw new Error(`Import unknown module or symbol`);
} }
const {moduleImport, symbol} = const {moduleImport, symbol} =
this.imports.generateNamedImport(ast.value.moduleName, ast.value.name); this.imports.generateNamedImport(ast.value.moduleName, ast.value.name);
const base = moduleImport ? `${moduleImport}.${symbol}` : symbol; const symbolIdentifier = ts.createIdentifier(symbol);
if (ast.typeParams !== null) {
const generics = ast.typeParams.map(type => type.visitType(this, context)).join(', '); const typeName = moduleImport ?
return `${base}<${generics}>`; ts.createPropertyAccess(ts.createIdentifier(moduleImport), symbolIdentifier) :
} else { symbolIdentifier;
return base;
} const typeArguments =
ast.typeParams ? ast.typeParams.map(type => type.visitType(this, context)) : undefined;
return ts.createExpressionWithTypeArguments(typeArguments, typeName);
} }
visitConditionalExpr(ast: ConditionalExpr, context: Context) { visitConditionalExpr(ast: ConditionalExpr, context: Context) {
@ -417,37 +415,43 @@ export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor {
throw new Error('Method not implemented.'); 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)); 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 entries = ast.entries.map(entry => {
const {key, quoted} = entry; const {key, quoted} = entry;
const value = entry.value.visitExpression(this, context); const value = entry.value.visitExpression(this, context);
if (quoted) { return ts.createPropertyAssignment(quoted ? `'${key}'` : key, value);
return `'${key}': ${value}`;
} else {
return `${key}: ${value}`;
}
}); });
return `{${entries.join(', ')}}`; return ts.createObjectLiteral(entries);
} }
visitCommaExpr(ast: CommaExpr, context: Context) { throw new Error('Method not implemented.'); } 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; const node: ts.Node = ast.node;
if (ts.isIdentifier(node)) { if (ts.isIdentifier(node)) {
return node.text; return node;
} else { } else {
throw new Error( throw new Error(
`Unsupported WrappedNodeExpr in TypeTranslatorVisitor: ${ts.SyntaxKind[node.kind]}`); `Unsupported WrappedNodeExpr in TypeTranslatorVisitor: ${ts.SyntaxKind[node.kind]}`);
} }
} }
visitTypeofExpr(ast: TypeofExpr, context: Context): string { visitTypeofExpr(ast: TypeofExpr, context: Context): ts.TypeQueryNode {
return `typeof ${ast.expr.visitExpression(this, context)}`; 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'); const dtsContents = env.getContents('test.d.ts');
expect(dtsContents) expect(dtsContents)
.toContain( .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', () => { it('should compile Components without errors', () => {
@ -292,7 +292,7 @@ describe('ngtsc behavioral tests', () => {
const dtsContents = env.getContents('test.d.ts'); const dtsContents = env.getContents('test.d.ts');
expect(dtsContents) expect(dtsContents)
.toContain( .toContain(
'static ngComponentDef: i0.ɵComponentDefWithMeta<TestCmp, \'test-cmp\', never, {}, {}, never>'); 'static ngComponentDef: i0.ɵComponentDefWithMeta<TestCmp, "test-cmp", never, {}, {}, never>');
expect(dtsContents) expect(dtsContents)
.toContain( .toContain(
'static ngModuleDef: i0.ɵNgModuleDefWithMeta<TestModule, [typeof TestCmp], never, never>'); 'static ngModuleDef: i0.ɵNgModuleDefWithMeta<TestModule, [typeof TestCmp], never, never>');
@ -502,8 +502,7 @@ describe('ngtsc behavioral tests', () => {
.toContain( .toContain(
'TestPipe.ngPipeDef = i0.ɵdefinePipe({ name: "test-pipe", type: TestPipe, ' + 'TestPipe.ngPipeDef = i0.ɵdefinePipe({ name: "test-pipe", type: TestPipe, ' +
'factory: function TestPipe_Factory(t) { return new (t || TestPipe)(); }, pure: false })'); 'factory: function TestPipe_Factory(t) { return new (t || TestPipe)(); }, pure: false })');
expect(dtsContents) expect(dtsContents).toContain('static ngPipeDef: i0.ɵPipeDefWithMeta<TestPipe, "test-pipe">;');
.toContain('static ngPipeDef: i0.ɵPipeDefWithMeta<TestPipe, \'test-pipe\'>;');
}); });
it('should compile pure Pipes without errors', () => { it('should compile pure Pipes without errors', () => {
@ -526,8 +525,7 @@ describe('ngtsc behavioral tests', () => {
.toContain( .toContain(
'TestPipe.ngPipeDef = i0.ɵdefinePipe({ name: "test-pipe", type: TestPipe, ' + 'TestPipe.ngPipeDef = i0.ɵdefinePipe({ name: "test-pipe", type: TestPipe, ' +
'factory: function TestPipe_Factory(t) { return new (t || TestPipe)(); }, pure: true })'); 'factory: function TestPipe_Factory(t) { return new (t || TestPipe)(); }, pure: true })');
expect(dtsContents) expect(dtsContents).toContain('static ngPipeDef: i0.ɵPipeDefWithMeta<TestPipe, "test-pipe">;');
.toContain('static ngPipeDef: i0.ɵPipeDefWithMeta<TestPipe, \'test-pipe\'>;');
}); });
it('should compile Pipes with dependencies', () => { it('should compile Pipes with dependencies', () => {