fork of hey-api/openapi-ts because I need some additional things
at feat/skip-token 118 lines 3.4 kB view raw
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}