fork of hey-api/openapi-ts because I need some additional things

Merge pull request #2999 from hey-api/refactor/dsl-type-nodes-8

refactor: arktype plugin use dsl

authored by

Lubos and committed by
GitHub
ca9a032b 381803f8

+247 -266
+3 -3
dev/openapi-ts.config.ts
··· 357 357 }, 358 358 { 359 359 name: 'arktype', 360 - // types: { 361 - // infer: true, 362 - // }, 360 + types: { 361 + infer: true, 362 + }, 363 363 }, 364 364 { 365 365 // case: 'SCREAMING_SNAKE_CASE',
+73 -75
packages/openapi-ts/src/plugins/arktype/constants.ts
··· 1 - import { tsc } from '~/tsc'; 2 - 3 1 export const identifiers = { 4 2 keywords: { 5 - false: tsc.identifier({ text: 'false' }), 6 - true: tsc.identifier({ text: 'true' }), 3 + false: 'false', 4 + true: 'true', 7 5 }, 8 6 /** 9 7 * {@link https://arktype.io/docs/primitives#number Number} 10 8 */ 11 9 number: { 12 - Infinity: tsc.identifier({ text: 'Infinity' }), 13 - NaN: tsc.identifier({ text: 'NaN' }), 14 - NegativeInfinity: tsc.identifier({ text: 'NegativeInfinity' }), 15 - epoch: tsc.identifier({ text: 'epoch' }), 16 - integer: tsc.identifier({ text: 'integer' }), 17 - safe: tsc.identifier({ text: 'safe' }), 10 + Infinity: 'Infinity', 11 + NaN: 'NaN', 12 + NegativeInfinity: 'NegativeInfinity', 13 + epoch: 'epoch', 14 + integer: 'integer', 15 + safe: 'safe', 18 16 }, 19 17 /** 20 18 * {@link https://arktype.io/docs/primitives Primitives} 21 19 */ 22 20 primitives: { 23 - bigint: tsc.identifier({ text: 'bigint' }), 24 - boolean: tsc.identifier({ text: 'boolean' }), 25 - keywords: tsc.identifier({ text: 'keywords' }), 21 + bigint: 'bigint', 22 + boolean: 'boolean', 23 + keywords: 'keywords', 26 24 null: 'null', 27 - number: tsc.identifier({ text: 'number' }), 25 + number: 'number', 28 26 string: 'string', 29 - symbol: tsc.identifier({ text: 'symbol' }), 30 - undefined: tsc.identifier({ text: 'undefined' }), 31 - unit: tsc.identifier({ text: 'unit' }), 27 + symbol: 'symbol', 28 + undefined: 'undefined', 29 + unit: 'unit', 32 30 }, 33 31 /** 34 32 * {@link https://arktype.io/docs/primitives#string String} 35 33 */ 36 34 string: { 37 - NFC: tsc.identifier({ text: 'NFC' }), 38 - NFD: tsc.identifier({ text: 'NFD' }), 39 - NFKC: tsc.identifier({ text: 'NFKC' }), 40 - NFKD: tsc.identifier({ text: 'NFKD' }), 41 - alpha: tsc.identifier({ text: 'alpha' }), 42 - alphanumeric: tsc.identifier({ text: 'alphanumeric' }), 43 - base64: tsc.identifier({ text: 'base64' }), 44 - capitalize: tsc.identifier({ text: 'capitalize' }), 45 - creditCard: tsc.identifier({ text: 'creditCard' }), 35 + NFC: 'NFC', 36 + NFD: 'NFD', 37 + NFKC: 'NFKC', 38 + NFKD: 'NFKD', 39 + alpha: 'alpha', 40 + alphanumeric: 'alphanumeric', 41 + base64: 'base64', 42 + capitalize: 'capitalize', 43 + creditCard: 'creditCard', 46 44 date: 'date', 47 - digits: tsc.identifier({ text: 'digits' }), 45 + digits: 'digits', 48 46 email: 'email', 49 - epoch: tsc.identifier({ text: 'epoch' }), 50 - hex: tsc.identifier({ text: 'hex' }), 51 - integer: tsc.identifier({ text: 'integer' }), 47 + epoch: 'epoch', 48 + hex: 'hex', 49 + integer: 'integer', 52 50 ip: 'ip', 53 51 iso: 'iso', 54 - json: tsc.identifier({ text: 'json' }), 55 - lower: tsc.identifier({ text: 'lower' }), 56 - normalize: tsc.identifier({ text: 'normalize' }), 57 - numeric: tsc.identifier({ text: 'numeric' }), 58 - parse: tsc.identifier({ text: 'parse' }), 59 - preformatted: tsc.identifier({ text: 'preformatted' }), 60 - regex: tsc.identifier({ text: 'regex' }), 61 - semver: tsc.identifier({ text: 'semver' }), 62 - trim: tsc.identifier({ text: 'trim' }), 63 - upper: tsc.identifier({ text: 'upper' }), 52 + json: 'json', 53 + lower: 'lower', 54 + normalize: 'normalize', 55 + numeric: 'numeric', 56 + parse: 'parse', 57 + preformatted: 'preformatted', 58 + regex: 'regex', 59 + semver: 'semver', 60 + trim: 'trim', 61 + upper: 'upper', 64 62 url: 'url', 65 63 uuid: 'uuid', 66 64 v1: 'v1', ··· 76 74 * {@link https://arktype.io/docs/type-api Type API} 77 75 */ 78 76 type: { 79 - $: tsc.identifier({ text: '$' }), 80 - allows: tsc.identifier({ text: 'allows' }), 81 - and: tsc.identifier({ text: 'and' }), 82 - array: tsc.identifier({ text: 'array' }), 83 - as: tsc.identifier({ text: 'as' }), 84 - assert: tsc.identifier({ text: 'assert' }), 85 - brand: tsc.identifier({ text: 'brand' }), 86 - configure: tsc.identifier({ text: 'configure' }), 87 - default: tsc.identifier({ text: 'default' }), 88 - describe: tsc.identifier({ text: 'describe' }), 89 - description: tsc.identifier({ text: 'description' }), 90 - equals: tsc.identifier({ text: 'equals' }), 91 - exclude: tsc.identifier({ text: 'exclude' }), 92 - expression: tsc.identifier({ text: 'expression' }), 93 - extends: tsc.identifier({ text: 'extends' }), 94 - extract: tsc.identifier({ text: 'extract' }), 95 - filter: tsc.identifier({ text: 'filter' }), 96 - from: tsc.identifier({ text: 'from' }), 97 - ifEquals: tsc.identifier({ text: 'ifEquals' }), 98 - ifExtends: tsc.identifier({ text: 'ifExtends' }), 99 - infer: tsc.identifier({ text: 'infer' }), 100 - inferIn: tsc.identifier({ text: 'inferIn' }), 101 - intersect: tsc.identifier({ text: 'intersect' }), 102 - json: tsc.identifier({ text: 'json' }), 103 - meta: tsc.identifier({ text: 'meta' }), 104 - narrow: tsc.identifier({ text: 'narrow' }), 105 - onDeepUndeclaredKey: tsc.identifier({ text: 'onDeepUndeclaredKey' }), 106 - onUndeclaredKey: tsc.identifier({ text: 'onUndeclaredKey' }), 107 - optional: tsc.identifier({ text: 'optional' }), 108 - or: tsc.identifier({ text: 'or' }), 109 - overlaps: tsc.identifier({ text: 'overlaps' }), 110 - pipe: tsc.identifier({ text: 'pipe' }), 111 - select: tsc.identifier({ text: 'select' }), 112 - to: tsc.identifier({ text: 'to' }), 113 - toJsonSchema: tsc.identifier({ text: 'toJsonSchema' }), 77 + $: '$', 78 + allows: 'allows', 79 + and: 'and', 80 + array: 'array', 81 + as: 'as', 82 + assert: 'assert', 83 + brand: 'brand', 84 + configure: 'configure', 85 + default: 'default', 86 + describe: 'describe', 87 + description: 'description', 88 + equals: 'equals', 89 + exclude: 'exclude', 90 + expression: 'expression', 91 + extends: 'extends', 92 + extract: 'extract', 93 + filter: 'filter', 94 + from: 'from', 95 + ifEquals: 'ifEquals', 96 + ifExtends: 'ifExtends', 97 + infer: 'infer', 98 + inferIn: 'inferIn', 99 + intersect: 'intersect', 100 + json: 'json', 101 + meta: 'meta', 102 + narrow: 'narrow', 103 + onDeepUndeclaredKey: 'onDeepUndeclaredKey', 104 + onUndeclaredKey: 'onUndeclaredKey', 105 + optional: 'optional', 106 + or: 'or', 107 + overlaps: 'overlaps', 108 + pipe: 'pipe', 109 + select: 'select', 110 + to: 'to', 111 + toJsonSchema: 'toJsonSchema', 114 112 }, 115 113 };
+23 -31
packages/openapi-ts/src/plugins/arktype/shared/export.ts
··· 1 1 import type { Symbol } from '@hey-api/codegen-core'; 2 - import ts from 'typescript'; 3 2 4 3 import type { IR } from '~/ir/types'; 5 4 import { createSchemaComment } from '~/plugins/shared/utils/schema'; 6 - import { tsc } from '~/tsc'; 5 + import { $ } from '~/ts-dsl'; 7 6 8 7 import { identifiers } from '../constants'; 9 8 import type { ArktypePlugin } from '../types'; ··· 27 26 resource: 'arktype.type', 28 27 }); 29 28 30 - const statement = tsc.constVariable({ 31 - comment: plugin.config.comments 32 - ? createSchemaComment({ schema }) 33 - : undefined, 34 - exportConst: symbol.exported, 35 - expression: tsc.callExpression({ 36 - functionName: type.placeholder, 37 - parameters: [ 38 - ast.def ? tsc.stringLiteral({ text: ast.def }) : ast.expression, 39 - ], 40 - }), 41 - name: symbol.placeholder, 42 - // typeName: ast.typeName 43 - // ? (tsc.propertyAccessExpression({ 44 - // expression: z.placeholder, 45 - // name: ast.typeName, 46 - // }) as unknown as ts.TypeNode) 47 - // : undefined, 48 - }); 29 + const statement = $.const(symbol.placeholder) 30 + .export(symbol.exported) 31 + .$if(plugin.config.comments && createSchemaComment({ schema }), (c, v) => 32 + c.doc(v as ReadonlyArray<string>), 33 + ) 34 + // .type( 35 + // ast.typeName 36 + // ? (tsc.propertyAccessExpression({ 37 + // expression: z.placeholder, 38 + // name: ast.typeName, 39 + // }) as unknown as ts.TypeNode) 40 + // : undefined, 41 + // ) 42 + .assign( 43 + $(type.placeholder).call(ast.def ? $.literal(ast.def) : ast.expression), 44 + ); 49 45 plugin.setSymbolValue(symbol, statement); 50 46 51 47 if (typeInferSymbol) { 52 - const inferType = tsc.typeAliasDeclaration({ 53 - exportType: typeInferSymbol.exported, 54 - name: typeInferSymbol.placeholder, 55 - type: ts.factory.createTypeQueryNode( 56 - ts.factory.createQualifiedName( 57 - ts.factory.createIdentifier(symbol.placeholder), 58 - identifiers.type.infer, 59 - ), 60 - ), 61 - }); 48 + const inferType = $.type 49 + .alias(typeInferSymbol.placeholder) 50 + .export(typeInferSymbol.exported) 51 + .type( 52 + $.type(symbol.placeholder).attr(identifiers.type.infer).typeofType(), 53 + ); 62 54 plugin.setSymbolValue(typeInferSymbol, inferType); 63 55 } 64 56 };
+2 -1
packages/openapi-ts/src/plugins/arktype/shared/types.d.ts
··· 3 3 4 4 import type { IR } from '~/ir/types'; 5 5 import type { ToRefs } from '~/plugins'; 6 + import type { $ } from '~/ts-dsl'; 6 7 7 8 import type { ArktypePlugin } from '../types'; 8 9 9 10 export type Ast = { 10 11 def: string; 11 - expression: ts.Expression; 12 + expression: ReturnType<typeof $.call | typeof $.expr | typeof $.object>; 12 13 hasLazyExpression?: boolean; 13 14 typeName?: string | ts.Identifier; 14 15 };
+7 -20
packages/openapi-ts/src/plugins/arktype/v2/plugin.ts
··· 5 5 import { buildName } from '~/openApi/shared/utils/name'; 6 6 import type { SchemaWithType } from '~/plugins/shared/types/schema'; 7 7 import { toRefs } from '~/plugins/shared/utils/refs'; 8 - import { tsc } from '~/tsc'; 8 + import { $ } from '~/ts-dsl'; 9 9 import { pathToJsonPointer, refToName } from '~/utils/ref'; 10 10 11 11 import { exportAst } from '../shared/export'; ··· 43 43 }; 44 44 const refSymbol = plugin.referenceSymbol(query); 45 45 if (plugin.isSymbolRegistered(query)) { 46 - const ref = tsc.identifier({ text: refSymbol.placeholder }); 46 + const ref = $(refSymbol.placeholder); 47 47 ast.expression = ref; 48 48 } else { 49 - const lazyExpression = tsc.callExpression({ 50 - functionName: tsc.propertyAccessExpression({ 51 - // expression: z.placeholder, 52 - expression: 'TODO', 53 - name: 'TODO', 54 - // name: identifiers.lazy, 55 - }), 56 - parameters: [ 57 - tsc.arrowFunction({ 58 - returnType: tsc.keywordTypeNode({ keyword: 'any' }), 59 - statements: [ 60 - tsc.returnStatement({ 61 - expression: tsc.identifier({ text: refSymbol.placeholder }), 62 - }), 63 - ], 64 - }), 65 - ], 66 - }); 49 + // expression: z.placeholder, 50 + // name: identifiers.lazy, 51 + const lazyExpression = $('TODO') 52 + .attr('TODO') 53 + .call($.func().returns('any').do($.return(refSymbol.placeholder))); 67 54 ast.expression = lazyExpression; 68 55 ast.hasLazyExpression = true; 69 56 state.hasLazyExpression.value = true;
+6 -26
packages/openapi-ts/src/plugins/arktype/v2/toAst/index.ts
··· 1 - import ts from 'typescript'; 2 - 3 1 import type { SchemaWithType } from '~/plugins/shared/types/schema'; 2 + import { $ } from '~/ts-dsl'; 4 3 5 4 import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; 6 5 import { nullToAst } from './null'; ··· 91 90 resource: 'arktype.type', 92 91 }); 93 92 94 - const expression = ts.factory.createCallExpression( 95 - ts.factory.createIdentifier(type.placeholder), 96 - undefined, 97 - [ 98 - ts.factory.createObjectLiteralExpression( 99 - [ 100 - ts.factory.createPropertyAssignment( 101 - 'name', 102 - ts.factory.createStringLiteral('string'), 103 - ), 104 - ts.factory.createPropertyAssignment( 105 - 'platform', 106 - ts.factory.createStringLiteral("'android' | 'ios'"), 107 - ), 108 - ts.factory.createPropertyAssignment( 109 - ts.factory.createComputedPropertyName( 110 - ts.factory.createStringLiteral('versions?'), 111 - ), 112 - ts.factory.createStringLiteral('(number | string)[]'), 113 - ), 114 - ], 115 - true, 116 - ), 117 - ], 93 + const expression = $(type.placeholder).call( 94 + $.object() 95 + .prop('name', $.literal('string')) 96 + .prop('platform', $.literal("'android' | 'ios'")) 97 + .computed('versions?', $.literal('(number | string)[]')), 118 98 ); 119 99 120 100 return {
+13 -64
packages/openapi-ts/src/plugins/arktype/v2/toAst/object.ts
··· 1 - import ts from 'typescript'; 2 - 3 1 import type { SchemaWithType } from '~/plugins/shared/types/schema'; 4 2 import { toRef } from '~/plugins/shared/utils/refs'; 5 - import { tsc } from '~/tsc'; 6 - import { numberRegExp } from '~/utils/regexp'; 3 + import { $ } from '~/ts-dsl'; 7 4 8 5 // import { identifiers } from '../../constants'; 9 6 import type { Ast, IrSchemaToAstOptions } from '../../shared/types'; ··· 19 16 const result: Partial<Omit<Ast, 'typeName'>> = {}; 20 17 21 18 // TODO: parser - handle constants 22 - const properties: Array<ts.PropertyAssignment | ts.GetAccessorDeclaration> = 23 - []; 19 + 20 + const shape = $.object().pretty(); 24 21 25 22 const required = schema.required ?? []; 26 23 ··· 41 38 result.hasLazyExpression = true; 42 39 } 43 40 44 - let propertyName: 45 - | ts.ComputedPropertyName 46 - | ts.StringLiteral 47 - | ts.NumericLiteral 48 - | string = isRequired ? name : `${name}?`; 49 - 50 41 // if (propertyAst.hasCircularReference) { 51 42 // properties.push( 52 43 // tsc.getAccessorDeclaration({ ··· 76 67 // ); 77 68 // } 78 69 79 - if (propertyName.endsWith('?')) { 80 - propertyName = ts.factory.createComputedPropertyName( 81 - tsc.stringLiteral({ text: propertyName }), 82 - ); 70 + if (isRequired) { 71 + shape.prop(name, propertyAst.expression); 83 72 } else { 84 - // TODO: parser - abstract safe property name logic 85 - if ( 86 - ((propertyName.match(/^[0-9]/) && propertyName.match(/\D+/g)) || 87 - propertyName.match(/\W/g)) && 88 - !propertyName.startsWith("'") && 89 - !propertyName.endsWith("'") 90 - ) { 91 - propertyName = `'${propertyName}'`; 92 - } 93 - 94 - numberRegExp.lastIndex = 0; 95 - if (numberRegExp.test(propertyName)) { 96 - // For numeric literals, we'll handle negative numbers by using a string literal 97 - // instead of trying to use a PrefixUnaryExpression 98 - propertyName = propertyName.startsWith('-') 99 - ? tsc.stringLiteral({ text: name }) 100 - : ts.factory.createNumericLiteral(name); 101 - } else { 102 - propertyName = name; 103 - } 73 + shape.computed(`${name}?`, propertyAst.expression); 104 74 } 105 - properties.push( 106 - tsc.propertyAssignment({ 107 - initializer: propertyAst.expression, 108 - name: propertyName, 109 - }), 110 - ); 111 75 } 112 76 113 77 if ( ··· 122 86 path: toRef([...state.path.value, 'additionalProperties']), 123 87 }, 124 88 }); 125 - result.expression = tsc.callExpression({ 126 - functionName: tsc.propertyAccessExpression({ 127 - expression: 'TODO', 128 - name: 'record', 129 - // name: identifiers.record, 130 - }), 131 - parameters: [ 132 - tsc.callExpression({ 133 - functionName: tsc.propertyAccessExpression({ 134 - expression: 'TODO', 135 - name: 'string', 136 - // name: identifiers.string, 137 - }), 138 - parameters: [], 139 - }), 140 - additionalAst.expression, 141 - ], 142 - }); 89 + // name: identifiers.record, 90 + result.expression = $('TODO').attr('record').call( 91 + // name: identifiers.string, 92 + $('TODO').attr('string').call(), 93 + additionalAst.expression, 94 + ); 143 95 if (additionalAst.hasLazyExpression) { 144 96 result.hasLazyExpression = true; 145 97 } ··· 155 107 return result as Omit<Ast, 'typeName'>; 156 108 } 157 109 158 - result.expression = ts.factory.createObjectLiteralExpression( 159 - properties, 160 - true, 161 - ); 110 + result.expression = shape; 162 111 163 112 // return with typeName for circular references 164 113 if (result.hasLazyExpression) {
+1 -1
packages/openapi-ts/src/ts-dsl/as.ts
··· 30 30 export interface AsTsDsl extends ExprMixin {} 31 31 mixin(AsTsDsl, ExprMixin); 32 32 33 - registerLazyAccessAsFactory((expr, type) => new AsTsDsl(expr, type)); 33 + registerLazyAccessAsFactory((...args) => new AsTsDsl(...args));
+1 -1
packages/openapi-ts/src/ts-dsl/attr.ts
··· 60 60 OptionalMixin {} 61 61 mixin(AttrTsDsl, AssignmentMixin, ExprMixin, OperatorMixin, OptionalMixin); 62 62 63 - registerLazyAccessAttrFactory((expr, name) => new AttrTsDsl(expr, name)); 63 + registerLazyAccessAttrFactory((...args) => new AttrTsDsl(...args));
+1 -1
packages/openapi-ts/src/ts-dsl/await.ts
··· 22 22 export interface AwaitTsDsl extends ExprMixin {} 23 23 mixin(AwaitTsDsl, ExprMixin); 24 24 25 - registerLazyAccessAwaitFactory((expr) => new AwaitTsDsl(expr)); 25 + registerLazyAccessAwaitFactory((...args) => new AwaitTsDsl(...args));
+3 -17
packages/openapi-ts/src/ts-dsl/expr.ts
··· 6 6 import { mixin } from './mixins/apply'; 7 7 import { ExprMixin } from './mixins/expr'; 8 8 import { OperatorMixin } from './mixins/operator'; 9 - import { TypeExprTsDsl } from './type/expr'; 10 - import { TypeQueryTsDsl } from './type/query'; 11 - import { TypeOfExprTsDsl } from './typeof'; 9 + import { TypeExprMixin } from './mixins/type-expr'; 12 10 13 11 export class ExprTsDsl extends TsDsl<ts.Expression> { 14 12 private _exprInput: string | MaybeTsDsl<ts.Expression>; ··· 18 16 this._exprInput = id; 19 17 } 20 18 21 - returnType(): TypeExprTsDsl { 22 - return new TypeExprTsDsl('ReturnType').generic(this.typeofType()); 23 - } 24 - 25 - typeofExpr(): TypeOfExprTsDsl { 26 - return new TypeOfExprTsDsl(this); 27 - } 28 - 29 - typeofType(): TypeQueryTsDsl { 30 - return new TypeQueryTsDsl(this); 31 - } 32 - 33 19 $render(): ts.Expression { 34 20 return this.$node(this._exprInput); 35 21 } 36 22 } 37 23 38 - export interface ExprTsDsl extends ExprMixin, OperatorMixin {} 39 - mixin(ExprTsDsl, ExprMixin, OperatorMixin); 24 + export interface ExprTsDsl extends ExprMixin, OperatorMixin, TypeExprMixin {} 25 + mixin(ExprTsDsl, ExprMixin, OperatorMixin, TypeExprMixin);
+3 -5
packages/openapi-ts/src/ts-dsl/mixins/expr.ts
··· 8 8 import type { ReturnTsDsl } from '../return'; 9 9 10 10 /** 11 - * Access helpers depend on other DSL classes that are initialized later in the 12 - * module graph. We store factory callbacks here and let each class lazily 13 - * register its own implementation once it has finished evaluation. This keeps 14 - * mixin application order predictable and avoids circular import crashes. 11 + * Lazily register factory callbacks to avoid circular imports and 12 + * ensure predictable mixin application order. 15 13 */ 16 14 17 15 type AsFactory = ( ··· 19 17 type: string | TypeTsDsl, 20 18 ) => AsTsDsl; 21 19 let asFactory: AsFactory | undefined; 22 - /** Registers the Attr DSL factory after its module has finished evaluating. */ 20 + /** Registers the As DSL factory after its module has finished evaluating. */ 23 21 export function registerLazyAccessAsFactory(factory: AsFactory): void { 24 22 asFactory = factory; 25 23 }
+1 -3
packages/openapi-ts/src/ts-dsl/mixins/type-args.ts
··· 20 20 return this; 21 21 } 22 22 23 - /** 24 - * Returns the type arguments as an array of ts.TypeNode nodes. 25 - */ 23 + /** Returns the type arguments as an array of ts.TypeNode nodes. */ 26 24 protected $generics(): ReadonlyArray<ts.TypeNode> | undefined { 27 25 return this.$type(this._generics); 28 26 }
+64
packages/openapi-ts/src/ts-dsl/mixins/type-expr.ts
··· 1 + import type ts from 'typescript'; 2 + 3 + import type { MaybeTsDsl, TypeTsDsl } from '../base'; 4 + import type { TypeExprTsDsl } from '../type/expr'; 5 + import type { TypeQueryTsDsl } from '../type/query'; 6 + import type { TypeOfExprTsDsl } from '../typeof'; 7 + 8 + /** 9 + * Lazily register factory callbacks to avoid circular imports and 10 + * ensure predictable mixin application order. 11 + */ 12 + 13 + type TypeExprFactory = ( 14 + nameOrFn?: string | ((t: TypeExprTsDsl) => void), 15 + fn?: (t: TypeExprTsDsl) => void, 16 + ) => TypeExprTsDsl; 17 + let typeExprFactory: TypeExprFactory | undefined; 18 + /** Registers the TypeExpr DSL factory after its module has finished evaluating. */ 19 + export function registerLazyAccessTypeExprFactory( 20 + factory: TypeExprFactory, 21 + ): void { 22 + typeExprFactory = factory; 23 + } 24 + 25 + type TypeOfExprFactory = ( 26 + expr: string | MaybeTsDsl<ts.Expression>, 27 + ) => TypeOfExprTsDsl; 28 + let typeOfExprFactory: TypeOfExprFactory | undefined; 29 + /** Registers the TypeOfExpr DSL factory after its module has finished evaluating. */ 30 + export function registerLazyAccessTypeOfExprFactory( 31 + factory: TypeOfExprFactory, 32 + ): void { 33 + typeOfExprFactory = factory; 34 + } 35 + 36 + type TypeQueryFactory = ( 37 + expr: string | MaybeTsDsl<TypeTsDsl | ts.Expression>, 38 + ) => TypeQueryTsDsl; 39 + let typeQueryFactory: TypeQueryFactory | undefined; 40 + /** Registers the TypeQuery DSL factory after its module has finished evaluating. */ 41 + export function registerLazyAccessTypeQueryFactory( 42 + factory: TypeQueryFactory, 43 + ): void { 44 + typeQueryFactory = factory; 45 + } 46 + 47 + export class TypeExprMixin { 48 + /** Create a TypeExpr DSL node representing ReturnType<this>. */ 49 + returnType(this: MaybeTsDsl<ts.Expression>): TypeExprTsDsl { 50 + return typeExprFactory!('ReturnType').generic(typeQueryFactory!(this)); 51 + } 52 + 53 + /** Create a TypeOfExpr DSL node representing typeof this. */ 54 + typeofExpr(this: string | MaybeTsDsl<ts.Expression>): TypeOfExprTsDsl { 55 + return typeOfExprFactory!(this); 56 + } 57 + 58 + /** Create a TypeQuery DSL node representing typeof this. */ 59 + typeofType( 60 + this: string | MaybeTsDsl<TypeTsDsl | ts.Expression>, 61 + ): TypeQueryTsDsl { 62 + return typeQueryFactory!(this); 63 + } 64 + }
+1 -3
packages/openapi-ts/src/ts-dsl/mixins/type-params.ts
··· 22 22 return this; 23 23 } 24 24 25 - /** 26 - * Returns the type parameters as an array of ts.TypeParameterDeclaration nodes. 27 - */ 25 + /** Returns the type parameters as an array of ts.TypeParameterDeclaration nodes. */ 28 26 protected $generics(): 29 27 | ReadonlyArray<ts.TypeParameterDeclaration> 30 28 | undefined {
+15 -1
packages/openapi-ts/src/ts-dsl/object.ts
··· 15 15 export class ObjectTsDsl extends TsDsl<ts.ObjectLiteralExpression> { 16 16 private props: Array< 17 17 | { 18 + expr: string | MaybeTsDsl<ts.Expression>; 19 + kind: 'computed'; 20 + name: string; 21 + } 22 + | { 18 23 expr: string | MaybeTsDsl<ts.Statement>; 19 24 kind: 'getter'; 20 25 name: string; ··· 27 32 } 28 33 | { expr: string | MaybeTsDsl<ts.Expression>; kind: 'spread' } 29 34 > = []; 35 + 36 + /** Adds a computed property (e.g. `{ [expr]: value }`). */ 37 + computed(name: string, expr: string | MaybeTsDsl<ts.Expression>): this { 38 + this.props.push({ expr, kind: 'computed', name }); 39 + return this; 40 + } 30 41 31 42 /** Adds a getter property (e.g. `{ get foo() { ... } }`). */ 32 43 getter(name: string, expr: string | MaybeTsDsl<ts.Statement>): this { ··· 94 105 'Invalid property: object property value must be an expression, not a statement.', 95 106 ); 96 107 } 108 + const name = this.safePropertyName(p.name); 97 109 return ts.factory.createPropertyAssignment( 98 - this.safePropertyName(p.name), 110 + p.kind === 'computed' 111 + ? ts.factory.createComputedPropertyName(this.$node(name as string)) 112 + : name, 99 113 node, 100 114 ); 101 115 });
+1 -1
packages/openapi-ts/src/ts-dsl/return.ts
··· 22 22 export interface ReturnTsDsl extends ExprMixin {} 23 23 mixin(ReturnTsDsl, ExprMixin); 24 24 25 - registerLazyAccessReturnFactory((expr) => new ReturnTsDsl(expr)); 25 + registerLazyAccessReturnFactory((...args) => new ReturnTsDsl(...args));
+6
packages/openapi-ts/src/ts-dsl/type/attr.ts
··· 1 + /* eslint-disable @typescript-eslint/no-empty-object-type, @typescript-eslint/no-unsafe-declaration-merging */ 1 2 import ts from 'typescript'; 2 3 3 4 import type { MaybeTsDsl } from '../base'; 4 5 import { TsDsl } from '../base'; 6 + import { mixin } from '../mixins/apply'; 7 + import { TypeExprMixin } from '../mixins/type-expr'; 5 8 6 9 export class TypeAttrTsDsl extends TsDsl<ts.QualifiedName> { 7 10 private _base?: string | MaybeTsDsl<ts.EntityName>; ··· 43 46 return ts.factory.createQualifiedName(left, right); 44 47 } 45 48 } 49 + 50 + export interface TypeAttrTsDsl extends TypeExprMixin {} 51 + mixin(TypeAttrTsDsl, TypeExprMixin);
+12 -3
packages/openapi-ts/src/ts-dsl/type/expr.ts
··· 1 - /* eslint-disable @typescript-eslint/no-empty-object-type, @typescript-eslint/no-unsafe-declaration-merging */ 1 + /* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ 2 2 import ts from 'typescript'; 3 3 4 4 import { TypeTsDsl } from '../base'; 5 5 import { mixin } from '../mixins/apply'; 6 6 import { TypeArgsMixin } from '../mixins/type-args'; 7 + import { 8 + registerLazyAccessTypeExprFactory, 9 + TypeExprMixin, 10 + } from '../mixins/type-expr'; 7 11 import { TypeAttrTsDsl } from './attr'; 8 12 import { TypeIdxTsDsl } from './idx'; 9 13 ··· 52 56 } 53 57 } 54 58 55 - export interface TypeExprTsDsl extends TypeArgsMixin {} 56 - mixin(TypeExprTsDsl, TypeArgsMixin); 59 + export interface TypeExprTsDsl extends TypeArgsMixin, TypeExprMixin {} 60 + mixin(TypeExprTsDsl, TypeArgsMixin, TypeExprMixin); 61 + 62 + registerLazyAccessTypeExprFactory( 63 + (...args) => 64 + new TypeExprTsDsl(...(args as ConstructorParameters<typeof TypeExprTsDsl>)), 65 + );
+8 -10
packages/openapi-ts/src/ts-dsl/type/query.ts
··· 2 2 3 3 import type { MaybeTsDsl } from '../base'; 4 4 import { TypeTsDsl } from '../base'; 5 + import { registerLazyAccessTypeQueryFactory } from '../mixins/type-expr'; 5 6 6 7 export class TypeQueryTsDsl extends TypeTsDsl<ts.TypeQueryNode> { 7 - private expr: string | MaybeTsDsl<ts.Expression>; 8 + private _expr: string | MaybeTsDsl<TypeTsDsl | ts.Expression>; 8 9 9 - constructor(expr: string | MaybeTsDsl<ts.Expression>) { 10 + constructor(expr: string | MaybeTsDsl<TypeTsDsl | ts.Expression>) { 10 11 super(); 11 - this.expr = expr; 12 + this._expr = expr; 12 13 } 13 14 14 15 $render(): ts.TypeQueryNode { 15 - const exprName = this.$node(this.expr); 16 - if (!ts.isEntityName(exprName)) { 17 - throw new Error( 18 - 'TypeQueryTsDsl: expression must resolve to an EntityName', 19 - ); 20 - } 21 - return ts.factory.createTypeQueryNode(exprName); 16 + const expr = this.$node(this._expr); 17 + return ts.factory.createTypeQueryNode(expr as unknown as ts.EntityName); 22 18 } 23 19 } 20 + 21 + registerLazyAccessTypeQueryFactory((...args) => new TypeQueryTsDsl(...args));
+3
packages/openapi-ts/src/ts-dsl/typeof.ts
··· 5 5 import { TsDsl } from './base'; 6 6 import { mixin } from './mixins/apply'; 7 7 import { OperatorMixin } from './mixins/operator'; 8 + import { registerLazyAccessTypeOfExprFactory } from './mixins/type-expr'; 8 9 9 10 export class TypeOfExprTsDsl extends TsDsl<ts.TypeOfExpression> { 10 11 private _expr: string | MaybeTsDsl<ts.Expression>; ··· 21 22 22 23 export interface TypeOfExprTsDsl extends OperatorMixin {} 23 24 mixin(TypeOfExprTsDsl, OperatorMixin); 25 + 26 + registerLazyAccessTypeOfExprFactory((...args) => new TypeOfExprTsDsl(...args));