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