|
| 1 | +/** |
| 2 | + * @license |
| 3 | + * Copyright Google Inc. All Rights Reserved. |
| 4 | + * |
| 5 | + * Use of this source code is governed by an MIT-style license that can be |
| 6 | + * found in the LICENSE file at https://angular.io/license |
| 7 | + */ |
| 8 | + |
| 9 | +import * as ts from 'typescript'; |
| 10 | + |
| 11 | +import {TypeCtorMetadata} from './api'; |
| 12 | + |
| 13 | +/** |
| 14 | + * Generate a type constructor for the given class and metadata. |
| 15 | + * |
| 16 | + * A type constructor is a specially shaped TypeScript static method, intended to be placed within |
| 17 | + * a directive class itself, that permits type inference of any generic type parameters of the class |
| 18 | + * from the types of expressions bound to inputs or outputs, and the types of elements that match |
| 19 | + * queries performed by the directive. It also catches any errors in the types of these expressions. |
| 20 | + * This method is never called at runtime, but is used in type-check blocks to construct directive |
| 21 | + * types. |
| 22 | + * |
| 23 | + * A type constructor for NgFor looks like: |
| 24 | + * |
| 25 | + * static ngTypeCtor<T>(init: Partial<Pick<NgForOf<T>, 'ngForOf'|'ngForTrackBy'|'ngForTemplate'>>): |
| 26 | + * NgForOf<T>; |
| 27 | + * |
| 28 | + * A typical usage would be: |
| 29 | + * |
| 30 | + * NgForOf.ngTypeCtor(init: {ngForOf: ['foo', 'bar']}); // Infers a type of NgForOf<string>. |
| 31 | + * |
| 32 | + * @param node the `ts.ClassDeclaration` for which a type constructor will be generated. |
| 33 | + * @param meta additional metadata required to generate the type constructor. |
| 34 | + * @returns a `ts.MethodDeclaration` for the type constructor. |
| 35 | + */ |
| 36 | +export function generateTypeCtor( |
| 37 | + node: ts.ClassDeclaration, meta: TypeCtorMetadata): ts.MethodDeclaration { |
| 38 | + // Build rawType, a `ts.TypeNode` of the class with its generic parameters passed through from |
| 39 | + // the definition without any type bounds. For example, if the class is |
| 40 | + // `FooDirective<T extends Bar>`, its rawType would be `FooDirective<T>`. |
| 41 | + const rawTypeArgs = node.typeParameters !== undefined ? |
| 42 | + node.typeParameters.map(param => ts.createTypeReferenceNode(param.name, undefined)) : |
| 43 | + undefined; |
| 44 | + const rawType: ts.TypeNode = ts.createTypeReferenceNode(node.name !, rawTypeArgs); |
| 45 | + |
| 46 | + // initType is the type of 'init', the single argument to the type constructor method. |
| 47 | + // If the Directive has any inputs, outputs, or queries, its initType will be: |
| 48 | + // |
| 49 | + // Partial<Pick<rawType, 'inputField'|'outputField'|'queryField'>> |
| 50 | + // |
| 51 | + // Pick here is used to select only those fields from which the generic type parameters of the |
| 52 | + // directive will be inferred. Partial is used because inputs are optional, so there may not be |
| 53 | + // bindings for each field. |
| 54 | + // |
| 55 | + // In the special case there are no inputs/outputs/etc, initType is set to {}. |
| 56 | + let initType: ts.TypeNode; |
| 57 | + |
| 58 | + const keys: string[] = [ |
| 59 | + ...meta.fields.inputs, |
| 60 | + ...meta.fields.outputs, |
| 61 | + ...meta.fields.queries, |
| 62 | + ]; |
| 63 | + if (keys.length === 0) { |
| 64 | + // Special case - no inputs, outputs, or other fields which could influence the result type. |
| 65 | + initType = ts.createTypeLiteralNode([]); |
| 66 | + } else { |
| 67 | + // Construct a union of all the field names. |
| 68 | + const keyTypeUnion = ts.createUnionTypeNode( |
| 69 | + keys.map(key => ts.createLiteralTypeNode(ts.createStringLiteral(key)))); |
| 70 | + |
| 71 | + // Construct the Pick<rawType, keyTypeUnion>. |
| 72 | + const pickType = ts.createTypeReferenceNode('Pick', [rawType, keyTypeUnion]); |
| 73 | + |
| 74 | + // Construct the Partial<pickType>. |
| 75 | + initType = ts.createTypeReferenceNode('Partial', [pickType]); |
| 76 | + } |
| 77 | + |
| 78 | + // If this constructor is being generated into a .ts file, then it needs a fake body. The body |
| 79 | + // is set to a return of `null!`. If the type constructor is being generated into a .d.ts file, |
| 80 | + // it needs no body. |
| 81 | + let body: ts.Block|undefined = undefined; |
| 82 | + if (meta.body) { |
| 83 | + body = ts.createBlock([ |
| 84 | + ts.createReturn(ts.createNonNullExpression(ts.createNull())), |
| 85 | + ]); |
| 86 | + } |
| 87 | + |
| 88 | + // Create the 'init' parameter itself. |
| 89 | + const initParam = ts.createParameter( |
| 90 | + /* decorators */ undefined, |
| 91 | + /* modifiers */ undefined, |
| 92 | + /* dotDotDotToken */ undefined, |
| 93 | + /* name */ 'init', |
| 94 | + /* questionToken */ undefined, |
| 95 | + /* type */ initType, |
| 96 | + /* initializer */ undefined, ); |
| 97 | + |
| 98 | + // Create the type constructor method declaration. |
| 99 | + return ts.createMethod( |
| 100 | + /* decorators */ undefined, |
| 101 | + /* modifiers */[ts.createModifier(ts.SyntaxKind.StaticKeyword)], |
| 102 | + /* asteriskToken */ undefined, |
| 103 | + /* name */ meta.fnName, |
| 104 | + /* questionToken */ undefined, |
| 105 | + /* typeParameters */ node.typeParameters, |
| 106 | + /* parameters */[initParam], |
| 107 | + /* type */ rawType, |
| 108 | + /* body */ body, ); |
| 109 | +} |
0 commit comments