|
9 | 9 | import * as o from '@angular/compiler'; |
10 | 10 | import ts from 'typescript'; |
11 | 11 |
|
| 12 | +import {assertSuccessfulReferenceEmit, ImportFlags, Reference, ReferenceEmitter} from '../../imports'; |
| 13 | +import {ReflectionHost} from '../../reflection'; |
| 14 | + |
12 | 15 | import {Context} from './context'; |
13 | 16 | import {ImportManager} from './import_manager'; |
14 | 17 |
|
15 | 18 |
|
16 | | -export function translateType(type: o.Type, imports: ImportManager): ts.TypeNode { |
17 | | - return type.visitType(new TypeTranslatorVisitor(imports), new Context(false)); |
| 19 | +export function translateType( |
| 20 | + type: o.Type, contextFile: ts.SourceFile, reflector: ReflectionHost, |
| 21 | + refEmitter: ReferenceEmitter, imports: ImportManager): ts.TypeNode { |
| 22 | + return type.visitType( |
| 23 | + new TypeTranslatorVisitor(imports, contextFile, reflector, refEmitter), new Context(false)); |
18 | 24 | } |
19 | 25 |
|
20 | | -export class TypeTranslatorVisitor implements o.ExpressionVisitor, o.TypeVisitor { |
21 | | - constructor(private imports: ImportManager) {} |
| 26 | +class TypeTranslatorVisitor implements o.ExpressionVisitor, o.TypeVisitor { |
| 27 | + constructor( |
| 28 | + private imports: ImportManager, private contextFile: ts.SourceFile, |
| 29 | + private reflector: ReflectionHost, private refEmitter: ReferenceEmitter) {} |
22 | 30 |
|
23 | 31 | visitBuiltinType(type: o.BuiltinType, context: Context): ts.KeywordTypeNode { |
24 | 32 | switch (type.name) { |
@@ -71,6 +79,14 @@ export class TypeTranslatorVisitor implements o.ExpressionVisitor, o.TypeVisitor |
71 | 79 | return ts.factory.createTypeLiteralNode([indexSignature]); |
72 | 80 | } |
73 | 81 |
|
| 82 | + visitTransplantedType(ast: o.TransplantedType<ts.Node>, context: any) { |
| 83 | + if (!ts.isTypeNode(ast.type)) { |
| 84 | + throw new Error(`A TransplantedType must wrap a TypeNode`); |
| 85 | + } |
| 86 | + |
| 87 | + return this.translateTransplantedTypeNode(ast.type, context); |
| 88 | + } |
| 89 | + |
74 | 90 | visitReadVarExpr(ast: o.ReadVarExpr, context: Context): ts.TypeQueryNode { |
75 | 91 | if (ast.name === null) { |
76 | 92 | throw new Error(`ReadVarExpr with no variable name in type`); |
@@ -228,4 +244,71 @@ export class TypeTranslatorVisitor implements o.ExpressionVisitor, o.TypeVisitor |
228 | 244 | } |
229 | 245 | return typeNode; |
230 | 246 | } |
| 247 | + |
| 248 | + /** |
| 249 | + * Translates a type reference node so that all of its references |
| 250 | + * are imported into the context file. |
| 251 | + */ |
| 252 | + private translateTransplantedTypeReferenceNode( |
| 253 | + node: ts.TypeReferenceNode&{typeName: ts.Identifier}, context: any): ts.TypeReferenceNode { |
| 254 | + const declaration = this.reflector.getDeclarationOfIdentifier(node.typeName); |
| 255 | + |
| 256 | + if (declaration === null) { |
| 257 | + throw new Error( |
| 258 | + `Unable to statically determine the declaration file of type node ${node.typeName.text}`); |
| 259 | + } |
| 260 | + |
| 261 | + const emittedType = this.refEmitter.emit( |
| 262 | + new Reference(declaration.node), this.contextFile, |
| 263 | + ImportFlags.NoAliasing | ImportFlags.AllowTypeImports | |
| 264 | + ImportFlags.AllowRelativeDtsImports); |
| 265 | + |
| 266 | + assertSuccessfulReferenceEmit(emittedType, node, 'type'); |
| 267 | + |
| 268 | + const result = emittedType.expression.visitExpression(this, context); |
| 269 | + |
| 270 | + if (!ts.isTypeReferenceNode(result)) { |
| 271 | + throw new Error(`Expected TypeReferenceNode when referencing the type for ${ |
| 272 | + node.typeName.text}, but received ${ts.SyntaxKind[result.kind]}`); |
| 273 | + } |
| 274 | + |
| 275 | + // If the original node doesn't have any generic parameters we return the results. |
| 276 | + if (node.typeArguments === undefined || node.typeArguments.length === 0) { |
| 277 | + return result; |
| 278 | + } |
| 279 | + |
| 280 | + // If there are any generics, we have to reflect them as well. |
| 281 | + const translatedArgs = |
| 282 | + node.typeArguments.map(arg => this.translateTransplantedTypeNode(arg, context)); |
| 283 | + |
| 284 | + return ts.factory.updateTypeReferenceNode( |
| 285 | + result, result.typeName, ts.factory.createNodeArray(translatedArgs)); |
| 286 | + } |
| 287 | + |
| 288 | + /** |
| 289 | + * Translates a type node so that all of the type references it |
| 290 | + * contains are imported and can be referenced in the context file. |
| 291 | + */ |
| 292 | + private translateTransplantedTypeNode(rootNode: ts.TypeNode, context: any): ts.TypeNode { |
| 293 | + const factory: ts.TransformerFactory<ts.Node> = transformContext => root => { |
| 294 | + const walk = (node: ts.Node): ts.Node => { |
| 295 | + if (ts.isTypeReferenceNode(node) && ts.isIdentifier(node.typeName)) { |
| 296 | + const translated = |
| 297 | + this.translateTransplantedTypeReferenceNode(node as ts.TypeReferenceNode & { |
| 298 | + typeName: ts.Identifier; |
| 299 | + }, context); |
| 300 | + |
| 301 | + if (translated !== node) { |
| 302 | + return translated; |
| 303 | + } |
| 304 | + } |
| 305 | + |
| 306 | + return ts.visitEachChild(node, walk, transformContext); |
| 307 | + }; |
| 308 | + |
| 309 | + return ts.visitNode(root, walk); |
| 310 | + }; |
| 311 | + |
| 312 | + return ts.transform(rootNode, [factory]).transformed[0] as ts.TypeNode; |
| 313 | + } |
231 | 314 | } |
0 commit comments