fork of hey-api/openapi-ts because I need some additional things
1// TODO: symbol should be protected, but needs to be public to satisfy types
2import type {
3 AnalysisContext,
4 File,
5 FromRef,
6 Language,
7 Node,
8 NodeName,
9 NodeNameSanitizer,
10 NodeRelationship,
11 NodeScope,
12 Ref,
13 Symbol,
14} from '@hey-api/codegen-core';
15import { fromRef, isNode, isRef, isSymbol, nodeBrand, ref } from '@hey-api/codegen-core';
16import type { AnyString } from '@hey-api/types';
17
18import { py } from '../ts-python';
19import type { AccessOptions } from './utils/context';
20
21export abstract class PyDsl<T extends py.Node = py.Node> implements Node<T> {
22 // eslint-disable-next-line @typescript-eslint/no-unused-vars
23 analyze(_: AnalysisContext): void {}
24 clone(): this {
25 const cloned = Object.create(Object.getPrototypeOf(this));
26 Object.assign(cloned, this);
27 return cloned;
28 }
29 exported?: boolean;
30 file?: File;
31 get name(): Node['name'] {
32 return {
33 ...this._name,
34 set: (value) => {
35 this._name = ref(value);
36 if (isSymbol(value)) {
37 value.setNode(this);
38 }
39 },
40 toString: () => (this._name ? this.$name(this._name) : ''),
41 } as Node['name'];
42 }
43 readonly nameSanitizer?: NodeNameSanitizer;
44 language: Language = 'python';
45 parent?: Node;
46 root: boolean = false;
47 scope?: NodeScope = 'value';
48 structuralChildren?: Map<PyDsl, NodeRelationship>;
49 structuralParents?: Map<PyDsl, NodeRelationship>;
50 symbol?: Symbol;
51 toAst(): T {
52 return undefined as unknown as T;
53 }
54 readonly '~brand' = nodeBrand;
55
56 /** Branding property to identify the DSL class at runtime. */
57 abstract readonly '~dsl': AnyString;
58
59 /** Conditionally applies a callback to this builder. */
60 $if<T extends PyDsl, V, R extends PyDsl = T>(
61 this: T,
62 value: V,
63 ifTrue: (self: T, v: Exclude<V, false | null | undefined>) => R | void,
64 ifFalse?: (self: T, v: Extract<V, false | null | undefined>) => R | void,
65 ): R | T;
66 $if<T extends PyDsl, V, R extends PyDsl = T>(
67 this: T,
68 value: V,
69 ifTrue: (v: Exclude<V, false | null | undefined>) => R | void,
70 ifFalse?: (v: Extract<V, false | null | undefined>) => R | void,
71 ): R | T;
72 $if<T extends PyDsl, V, R extends PyDsl = T>(
73 this: T,
74 value: V,
75 ifTrue: () => R | void,
76 ifFalse?: () => R | void,
77 ): R | T;
78 $if<T extends PyDsl, V, R extends PyDsl = T>(
79 this: T,
80 value: V,
81 ifTrue: any,
82 ifFalse?: any,
83 ): R | T {
84 if (value) {
85 // Try calling with (self, value), then (value), then ()
86 let result: R | void | undefined;
87 try {
88 result = ifTrue?.(this, value as Exclude<V, false | null | undefined>);
89 } catch {
90 // ignore and try other signatures
91 }
92 if (result === undefined) {
93 try {
94 result = ifTrue?.(value as Exclude<V, false | null | undefined>);
95 } catch {
96 // ignore and try zero-arg
97 }
98 }
99 if (result === undefined) {
100 try {
101 result = ifTrue?.();
102 } catch {
103 // swallow
104 }
105 }
106 return (result ?? this) as R | T;
107 }
108 if (ifFalse) {
109 let result: R | void | undefined;
110 try {
111 result = ifFalse?.(this, value as Extract<V, false | null | undefined>);
112 } catch {
113 // ignore
114 }
115 if (result === undefined) {
116 try {
117 result = ifFalse?.(value as Extract<V, false | null | undefined>);
118 } catch {
119 // ignore
120 }
121 }
122 if (result === undefined) {
123 try {
124 result = ifFalse?.();
125 } catch {
126 // ignore
127 }
128 }
129 return (result ?? this) as R | T;
130 }
131 return this;
132 }
133
134 /** Access patterns for this node. */
135 toAccessNode?(
136 node: this,
137 options: AccessOptions,
138 ctx: {
139 /** The full chain. */
140 chain: ReadonlyArray<PyDsl>;
141 /** Position in the chain (0 = root). */
142 index: number;
143 /** Is this the leaf node? */
144 isLeaf: boolean;
145 /** Is this the root node? */
146 isRoot: boolean;
147 /** Total length of the chain. */
148 length: number;
149 },
150 ): PyDsl | undefined;
151
152 protected $maybeId<T extends string | py.Expression>(
153 expr: T,
154 ): T extends string ? py.Identifier : T {
155 return (typeof expr === 'string' ? py.factory.createIdentifier(expr) : expr) as T extends string
156 ? py.Identifier
157 : T;
158 }
159
160 protected $name(name: Ref<NodeName>): string {
161 const value = fromRef(name);
162 if (isSymbol(value)) {
163 try {
164 return value.finalName;
165 } catch {
166 return value.name;
167 }
168 }
169 return String(value);
170 }
171
172 protected $node<I>(value: I): NodeOfMaybe<I> {
173 if (value === undefined) {
174 return undefined as NodeOfMaybe<I>;
175 }
176 // @ts-expect-error
177 if (isRef(value)) value = fromRef(value);
178 if (isSymbol(value)) {
179 return this.$maybeId(value.finalName) as NodeOfMaybe<I>;
180 }
181 if (typeof value === 'string') {
182 return this.$maybeId(value) as NodeOfMaybe<I>;
183 }
184 if (value instanceof Array) {
185 return value.map((item) => {
186 if (isRef(item)) item = fromRef(item);
187 return this.unwrap(item);
188 }) as NodeOfMaybe<I>;
189 }
190 return this.unwrap(value as any) as NodeOfMaybe<I>;
191 }
192
193 // protected $type<I>(value: I, args?: ReadonlyArray<ts.TypeNode>): TypeOfMaybe<I> {
194 // if (value === undefined) {
195 // return undefined as TypeOfMaybe<I>;
196 // }
197 // // @ts-expect-error
198 // if (isRef(value)) value = fromRef(value);
199 // if (isSymbol(value)) {
200 // return ts.factory.createTypeReferenceNode(value.finalName, args) as TypeOfMaybe<I>;
201 // }
202 // if (typeof value === 'string') {
203 // return ts.factory.createTypeReferenceNode(value, args) as TypeOfMaybe<I>;
204 // }
205 // if (typeof value === 'boolean') {
206 // const literal = value ? ts.factory.createTrue() : ts.factory.createFalse();
207 // return ts.factory.createLiteralTypeNode(literal) as TypeOfMaybe<I>;
208 // }
209 // if (typeof value === 'number') {
210 // return ts.factory.createLiteralTypeNode(
211 // ts.factory.createNumericLiteral(value),
212 // ) as TypeOfMaybe<I>;
213 // }
214 // if (value instanceof Array) {
215 // return value.map((item) => this.$type(item, args)) as TypeOfMaybe<I>;
216 // }
217 // return this.unwrap(value as any) as TypeOfMaybe<I>;
218 // }
219
220 private _name?: Ref<NodeName>;
221
222 /** Unwraps nested nodes into raw Python AST. */
223 private unwrap<I>(value: I): I extends PyDsl<infer N> ? N : I {
224 return (isNode(value) ? value.toAst() : value) as I extends PyDsl<infer N> ? N : I;
225 }
226}
227
228type NodeOfMaybe<I> = undefined extends I
229 ? NodeOf<NonNullable<FromRef<I>>> | undefined
230 : NodeOf<FromRef<I>>;
231
232type NodeOf<I> =
233 I extends ReadonlyArray<infer U>
234 ? ReadonlyArray<U extends PyDsl<infer N> ? N : U>
235 : I extends string
236 ? py.Expression
237 : I extends PyDsl<infer N>
238 ? N
239 : I extends py.Node
240 ? I
241 : never;
242
243export type MaybePyDsl<T> =
244 T extends PyDsl<infer U> ? U | PyDsl<U> : T extends py.Node ? T | PyDsl<T> : never;
245
246// export abstract class TypePyDsl<
247// T extends
248// | ts.LiteralTypeNode
249// | ts.QualifiedName
250// | ts.TypeElement
251// | ts.TypeNode
252// | ts.TypeParameterDeclaration = ts.TypeNode,
253// > extends PyDsl<T> {}
254
255// type TypeOfMaybe<I> = undefined extends I
256// ? TypeOf<NonNullable<FromRef<I>>> | undefined
257// : TypeOf<FromRef<I>>;
258
259// type TypeOf<I> =
260// I extends ReadonlyArray<infer U>
261// ? ReadonlyArray<TypeOf<U>>
262// : I extends string
263// ? ts.TypeNode
264// : I extends boolean
265// ? ts.LiteralTypeNode
266// : I extends PyDsl<infer N>
267// ? N
268// : I extends ts.TypeNode
269// ? I
270// : never;