Skip to content

Commit 0489610

Browse files
committed
feat: add for loop to dsl
1 parent bb888a0 commit 0489610

12 files changed

Lines changed: 358 additions & 118 deletions

File tree

packages/openapi-ts-tests/main/test/3.1.x.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -898,7 +898,7 @@ describe(`OpenAPI ${version}`, () => {
898898
},
899899
{
900900
config: createConfig({
901-
input: 'transformers-additional-properties.json',
901+
input: 'transformers-additional-properties.yaml',
902902
output: 'transformers-additional-properties',
903903
plugins: ['@hey-api/client-fetch', '@hey-api/transformers'],
904904
}),

packages/openapi-ts/src/plugins/@hey-api/transformers/expressions.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export const bigIntExpressions: ExpressionTransformer = ({ dataExpression, schem
88

99
const bigIntCallExpression =
1010
dataExpression !== undefined
11-
? $('BigInt').call($.expr(dataExpression).attr('toString').call())
11+
? $('BigInt').call($(dataExpression).attr('toString').call())
1212
: undefined;
1313

1414
if (bigIntCallExpression) {
@@ -17,7 +17,7 @@ export const bigIntExpressions: ExpressionTransformer = ({ dataExpression, schem
1717
}
1818

1919
if (dataExpression) {
20-
return [$.expr(dataExpression).assign(bigIntCallExpression)];
20+
return [$(dataExpression).assign(bigIntCallExpression)];
2121
}
2222
}
2323

@@ -34,7 +34,7 @@ export const dateExpressions: ExpressionTransformer = ({ dataExpression, schema
3434
}
3535

3636
if (dataExpression) {
37-
return [$.expr(dataExpression).assign($.new('Date').arg(dataExpression))];
37+
return [$(dataExpression).assign($.new('Date').arg(dataExpression))];
3838
}
3939

4040
return;

packages/openapi-ts/src/plugins/@hey-api/transformers/plugin.ts

Lines changed: 5 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const dataVariableName = 'data';
1212
// can emit calls to transformers that will be implemented later.
1313
const buildingSymbols = new Set<number>();
1414

15-
type Expr = ReturnType<typeof $.fromValue | typeof $.return | typeof $.if>;
15+
type Expr = ReturnType<typeof $.fromValue | typeof $.for | typeof $.if | typeof $.return>;
1616

1717
function isNodeReturnStatement(node: Expr) {
1818
return node['~dsl'] === 'ReturnTsDsl';
@@ -195,48 +195,17 @@ function processSchemaType({
195195
}
196196

197197
if (schema.additionalProperties && dataExpression) {
198-
const entryValueExpression = $.expr('entry[1]');
199198
const entryValueNodes = processSchemaType({
200-
dataExpression: entryValueExpression,
199+
dataExpression: $(dataExpression).attr('key').computed(),
201200
plugin,
202201
schema: schema.additionalProperties,
203202
});
204203

205204
if (entryValueNodes.length) {
206-
const declaredProperties = Object.keys(schema.properties ?? {});
207-
const mapCallbackNodes: Array<Expr> = [];
208-
209-
if (declaredProperties.length) {
210-
mapCallbackNodes.push(
211-
$.if(
212-
$.expr($.array(...declaredProperties.map((name) => $.literal(name))))
213-
.attr('includes')
214-
.call($.expr('entry[0]')),
215-
).do($.return('entry')),
216-
);
217-
}
218-
219-
mapCallbackNodes.push(
220-
...entryValueNodes,
221-
$.return($.array($.expr('entry[0]'), $.expr('entry[1]'))),
222-
);
223-
224205
nodes.push(
225-
$(dataExpression).assign(
226-
$('Object')
227-
.attr('fromEntries')
228-
.call(
229-
$('Object')
230-
.attr('entries')
231-
.call(dataExpression)
232-
.attr('map')
233-
.call(
234-
$.func()
235-
.param('entry', (p) => p.type('any'))
236-
.do(...mapCallbackNodes),
237-
),
238-
),
239-
),
206+
$.for($.const('key'))
207+
.of($('Object').attr('keys').call(dataExpression))
208+
.do(...entryValueNodes),
240209
);
241210
}
242211
}

packages/openapi-ts/src/ts-dsl/expr/attr.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,25 +25,32 @@ const Mixed = AsMixin(
2525
export class AttrTsDsl extends Mixed {
2626
readonly '~dsl' = 'AttrTsDsl';
2727

28-
protected left: Ref<AttrLeft>;
28+
protected _computed = false;
29+
protected _left: Ref<AttrLeft>;
2930

3031
constructor(left: AttrLeft, right: NodeName) {
3132
super();
32-
this.left = ref(left);
33+
this._left = ref(left);
3334
this.name.set(right);
3435
}
3536

3637
override analyze(ctx: AnalysisContext): void {
3738
super.analyze(ctx);
38-
ctx.analyze(this.left);
39+
ctx.analyze(this._left);
3940
ctx.analyze(this.name);
4041
}
4142

43+
/** Use computed property access (e.g., `obj[expr]`)? */
44+
computed(condition?: boolean): this {
45+
this._computed = condition ?? true;
46+
return this;
47+
}
48+
4249
override toAst() {
43-
const leftNode = this.$node(this.left);
50+
const left = this.$node(this._left);
4451
regexp.typeScriptIdentifier.lastIndex = 0;
45-
const right = fromRef(this.name);
46-
if (!regexp.typeScriptIdentifier.test(this.name.toString())) {
52+
if (this._computed || !regexp.typeScriptIdentifier.test(this.name.toString())) {
53+
const right = fromRef(this.name);
4754
let value = isSymbol(right) ? right.finalName : right;
4855
if (typeof value === 'string') {
4956
if (
@@ -55,27 +62,24 @@ export class AttrTsDsl extends Mixed {
5562
}
5663
if (this._optional) {
5764
return ts.factory.createElementAccessChain(
58-
leftNode,
65+
left,
5966
this.$node(new TokenTsDsl().questionDot()),
60-
this.$node(new LiteralTsDsl(value)),
67+
this.$node(this._computed ? this.name : new LiteralTsDsl(value)),
6168
);
6269
}
6370
return ts.factory.createElementAccessExpression(
64-
leftNode,
65-
this.$node(new LiteralTsDsl(value)),
71+
left,
72+
this.$node(this._computed ? this.name : new LiteralTsDsl(value)),
6673
);
6774
}
6875
if (this._optional) {
6976
return ts.factory.createPropertyAccessChain(
70-
leftNode,
77+
left,
7178
this.$node(new TokenTsDsl().questionDot()),
7279
this.$node(this.name) as ts.MemberName,
7380
);
7481
}
75-
return ts.factory.createPropertyAccessExpression(
76-
leftNode,
77-
this.$node(this.name) as ts.MemberName,
78-
);
82+
return ts.factory.createPropertyAccessExpression(left, this.$node(this.name) as ts.MemberName);
7983
}
8084
}
8185

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import type { AnalysisContext } from '@hey-api/codegen-core';
2+
import ts from 'typescript';
3+
4+
import type { MaybeTsDsl } from '../base';
5+
import { TsDsl } from '../base';
6+
7+
export type PostfixExpr = string | MaybeTsDsl<ts.Expression>;
8+
export type PostfixOp = ts.PostfixUnaryOperator;
9+
10+
const Mixed = TsDsl<ts.PostfixUnaryExpression>;
11+
12+
export class PostfixTsDsl extends Mixed {
13+
readonly '~dsl' = 'PostfixTsDsl';
14+
15+
protected _expr?: PostfixExpr;
16+
protected _op?: PostfixOp;
17+
18+
constructor(expr?: PostfixExpr, op?: PostfixOp) {
19+
super();
20+
this._expr = expr;
21+
this._op = op;
22+
}
23+
24+
override analyze(ctx: AnalysisContext): void {
25+
super.analyze(ctx);
26+
ctx.analyze(this._expr);
27+
}
28+
29+
/** Returns true when all required builder calls are present. */
30+
get isValid(): boolean {
31+
return this.missingRequiredCalls().length === 0;
32+
}
33+
34+
/** Sets the operator to MinusMinusToken for decrement (`--`). */
35+
dec(): this {
36+
this._op = ts.SyntaxKind.MinusMinusToken;
37+
return this;
38+
}
39+
40+
/** Sets the operand (the expression being postfixed). */
41+
expr(expr: PostfixExpr): this {
42+
this._expr = expr;
43+
return this;
44+
}
45+
46+
/** Sets the operator to PlusPlusToken for increment (`++`). */
47+
inc(): this {
48+
this._op = ts.SyntaxKind.PlusPlusToken;
49+
return this;
50+
}
51+
52+
/** Sets the operator (e.g., `ts.SyntaxKind.PlusPlusToken` for `++`). */
53+
op(op: PostfixOp): this {
54+
this._op = op;
55+
return this;
56+
}
57+
58+
override toAst() {
59+
this.$validate();
60+
return ts.factory.createPostfixUnaryExpression(this.$node(this._expr), this._op);
61+
}
62+
63+
$validate(): asserts this is this & {
64+
_expr: PostfixExpr;
65+
_op: PostfixOp;
66+
} {
67+
const missing = this.missingRequiredCalls();
68+
if (missing.length === 0) return;
69+
throw new Error(`Postfix unary expression missing ${missing.join(' and ')}`);
70+
}
71+
72+
private missingRequiredCalls(): ReadonlyArray<string> {
73+
const missing: Array<string> = [];
74+
if (!this._expr) missing.push('.expr()');
75+
if (!this._op) missing.push('operator (e.g., .inc(), .dec())');
76+
return missing;
77+
}
78+
}

packages/openapi-ts/src/ts-dsl/expr/prefix.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,18 @@ import ts from 'typescript';
44
import type { MaybeTsDsl } from '../base';
55
import { TsDsl } from '../base';
66

7+
export type PrefixExpr = string | MaybeTsDsl<ts.Expression>;
8+
export type PrefixOp = ts.PrefixUnaryOperator;
9+
710
const Mixed = TsDsl<ts.PrefixUnaryExpression>;
811

912
export class PrefixTsDsl extends Mixed {
1013
readonly '~dsl' = 'PrefixTsDsl';
1114

12-
protected _expr?: string | MaybeTsDsl<ts.Expression>;
13-
protected _op?: ts.PrefixUnaryOperator;
15+
protected _expr?: PrefixExpr;
16+
protected _op?: PrefixOp;
1417

15-
constructor(expr?: string | MaybeTsDsl<ts.Expression>, op?: ts.PrefixUnaryOperator) {
18+
constructor(expr?: PrefixExpr, op?: PrefixOp) {
1619
super();
1720
this._expr = expr;
1821
this._op = op;
@@ -29,7 +32,7 @@ export class PrefixTsDsl extends Mixed {
2932
}
3033

3134
/** Sets the operand (the expression being prefixed). */
32-
expr(expr: string | MaybeTsDsl<ts.Expression>): this {
35+
expr(expr: PrefixExpr): this {
3336
this._expr = expr;
3437
return this;
3538
}
@@ -47,7 +50,7 @@ export class PrefixTsDsl extends Mixed {
4750
}
4851

4952
/** Sets the operator (e.g. `ts.SyntaxKind.ExclamationToken` for `!`). */
50-
op(op: ts.PrefixUnaryOperator): this {
53+
op(op: PrefixOp): this {
5154
this._op = op;
5255
return this;
5356
}
@@ -58,8 +61,8 @@ export class PrefixTsDsl extends Mixed {
5861
}
5962

6063
$validate(): asserts this is this & {
61-
_expr: string | MaybeTsDsl<ts.Expression>;
62-
_op: ts.PrefixUnaryOperator;
64+
_expr: PrefixExpr;
65+
_op: PrefixOp;
6366
} {
6467
const missing = this.missingRequiredCalls();
6568
if (missing.length === 0) return;

packages/openapi-ts/src/ts-dsl/index.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { IdTsDsl } from './expr/id';
2424
import { LiteralTsDsl } from './expr/literal';
2525
import { NewTsDsl } from './expr/new';
2626
import { ObjectTsDsl } from './expr/object';
27+
import { PostfixTsDsl } from './expr/postfix';
2728
import { PrefixTsDsl } from './expr/prefix';
2829
import { ObjectPropTsDsl } from './expr/prop';
2930
import { RegExpTsDsl } from './expr/regexp';
@@ -35,6 +36,8 @@ import { HintTsDsl } from './layout/hint';
3536
import { NewlineTsDsl } from './layout/newline';
3637
import { NoteTsDsl } from './layout/note';
3738
import { BlockTsDsl } from './stmt/block';
39+
import type { ForCondition, ForIterable, ForMode } from './stmt/for';
40+
import { ForTsDsl } from './stmt/for';
3841
import { IfTsDsl } from './stmt/if';
3942
import { ReturnTsDsl } from './stmt/return';
4043
import { StmtTsDsl } from './stmt/stmt';
@@ -88,6 +91,9 @@ const tsDsl = {
8891
/** Creates a constant variable declaration (`const`). */
8992
const: (...args: ConstructorParameters<typeof VarTsDsl>) => new VarTsDsl(...args).const(),
9093

94+
/** Creates a postfix decrement expression (`i--`). */
95+
dec: (...args: ConstructorParameters<typeof PostfixTsDsl>) => new PostfixTsDsl(...args).dec(),
96+
9197
/** Creates a decorator expression (e.g. `@decorator`). */
9298
decorator: (...args: ConstructorParameters<typeof DecoratorTsDsl>) => new DecoratorTsDsl(...args),
9399

@@ -103,14 +109,28 @@ const tsDsl = {
103109
/** Creates a field declaration in a class or object. */
104110
field: (...args: ConstructorParameters<typeof FieldTsDsl>) => new FieldTsDsl(...args),
105111

112+
/** Creates a for loop (for, for...of, for...in, or for await...of). */
113+
for: ((...args: ReadonlyArray<any>) => new ForTsDsl(...args)) as {
114+
(variableOrInit?: VarTsDsl): ForTsDsl<ForMode>;
115+
(
116+
variableOrInit: VarTsDsl,
117+
condition: ForCondition,
118+
iterableOrUpdate?: ForIterable,
119+
): ForTsDsl<'for'>;
120+
<T extends ForMode>(
121+
variableOrInit: VarTsDsl,
122+
mode: T,
123+
iterableOrUpdate?: ForIterable,
124+
): ForTsDsl<T>;
125+
},
126+
106127
/** Converts a runtime value into a corresponding expression node. */
107128
fromValue: (...args: Parameters<typeof exprValue>) => exprValue(...args),
108129

109130
/** Creates a function expression or declaration. */
110131
func: ((nameOrFn?: any, fn?: any) => {
111132
if (nameOrFn === undefined) return new FuncTsDsl();
112-
if (typeof nameOrFn !== 'string') return new FuncTsDsl(nameOrFn);
113-
if (fn === undefined) return new FuncTsDsl(nameOrFn);
133+
if (typeof nameOrFn !== 'string' || fn === undefined) return new FuncTsDsl(nameOrFn);
114134
return new FuncTsDsl(nameOrFn, fn);
115135
}) as {
116136
(): FuncTsDsl<'arrow'>;
@@ -132,6 +152,9 @@ const tsDsl = {
132152
/** Creates an if statement. */
133153
if: (...args: ConstructorParameters<typeof IfTsDsl>) => new IfTsDsl(...args),
134154

155+
/** Creates a postfix increment expression (`i++`). */
156+
inc: (...args: ConstructorParameters<typeof PostfixTsDsl>) => new PostfixTsDsl(...args).inc(),
157+
135158
/** Creates an initialization block or statement. */
136159
init: (...args: ConstructorParameters<typeof InitTsDsl>) => new InitTsDsl(...args),
137160

0 commit comments

Comments
 (0)