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

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #3247 from hey-api/fix/ts-dsl-object-prop-remove

fix: object ts-dsl improvements, ability to remove

authored by

Lubos and committed by
GitHub
2fe51582 361d5dca

+157 -54
+5
.changeset/big-streets-behave.md
··· 1 + --- 2 + '@hey-api/openapi-ts': patch 3 + --- 4 + 5 + **ts-dsl**: allow removing object properties by passing `null`
+5
.changeset/ready-dots-beg.md
··· 1 + --- 2 + '@hey-api/openapi-ts': patch 3 + --- 4 + 5 + **ts-dsl**: override object properties when called multiple times with the same name
+2 -1
dev/openapi-ts.config.ts
··· 475 475 // definitions: 'z{{name}}', 476 476 exportFromIndex: true, 477 477 // metadata: true, 478 - // name: 'valibot', 478 + name: 'valibot', 479 479 // requests: { 480 480 // case: 'PascalCase', 481 481 // name: '{{name}}Data', ··· 524 524 const additional = ctx.nodes.additionalProperties(ctx); 525 525 if (additional === undefined) { 526 526 const shape = ctx.nodes.shape(ctx); 527 + shape.prop('body', $(v).attr('never').call()); 527 528 ctx.nodes.base = () => $(v).attr('looseObject').call(shape); 528 529 } 529 530 },
+61 -22
packages/openapi-ts/src/ts-dsl/expr/object.ts
··· 21 21 export class ObjectTsDsl extends Mixed { 22 22 readonly '~dsl' = 'ObjectTsDsl'; 23 23 24 - protected _props: Array<ObjectPropTsDsl> = []; 24 + protected _props = new Map<string, ObjectPropTsDsl>(); 25 + protected _spreadCounter = 0; 25 26 26 27 constructor(...props: Array<ObjectPropTsDsl>) { 27 28 super(); ··· 30 31 31 32 override analyze(ctx: AnalysisContext): void { 32 33 super.analyze(ctx); 33 - for (const prop of this._props) { 34 + for (const prop of this._props.values()) { 34 35 ctx.analyze(prop); 35 36 } 36 37 } 37 38 38 - /** Adds a computed property (e.g. `{ [expr]: value }`). */ 39 - computed(name: string, expr: ExprFn): this { 40 - this._props.push( 41 - new ObjectPropTsDsl({ kind: 'computed', name }).value(expr), 42 - ); 39 + /** Returns composite key for the property. */ 40 + private _propKey(prop: ObjectPropTsDsl): string { 41 + if (prop.kind === 'spread') { 42 + return `spread:${this._spreadCounter++}`; 43 + } 44 + return `${prop.kind}:${prop.propName}`; 45 + } 46 + 47 + /** Adds a computed property (e.g. `{ [expr]: value }`), or removes if null. */ 48 + computed(name: string, expr: ExprFn | null): this { 49 + if (expr === null) { 50 + this._props.delete(`computed:${name}`); 51 + } else { 52 + this._props.set( 53 + `computed:${name}`, 54 + new ObjectPropTsDsl({ kind: 'computed', name }).value(expr), 55 + ); 56 + } 43 57 return this; 44 58 } 45 59 46 - /** Adds a getter property (e.g. `{ get foo() { ... } }`). */ 47 - getter(name: string, stmt: StmtFn): this { 48 - this._props.push(new ObjectPropTsDsl({ kind: 'getter', name }).value(stmt)); 60 + /** Adds a getter property (e.g. `{ get foo() { ... } }`), or removes if null. */ 61 + getter(name: string, stmt: StmtFn | null): this { 62 + if (stmt === null) { 63 + this._props.delete(`getter:${name}`); 64 + } else { 65 + this._props.set( 66 + `getter:${name}`, 67 + new ObjectPropTsDsl({ kind: 'getter', name }).value(stmt), 68 + ); 69 + } 49 70 return this; 50 71 } 51 72 52 73 /** Returns true if object has at least one property or spread. */ 53 74 hasProps(): boolean { 54 - return this._props.length > 0; 75 + return this._props.size > 0; 55 76 } 56 77 57 78 /** Returns true if object has no properties or spreads. */ 58 79 get isEmpty(): boolean { 59 - return this._props.length === 0; 80 + return this._props.size === 0; 60 81 } 61 82 62 - /** Adds a property assignment. */ 63 - prop(name: string, expr: ExprFn): this { 64 - this._props.push(new ObjectPropTsDsl({ kind: 'prop', name }).value(expr)); 83 + /** Adds a property assignment, or removes if null. */ 84 + prop(name: string, expr: ExprFn | null): this { 85 + if (expr === null) { 86 + this._props.delete(`prop:${name}`); 87 + } else { 88 + this._props.set( 89 + `prop:${name}`, 90 + new ObjectPropTsDsl({ kind: 'prop', name }).value(expr), 91 + ); 92 + } 65 93 return this; 66 94 } 67 95 68 96 /** Adds multiple properties. */ 69 97 props(...props: ReadonlyArray<ObjectPropTsDsl>): this { 70 - this._props.push(...props); 98 + for (const prop of props) { 99 + this._props.set(this._propKey(prop), prop); 100 + } 71 101 return this; 72 102 } 73 103 74 - /** Adds a setter property (e.g. `{ set foo(v) { ... } }`). */ 75 - setter(name: string, stmt: StmtFn): this { 76 - this._props.push(new ObjectPropTsDsl({ kind: 'setter', name }).value(stmt)); 104 + /** Adds a setter property (e.g. `{ set foo(v) { ... } }`), or removes if null. */ 105 + setter(name: string, stmt: StmtFn | null): this { 106 + if (stmt === null) { 107 + this._props.delete(`setter:${name}`); 108 + } else { 109 + this._props.set( 110 + `setter:${name}`, 111 + new ObjectPropTsDsl({ kind: 'setter', name }).value(stmt), 112 + ); 113 + } 77 114 return this; 78 115 } 79 116 80 117 /** Adds a spread property (e.g. `{ ...options }`). */ 81 118 spread(expr: ExprFn): this { 82 - this._props.push(new ObjectPropTsDsl({ kind: 'spread' }).value(expr)); 119 + const key = `spread:${this._spreadCounter++}`; 120 + this._props.set(key, new ObjectPropTsDsl({ kind: 'spread' }).value(expr)); 83 121 return this; 84 122 } 85 123 86 124 override toAst() { 125 + const props = [...this._props.values()]; 87 126 const node = ts.factory.createObjectLiteralExpression( 88 - this.$node(this._props), 89 - this.$multiline(this._props.length), 127 + this.$node(props), 128 + this.$multiline(props.length), 90 129 ); 91 130 return this.$hint(node); 92 131 }
+29 -16
packages/openapi-ts/src/ts-dsl/expr/prop.ts
··· 12 12 13 13 type Expr = NodeName | MaybeTsDsl<ts.Expression>; 14 14 type Stmt = NodeName | MaybeTsDsl<ts.Statement>; 15 - type Kind = 'computed' | 'getter' | 'prop' | 'setter' | 'spread'; 15 + 16 + export type ObjectPropKind = 17 + | 'computed' 18 + | 'getter' 19 + | 'prop' 20 + | 'setter' 21 + | 'spread'; 16 22 17 23 type Meta = 18 24 | { kind: 'computed'; name: string } ··· 27 33 readonly '~dsl' = 'ObjectPropTsDsl'; 28 34 29 35 protected _value?: Ref<Expr | Stmt>; 30 - protected meta: Meta; 36 + protected _meta: Meta; 31 37 32 38 constructor(meta: Meta) { 33 39 super(); 34 - this.meta = meta; 40 + this._meta = meta; 41 + } 42 + 43 + get kind(): ObjectPropKind { 44 + return this._meta.kind; 45 + } 46 + 47 + get propName(): string | undefined { 48 + return this._meta.name; 35 49 } 36 50 37 51 override analyze(ctx: AnalysisContext): void { ··· 39 53 ctx.analyze(this._value); 40 54 } 41 55 42 - /** Returns true when all required builder calls are present. */ 43 56 get isValid(): boolean { 44 57 return this.missingRequiredCalls().length === 0; 45 58 } ··· 56 69 override toAst() { 57 70 this.$validate(); 58 71 const node = this.$node(this._value); 59 - if (this.meta.kind === 'spread') { 72 + if (this._meta.kind === 'spread') { 60 73 if (ts.isStatement(node)) { 61 74 throw new Error( 62 75 'Invalid spread: object spread must be an expression, not a statement.', ··· 65 78 const result = ts.factory.createSpreadAssignment(node); 66 79 return this.$docs(result); 67 80 } 68 - if (this.meta.kind === 'getter') { 69 - const getter = new GetterTsDsl(this.meta.name).do(node); 81 + if (this._meta.kind === 'getter') { 82 + const getter = new GetterTsDsl(this._meta.name).do(node); 70 83 const result = this.$node(getter); 71 84 return this.$docs(result); 72 85 } 73 - if (this.meta.kind === 'setter') { 74 - const setter = new SetterTsDsl(this.meta.name).do(node); 86 + if (this._meta.kind === 'setter') { 87 + const setter = new SetterTsDsl(this._meta.name).do(node); 75 88 const result = this.$node(setter); 76 89 return this.$docs(result); 77 90 } 78 - if (ts.isIdentifier(node) && node.text === this.meta.name) { 91 + if (ts.isIdentifier(node) && node.text === this._meta.name) { 79 92 const result = ts.factory.createShorthandPropertyAssignment( 80 - this.meta.name, 93 + this._meta.name, 81 94 ); 82 95 return this.$docs(result); 83 96 } ··· 87 100 ); 88 101 } 89 102 const result = ts.factory.createPropertyAssignment( 90 - this.meta.kind === 'computed' 103 + this._meta.kind === 'computed' 91 104 ? ts.factory.createComputedPropertyName( 92 - this.$node(new IdTsDsl(this.meta.name)), 105 + this.$node(new IdTsDsl(this._meta.name)), 93 106 ) 94 - : this.$node(safePropName(this.meta.name)), 107 + : this.$node(safePropName(this._meta.name)), 95 108 node, 96 109 ); 97 110 return this.$docs(result); ··· 99 112 100 113 $validate(): asserts this is this & { 101 114 _value: Expr | Stmt; 102 - kind: Kind; 115 + kind: ObjectPropKind; 103 116 } { 104 117 const missing = this.missingRequiredCalls(); 105 118 if (missing.length === 0) return; 106 119 throw new Error( 107 - `Object property${this.meta.name ? ` "${this.meta.name}"` : ''} missing ${missing.join(' and ')}`, 120 + `Object property${this._meta.name ? ` "${this._meta.name}"` : ''} missing ${missing.join(' and ')}`, 108 121 ); 109 122 } 110 123
+11
packages/openapi-ts/src/ts-dsl/type/idx-sig.ts
··· 11 11 import { ReadonlyMixin } from '../mixins/modifiers'; 12 12 13 13 export type TypeIdxSigType = string | MaybeTsDsl<ts.TypeNode>; 14 + export type TypeIdxSigKind = 'idxSig'; 14 15 15 16 const Mixed = DocMixin(ReadonlyMixin(TsDsl<ts.IndexSignatureDeclaration>)); 16 17 ··· 25 26 super(); 26 27 this.name.set(name); 27 28 fn?.(this); 29 + } 30 + 31 + /** Element kind. */ 32 + get kind(): TypeIdxSigKind { 33 + return 'idxSig'; 34 + } 35 + 36 + /** Index signature parameter name. */ 37 + get propName(): string { 38 + return this.name.toString(); 28 39 } 29 40 30 41 override analyze(ctx: AnalysisContext): void {
+33 -15
packages/openapi-ts/src/ts-dsl/type/object.ts
··· 11 11 readonly '~dsl' = 'TypeObjectTsDsl'; 12 12 override scope: NodeScope = 'type'; 13 13 14 - protected props: Array<TypePropTsDsl | TypeIdxSigTsDsl> = []; 14 + protected _props = new Map<string, TypePropTsDsl | TypeIdxSigTsDsl>(); 15 15 16 16 override analyze(ctx: AnalysisContext): void { 17 17 super.analyze(ctx); 18 - for (const prop of this.props) { 18 + for (const prop of this._props.values()) { 19 19 ctx.analyze(prop); 20 20 } 21 21 } 22 22 23 - /** Returns true if object has at least one property or spread. */ 23 + /** Returns true if object has at least one property or index signature. */ 24 24 hasProps(): boolean { 25 - return this.props.length > 0; 25 + return this._props.size > 0; 26 26 } 27 27 28 - /** Adds an index signature to the object type. */ 29 - idxSig(name: string, fn: (i: TypeIdxSigTsDsl) => void): this { 30 - const idx = new TypeIdxSigTsDsl(name, fn); 31 - this.props.push(idx); 28 + /** Adds an index signature to the object type, or removes if fn is null. */ 29 + idxSig(name: string, fn: ((i: TypeIdxSigTsDsl) => void) | null): this { 30 + const key = `idxSig:${name}`; 31 + if (fn === null) { 32 + this._props.delete(key); 33 + } else { 34 + this._props.set(key, new TypeIdxSigTsDsl(name, fn)); 35 + } 32 36 return this; 33 37 } 34 38 35 - /** Returns true if object has no properties or spreads. */ 39 + /** Returns true if object has no properties or index signatures. */ 36 40 get isEmpty(): boolean { 37 - return !this.props.length; 41 + return this._props.size === 0; 38 42 } 39 43 40 - /** Adds a property signature (returns property builder). */ 41 - prop(name: string, fn: (p: TypePropTsDsl) => void): this { 42 - const prop = new TypePropTsDsl(name, fn); 43 - this.props.push(prop); 44 + /** Adds a property signature, or removes if fn is null. */ 45 + prop(name: string, fn: ((p: TypePropTsDsl) => void) | null): this { 46 + const key = `prop:${name}`; 47 + if (fn === null) { 48 + this._props.delete(key); 49 + } else { 50 + this._props.set(key, new TypePropTsDsl(name, fn)); 51 + } 52 + return this; 53 + } 54 + 55 + /** Adds multiple properties/index signatures. */ 56 + props(...members: ReadonlyArray<TypePropTsDsl | TypeIdxSigTsDsl>): this { 57 + for (const member of members) { 58 + this._props.set(`${member.kind}:${member.propName}`, member); 59 + } 44 60 return this; 45 61 } 46 62 47 63 override toAst() { 48 - return ts.factory.createTypeLiteralNode(this.$node(this.props)); 64 + return ts.factory.createTypeLiteralNode( 65 + this.$node([...this._props.values()]), 66 + ); 49 67 } 50 68 }
+11
packages/openapi-ts/src/ts-dsl/type/prop.ts
··· 16 16 import { safePropName } from '../utils/name'; 17 17 18 18 export type TypePropType = NodeName | MaybeTsDsl<ts.TypeNode>; 19 + export type TypePropKind = 'prop'; 19 20 20 21 const Mixed = DocMixin(OptionalMixin(ReadonlyMixin(TsDsl<ts.TypeElement>))); 21 22 ··· 29 30 super(); 30 31 this.name.set(name); 31 32 fn(this); 33 + } 34 + 35 + /** Element kind. */ 36 + get kind(): TypePropKind { 37 + return 'prop'; 38 + } 39 + 40 + /** Property name. */ 41 + get propName(): string { 42 + return this.name.toString(); 32 43 } 33 44 34 45 override analyze(ctx: AnalysisContext): void {