fork of hey-api/openapi-ts because I need some additional things
1import type { AnalysisContext, NodeName } from '@hey-api/codegen-core';
2import { isSymbol } from '@hey-api/codegen-core';
3
4import { py } from '../../ts-python';
5import { type MaybePyDsl, PyDsl } from '../base';
6import { NewlinePyDsl } from '../layout/newline';
7import { DecoratorMixin } from '../mixins/decorator';
8import { DocMixin } from '../mixins/doc';
9import { LayoutMixin } from '../mixins/layout';
10import { safeRuntimeName } from '../utils/name';
11
12type Body = Array<MaybePyDsl<py.Statement>>;
13
14const Mixed = DecoratorMixin(DocMixin(LayoutMixin(PyDsl<py.ClassDeclaration>)));
15
16export class ClassPyDsl extends Mixed {
17 readonly '~dsl' = 'ClassPyDsl';
18 override readonly nameSanitizer = safeRuntimeName;
19
20 protected baseClasses: Array<NodeName> = [];
21 protected body: Body = [];
22
23 constructor(name: NodeName) {
24 super();
25 this.name.set(name);
26 if (isSymbol(name)) {
27 name.setKind('class');
28 }
29 }
30
31 override analyze(ctx: AnalysisContext): void {
32 super.analyze(ctx);
33 for (const baseClass of this.baseClasses) {
34 ctx.analyze(baseClass);
35 }
36 ctx.analyze(this.name);
37 ctx.pushScope();
38 try {
39 for (const item of this.body) {
40 ctx.analyze(item);
41 }
42 } finally {
43 ctx.popScope();
44 }
45 }
46
47 /** Returns true when all required builder calls are present. */
48 get isValid(): boolean {
49 return this.missingRequiredCalls().length === 0;
50 }
51
52 /** Returns true if the class has any members. */
53 get hasBody(): boolean {
54 return this.body.length > 0;
55 }
56
57 /** Adds one or more class members (fields, methods, etc.). */
58 do(...items: Body): this {
59 this.body.push(...items);
60 return this;
61 }
62
63 /** Records base classes to extend from. */
64 extends(...baseClass: ReadonlyArray<NodeName>): this {
65 this.baseClasses.push(...baseClass);
66 return this;
67 }
68
69 /** Inserts an empty line between members for formatting. */
70 newline(): this {
71 this.body.push(new NewlinePyDsl());
72 return this;
73 }
74
75 override toAst(): py.ClassDeclaration {
76 this.$validate();
77 // const uniqueClasses: Array<py.Expression> = [];
78
79 // for (const base of baseClass) {
80 // let expr: py.Expression;
81 // if (typeof base === 'string' || base instanceof PyDsl) {
82 // expr = this.$node(base) as py.Expression;
83 // } else if (isSymbol(base)) {
84 // expr = py.factory.createIdentifier(base.finalName);
85 // }
86
87 // // Avoid duplicates by checking if already added
88 // const exists = uniqueClasses.some((existing) => {
89 // const existingExpr = this.$node(existing) as py.Expression;
90 // return existingExpr === expr;
91 // });
92
93 // if (!exists) {
94 // uniqueClasses.push(expr);
95 // }
96 // }
97
98 return py.factory.createClassDeclaration(
99 this.name.toString(),
100 this.$node(this.body) as ReadonlyArray<py.Statement>,
101 this.$decorators(),
102 this.baseClasses.map((c) => this.$node(c)),
103 this.$docs(),
104 );
105 }
106
107 $validate(): asserts this {
108 const missing = this.missingRequiredCalls();
109 if (missing.length === 0) return;
110 throw new Error(`Class declaration missing ${missing.join(' and ')}`);
111 }
112
113 private missingRequiredCalls(): ReadonlyArray<string> {
114 const missing: Array<string> = [];
115 if (!this.name.toString()) missing.push('name');
116 return missing;
117 }
118}