// TODO: symbol should be protected, but needs to be public to satisfy types import type { AnalysisContext, File, FromRef, Language, Node, NodeName, NodeNameSanitizer, NodeRelationship, NodeScope, Ref, Symbol, } from '@hey-api/codegen-core'; import { fromRef, isNode, isRef, isSymbol, nodeBrand, ref } from '@hey-api/codegen-core'; import type { AnyString } from '@hey-api/types'; import { py } from '../ts-python'; import type { AccessOptions } from './utils/context'; export abstract class PyDsl implements Node { // eslint-disable-next-line @typescript-eslint/no-unused-vars analyze(_: AnalysisContext): void {} clone(): this { const cloned = Object.create(Object.getPrototypeOf(this)); Object.assign(cloned, this); return cloned; } exported?: boolean; file?: File; get name(): Node['name'] { return { ...this._name, set: (value) => { this._name = ref(value); if (isSymbol(value)) { value.setNode(this); } }, toString: () => (this._name ? this.$name(this._name) : ''), } as Node['name']; } readonly nameSanitizer?: NodeNameSanitizer; language: Language = 'python'; parent?: Node; root: boolean = false; scope?: NodeScope = 'value'; structuralChildren?: Map; structuralParents?: Map; symbol?: Symbol; toAst(): T { return undefined as unknown as T; } readonly '~brand' = nodeBrand; /** Branding property to identify the DSL class at runtime. */ abstract readonly '~dsl': AnyString; /** Conditionally applies a callback to this builder. */ $if( this: T, value: V, ifTrue: (self: T, v: Exclude) => R | void, ifFalse?: (self: T, v: Extract) => R | void, ): R | T; $if( this: T, value: V, ifTrue: (v: Exclude) => R | void, ifFalse?: (v: Extract) => R | void, ): R | T; $if( this: T, value: V, ifTrue: () => R | void, ifFalse?: () => R | void, ): R | T; $if( this: T, value: V, ifTrue: any, ifFalse?: any, ): R | T { if (value) { // Try calling with (self, value), then (value), then () let result: R | void | undefined; try { result = ifTrue?.(this, value as Exclude); } catch { // ignore and try other signatures } if (result === undefined) { try { result = ifTrue?.(value as Exclude); } catch { // ignore and try zero-arg } } if (result === undefined) { try { result = ifTrue?.(); } catch { // swallow } } return (result ?? this) as R | T; } if (ifFalse) { let result: R | void | undefined; try { result = ifFalse?.(this, value as Extract); } catch { // ignore } if (result === undefined) { try { result = ifFalse?.(value as Extract); } catch { // ignore } } if (result === undefined) { try { result = ifFalse?.(); } catch { // ignore } } return (result ?? this) as R | T; } return this; } /** Access patterns for this node. */ toAccessNode?( node: this, options: AccessOptions, ctx: { /** The full chain. */ chain: ReadonlyArray; /** Position in the chain (0 = root). */ index: number; /** Is this the leaf node? */ isLeaf: boolean; /** Is this the root node? */ isRoot: boolean; /** Total length of the chain. */ length: number; }, ): PyDsl | undefined; protected $maybeId( expr: T, ): T extends string ? py.Identifier : T { return (typeof expr === 'string' ? py.factory.createIdentifier(expr) : expr) as T extends string ? py.Identifier : T; } protected $name(name: Ref): string { const value = fromRef(name); if (isSymbol(value)) { try { return value.finalName; } catch { return value.name; } } return String(value); } protected $node(value: I): NodeOfMaybe { if (value === undefined) { return undefined as NodeOfMaybe; } // @ts-expect-error if (isRef(value)) value = fromRef(value); if (isSymbol(value)) { return this.$maybeId(value.finalName) as NodeOfMaybe; } if (typeof value === 'string') { return this.$maybeId(value) as NodeOfMaybe; } if (value instanceof Array) { return value.map((item) => { if (isRef(item)) item = fromRef(item); return this.unwrap(item); }) as NodeOfMaybe; } return this.unwrap(value as any) as NodeOfMaybe; } // protected $type(value: I, args?: ReadonlyArray): TypeOfMaybe { // if (value === undefined) { // return undefined as TypeOfMaybe; // } // // @ts-expect-error // if (isRef(value)) value = fromRef(value); // if (isSymbol(value)) { // return ts.factory.createTypeReferenceNode(value.finalName, args) as TypeOfMaybe; // } // if (typeof value === 'string') { // return ts.factory.createTypeReferenceNode(value, args) as TypeOfMaybe; // } // if (typeof value === 'boolean') { // const literal = value ? ts.factory.createTrue() : ts.factory.createFalse(); // return ts.factory.createLiteralTypeNode(literal) as TypeOfMaybe; // } // if (typeof value === 'number') { // return ts.factory.createLiteralTypeNode( // ts.factory.createNumericLiteral(value), // ) as TypeOfMaybe; // } // if (value instanceof Array) { // return value.map((item) => this.$type(item, args)) as TypeOfMaybe; // } // return this.unwrap(value as any) as TypeOfMaybe; // } private _name?: Ref; /** Unwraps nested nodes into raw Python AST. */ private unwrap(value: I): I extends PyDsl ? N : I { return (isNode(value) ? value.toAst() : value) as I extends PyDsl ? N : I; } } type NodeOfMaybe = undefined extends I ? NodeOf>> | undefined : NodeOf>; type NodeOf = I extends ReadonlyArray ? ReadonlyArray ? N : U> : I extends string ? py.Expression : I extends PyDsl ? N : I extends py.Node ? I : never; export type MaybePyDsl = T extends PyDsl ? U | PyDsl : T extends py.Node ? T | PyDsl : never; // export abstract class TypePyDsl< // T extends // | ts.LiteralTypeNode // | ts.QualifiedName // | ts.TypeElement // | ts.TypeNode // | ts.TypeParameterDeclaration = ts.TypeNode, // > extends PyDsl {} // type TypeOfMaybe = undefined extends I // ? TypeOf>> | undefined // : TypeOf>; // type TypeOf = // I extends ReadonlyArray // ? ReadonlyArray> // : I extends string // ? ts.TypeNode // : I extends boolean // ? ts.LiteralTypeNode // : I extends PyDsl // ? N // : I extends ts.TypeNode // ? I // : never;