fork of hey-api/openapi-ts because I need some additional things
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: port operation classes function

Lubos b7330971 71025a37

+866 -656
+7 -7
dev/openapi-ts.config.ts
··· 43 43 // 'circular.yaml', 44 44 // 'dutchie.json', 45 45 // 'enum-names-values.yaml', 46 - // 'full.yaml', 47 - 'integer-formats.yaml', 46 + 'full.yaml', 47 + // 'integer-formats.yaml', 48 48 // 'invalid', 49 49 // 'object-property-names.yaml', 50 50 // 'openai.yaml', ··· 54 54 // 'sdk-instance.yaml', 55 55 // 'sdk-method-class-conflict.yaml', 56 56 // 'sdk-nested-classes.yaml', 57 - 'sdk-nested-conflict.yaml', 57 + // 'sdk-nested-conflict.yaml', 58 58 // 'string-with-format.yaml', 59 59 // 'transformers.json', 60 60 // 'transformers-recursive.json', ··· 240 240 { 241 241 // baseUrl: false, 242 242 // exportFromIndex: true, 243 - name: '@hey-api/client-nuxt', 243 + // name: '@hey-api/client-nuxt', 244 244 // runtimeConfigPath: path.resolve(__dirname, 'hey-api.ts'), 245 245 // runtimeConfigPath: './src/hey-api.ts', 246 246 // strictBaseUrl: true, ··· 285 285 // fields.unwrap('path') 286 286 // }, 287 287 // include... 288 - // instance: 'Root', 289 - methodNameBuilder: '{{name}}Methods', 288 + instance: 'Root', 289 + methodNameBuilder: '{{name}}', 290 290 name: '@hey-api/sdk', 291 291 // operationId: false, 292 292 // paramsStructure: 'flat', ··· 349 349 // name: '{{name}}MO', 350 350 // name: 'options', 351 351 }, 352 - // name: '@tanstack/react-query', 352 + name: '@tanstack/react-query', 353 353 queryKeys: { 354 354 // name: '{{name}}QK', 355 355 // name: 'options',
+4 -4
packages/codegen-core/src/files/file.ts
··· 10 10 import type { Renderer } from '../renderer'; 11 11 import type { IFileIn } from './types'; 12 12 13 - export class File { 13 + export class File<Node extends INode = INode> { 14 14 /** 15 15 * Exports from this file. 16 16 */ ··· 42 42 /** 43 43 * Syntax nodes contained in this file. 44 44 */ 45 - private _nodes: Array<INode> = []; 45 + private _nodes: Array<Node> = []; 46 46 /** 47 47 * Renderer assigned to this file. 48 48 */ ··· 142 142 /** 143 143 * Syntax nodes contained in this file. 144 144 */ 145 - get nodes(): ReadonlyArray<INode> { 145 + get nodes(): ReadonlyArray<Node> { 146 146 return [...this._nodes]; 147 147 } 148 148 ··· 170 170 /** 171 171 * Add a syntax node to the file. 172 172 */ 173 - addNode(node: INode): void { 173 + addNode(node: Node): void { 174 174 this._nodes.push(node); 175 175 node.file = this; 176 176 }
+10 -1
packages/codegen-core/src/index.ts
··· 21 21 NameConflictResolvers, 22 22 } from './languages/types'; 23 23 export type { AstContext } from './nodes/context'; 24 - export type { INode as Node } from './nodes/node'; 24 + export type { 25 + AccessPatternContext, 26 + AccessPatternOptions, 27 + INode as Node, 28 + NodeName, 29 + NodeNameSanitizer, 30 + NodeRole, 31 + NodeScope, 32 + StructuralRelationship, 33 + } from './nodes/node'; 25 34 export type { IOutput as Output } from './output'; 26 35 export { 27 36 simpleNameConflictResolver,
+8 -4
packages/codegen-core/src/nodes/context.d.ts
··· 1 - import type { INode } from './node'; 1 + import type { Symbol } from '../symbols/symbol'; 2 + import type { AccessPatternOptions, INode } from './node'; 2 3 3 4 /** 4 5 * Context passed to `.toAst()` methods. 5 6 */ 6 - export type AstContext = { 7 + export interface AstContext { 7 8 /** 8 9 * Returns the canonical node for accessing the provided node. 9 10 */ 10 - getAccess<T extends INode>(node: T): T; 11 - }; 11 + getAccess<T = unknown>( 12 + node: INode | Symbol, 13 + options?: AccessPatternOptions, 14 + ): T; 15 + }
+61 -4
packages/codegen-core/src/nodes/node.d.ts
··· 1 1 import type { File } from '../files/file'; 2 2 import type { Language } from '../languages/types'; 3 3 import type { IAnalysisContext } from '../planner/types'; 4 + import type { Ref } from '../refs/types'; 4 5 import type { Symbol } from '../symbols/symbol'; 5 6 import type { AstContext } from './context'; 6 7 8 + export type AccessContext = 'docs' | 'runtime'; 9 + 10 + export interface AccessPatternContext<Node extends INode = INode> { 11 + /** The full chain. */ 12 + chain: ReadonlyArray<Node>; 13 + /** Position in the chain (0 = root). */ 14 + index: number; 15 + /** Is this the leaf node? */ 16 + isLeaf: boolean; 17 + /** Is this the root node? */ 18 + isRoot: boolean; 19 + /** Total length of the chain. */ 20 + length: number; 21 + } 22 + 23 + export interface AccessPatternOptions { 24 + /** The access context. */ 25 + context: AccessContext; 26 + } 27 + 28 + export type MaybeRef<T> = T | Ref<T>; 29 + 30 + export type NodeName = MaybeRef<Symbol | string | number>; 31 + 32 + export type NodeNameSanitizer = (name: string) => string; 33 + 34 + export type NodeRole = 35 + | 'accessor' 36 + | 'container' 37 + | 'control-flow' 38 + | 'expression' 39 + | 'literal'; 40 + 41 + export type NodeScope = 'type' | 'value'; 42 + 43 + export type StructuralRelationship = 'container' | 'reference'; 44 + 7 45 export interface INode<T = unknown> { 46 + /** Custom access pattern for this node. */ 47 + accessPattern?( 48 + node: this, 49 + options: AccessPatternOptions, 50 + ctx: AccessPatternContext<this>, 51 + ): unknown | undefined; 8 52 /** Perform semantic analysis. */ 9 53 analyze(ctx: IAnalysisContext): void; 10 54 /** Whether this node is exported from its file. */ ··· 13 57 file?: File; 14 58 /** The programming language associated with this node */ 15 59 language: Language; 16 - /** Parent node in the syntax tree. */ 17 - parent?: INode; 18 - /** Root node of the syntax tree. */ 19 - root?: INode; 60 + /** The display name of this node. */ 61 + readonly name: Ref<NodeName> & { 62 + set(value: NodeName): void; 63 + toString(): string; 64 + }; 65 + /** Optional function to sanitize the node name. */ 66 + readonly nameSanitizer?: NodeNameSanitizer; 67 + /** The role of this node within the structure. */ 68 + role?: NodeRole; 69 + /** Whether this node is a root node in the file. */ 70 + root?: boolean; 71 + /** The scope of this node. */ 72 + scope?: NodeScope; 73 + /** Semantic children in the structure hierarchy. */ 74 + structuralChildren?: Map<INode, StructuralRelationship>; 75 + /** Semantic parents in the structure hierarchy. */ 76 + structuralParents?: Map<INode, StructuralRelationship>; 20 77 /** The symbol associated with this node. */ 21 78 symbol?: Symbol; 22 79 /** Convert this node into AST representation. */
+70 -9
packages/codegen-core/src/planner/analyzer.ts
··· 1 1 import { isNodeRef, isSymbolRef } from '../guards'; 2 - import type { INode } from '../nodes/node'; 2 + import type { INode, StructuralRelationship } from '../nodes/node'; 3 3 import { fromRef, isRef, ref } from '../refs/refs'; 4 4 import type { Ref } from '../refs/types'; 5 5 import type { Symbol } from '../symbols/symbol'; ··· 8 8 import type { IAnalysisContext, Input } from './types'; 9 9 10 10 export class AnalysisContext implements IAnalysisContext { 11 + /** 12 + * Stack of parent nodes during analysis. 13 + * 14 + * The top of the stack is the current semantic container. 15 + */ 16 + private _parentStack: Array<INode> = []; 17 + 11 18 scope: Scope; 12 19 scopes: Scope = createScope(); 13 20 symbol?: Symbol; 14 21 15 - constructor(symbol?: Symbol) { 22 + constructor(node: INode) { 23 + this._parentStack.push(node); 16 24 this.scope = this.scopes; 17 - this.symbol = symbol; 25 + this.symbol = node.symbol; 26 + } 27 + 28 + /** 29 + * Get the current semantic parent (top of stack). 30 + */ 31 + get currentParent(): INode | undefined { 32 + return this._parentStack[this._parentStack.length - 1]; 33 + } 34 + 35 + /** 36 + * Register a child node under the current parent. 37 + */ 38 + addChild( 39 + child: INode, 40 + relationship: StructuralRelationship = 'container', 41 + ): void { 42 + const parent = this.currentParent; 43 + if (!parent) return; 44 + 45 + if (!parent.structuralChildren) { 46 + parent.structuralChildren = new Map(); 47 + } 48 + parent.structuralChildren.set(child, relationship); 49 + 50 + if (!child.structuralParents) { 51 + child.structuralParents = new Map(); 52 + } 53 + child.structuralParents.set(parent, relationship); 18 54 } 19 55 20 56 addDependency(symbol: Ref<Symbol>): void { ··· 24 60 } 25 61 26 62 analyze(input: Input): void { 27 - const v = isRef(input) ? input : ref(input); 28 - if (isSymbolRef(v)) { 29 - this.addDependency(v); 30 - } else if (isNodeRef(v)) { 31 - fromRef(v).analyze(this); 63 + const value = isRef(input) ? input : ref(input); 64 + if (isSymbolRef(value)) { 65 + const symbol = fromRef(value); 66 + // avoid adding self as child 67 + if (symbol.node && this.currentParent !== symbol.node) { 68 + this.addChild(symbol.node, 'reference'); 69 + } 70 + this.addDependency(value); 71 + } else if (isNodeRef(value)) { 72 + const node = fromRef(value); 73 + this.addChild(node, 'container'); 74 + this.pushParent(node); 75 + node.analyze(this); 76 + this.popParent(); 32 77 } 33 78 } 34 79 ··· 53 98 return names; 54 99 } 55 100 101 + /** 102 + * Pop the current semantic parent. 103 + * Call this when exiting a container node. 104 + */ 105 + popParent(): void { 106 + this._parentStack.pop(); 107 + } 108 + 56 109 popScope(): void { 57 110 this.scope = this.scope.parent ?? this.scope; 58 111 } 59 112 113 + /** 114 + * Push a node as the current semantic parent. 115 + */ 116 + pushParent(node: INode): void { 117 + this._parentStack.push(node); 118 + } 119 + 60 120 pushScope(): void { 61 121 const scope = createScope({ parent: this.scope }); 62 122 this.scope.children.push(scope); ··· 86 146 const cached = this.nodeCache.get(node); 87 147 if (cached) return cached; 88 148 89 - const ctx = new AnalysisContext(node.symbol); 149 + node.root = true; 150 + const ctx = new AnalysisContext(node); 90 151 node.analyze(ctx); 91 152 92 153 this.nodeCache.set(node, ctx);
+3 -7
packages/codegen-core/src/planner/planner.ts
··· 113 113 if (finalPath) { 114 114 file.setFinalPath(path.resolve(this.project.root, finalPath)); 115 115 } 116 - const ctx: Omit<RenderContext, 'astContext'> = { 117 - file, 118 - meta, 119 - project: this.project, 120 - }; 116 + const ctx: RenderContext = { file, meta, project: this.project }; 121 117 const renderer = this.project.renderers.find((r) => r.supports(ctx)); 122 118 if (renderer) file.setRenderer(renderer); 123 119 } ··· 401 397 if (this.cacheResolvedNames.has(symbol.id)) return; 402 398 403 399 const baseName = symbol.name; 404 - let finalName = symbol.nameSanitizer?.(baseName) ?? baseName; 400 + let finalName = symbol.node?.nameSanitizer?.(baseName) ?? baseName; 405 401 let attempt = 1; 406 402 407 403 const localNames = ctx.localNames(scope); ··· 420 416 throw new Error(`Unresolvable name conflict: ${symbol.toString()}`); 421 417 } 422 418 423 - finalName = symbol.nameSanitizer?.(resolvedName) ?? resolvedName; 419 + finalName = symbol.node?.nameSanitizer?.(resolvedName) ?? resolvedName; 424 420 attempt = attempt + 1; 425 421 } 426 422
+1 -12
packages/codegen-core/src/project/project.ts
··· 5 5 import { defaultExtensions } from '../languages/extensions'; 6 6 import { defaultNameConflictResolvers } from '../languages/resolvers'; 7 7 import type { Extensions, NameConflictResolvers } from '../languages/types'; 8 - import type { AstContext } from '../nodes/context'; 9 8 import { NodeRegistry } from '../nodes/registry'; 10 9 import type { IOutput } from '../output'; 11 10 import { Planner } from '../planner/planner'; ··· 61 60 render(meta?: IProjectRenderMeta): ReadonlyArray<IOutput> { 62 61 new Planner(this).plan(meta); 63 62 const files: Array<IOutput> = []; 64 - const astContext: AstContext = { 65 - getAccess(node) { 66 - return node; 67 - }, 68 - }; 69 63 for (const file of this.files.registered()) { 70 64 if (file.finalPath && file.renderer) { 71 - const content = file.renderer.render({ 72 - astContext, 73 - file, 74 - meta, 75 - project: this, 76 - }); 65 + const content = file.renderer.render({ file, meta, project: this }); 77 66 files.push({ content, path: file.finalPath }); 78 67 } 79 68 }
+12 -4
packages/codegen-core/src/refs/refs.ts
··· 1 - import type { FromRefs, Ref, Refs } from './types'; 1 + import type { FromRef, FromRefs, Ref, Refs } from './types'; 2 2 3 3 /** 4 4 * Wraps a single value in a Ref object. 5 + * 6 + * If the value is already a Ref, returns it as-is (idempotent). 5 7 * 6 8 * @example 7 9 * ```ts 8 10 * const r = ref(123); // { '~ref': 123 } 9 11 * console.log(r['~ref']); // 123 12 + * 13 + * const r2 = ref(r); // { '~ref': 123 } (not double-wrapped) 10 14 * ``` 11 15 */ 12 - export const ref = <T>(value: T): Ref<T> => ({ '~ref': value }); 16 + export const ref = <T>(value: T): Ref<T> => { 17 + if (isRef(value)) { 18 + return value as Ref<T>; 19 + } 20 + return { '~ref': value } as Ref<T>; 21 + }; 13 22 14 23 /** 15 24 * Converts a plain object to an object of Refs (deep, per property). ··· 42 51 */ 43 52 export const fromRef = <T extends Ref<unknown> | undefined>( 44 53 ref: T, 45 - ): T extends Ref<infer U> ? U : undefined => 46 - ref?.['~ref'] as T extends Ref<infer U> ? U : undefined; 54 + ): FromRef<T> => ref?.['~ref'] as FromRef<T>; 47 55 48 56 /** 49 57 * Converts an object of Refs back to a plain object (unwraps all refs).
+3 -3
packages/codegen-core/src/refs/types.d.ts
··· 8 8 * console.log(num['~ref']); // 42 9 9 * ``` 10 10 */ 11 - export type Ref<T> = { '~ref': T }; 11 + export type Ref<T> = T extends { ['~ref']: unknown } ? T : { '~ref': T }; 12 12 13 13 /** 14 14 * Maps every property of `T` to a `Ref` of that property. ··· 33 33 * type N = FromRef<{ '~ref': number }>; // number 34 34 * ``` 35 35 */ 36 - export type FromRef<T> = T extends Ref<infer V> ? V : T; 36 + export type FromRef<T> = T extends { '~ref': infer U } ? U : T; 37 37 38 38 /** 39 39 * Maps every property of a Ref-wrapped object back to its plain value. ··· 46 46 * ``` 47 47 */ 48 48 export type FromRefs<T> = { 49 - [K in keyof T]: T[K] extends Ref<infer V> ? V : T[K]; 49 + [K in keyof T]: T[K] extends Ref<infer U> ? U : T[K]; 50 50 };
+4 -8
packages/codegen-core/src/renderer.d.ts
··· 1 1 import type { IProjectRenderMeta } from './extensions'; 2 2 import type { File } from './files/file'; 3 - import type { AstContext } from './nodes/context'; 3 + import type { INode } from './nodes/node'; 4 4 import type { IProject } from './project/types'; 5 5 6 - export interface RenderContext { 7 - /** 8 - * The context passed to `.toAst()` methods. 9 - */ 10 - astContext: AstContext; 6 + export interface RenderContext<Node extends INode = INode> { 11 7 /** 12 8 * The current file. 13 9 */ 14 - file: File; 10 + file: File<Node>; 15 11 /** 16 12 * Arbitrary metadata. 17 13 */ ··· 26 22 /** Renders the given file. */ 27 23 render(ctx: RenderContext): string; 28 24 /** Returns whether this renderer can render the given file. */ 29 - supports(ctx: Omit<RenderContext, 'astContext'>): boolean; 25 + supports(ctx: RenderContext): boolean; 30 26 }
+6 -34
packages/codegen-core/src/symbols/symbol.ts
··· 3 3 import type { ISymbolMeta } from '../extensions'; 4 4 import type { File } from '../files/file'; 5 5 import type { INode } from '../nodes/node'; 6 - import type { 7 - BindingKind, 8 - ISymbolIn, 9 - SymbolKind, 10 - SymbolNameSanitizer, 11 - } from './types'; 6 + import type { BindingKind, ISymbolIn, SymbolKind } from './types'; 12 7 13 - export class Symbol { 8 + export class Symbol<Node extends INode = INode> { 14 9 /** 15 10 * Canonical symbol this stub resolves to, if any. 16 11 * ··· 79 74 */ 80 75 private _name: string; 81 76 /** 82 - * Optional function to sanitize the symbol name. 83 - * 84 - * @default undefined 85 - */ 86 - private _nameSanitizer?: SymbolNameSanitizer; 87 - /** 88 77 * Node that defines this symbol. 89 78 */ 90 - private _node?: INode; 79 + private _node?: Node; 91 80 92 81 /** Brand used for identifying symbols. */ 93 82 readonly '~brand' = symbolBrand; ··· 202 191 } 203 192 204 193 /** 205 - * Optional function to sanitize the symbol name. 206 - */ 207 - get nameSanitizer(): SymbolNameSanitizer | undefined { 208 - return this.canonical._nameSanitizer; 209 - } 210 - 211 - /** 212 194 * Read‑only accessor for the defining node. 213 195 */ 214 - get node(): INode | undefined { 215 - return this.canonical._node; 196 + get node(): Node | undefined { 197 + return this.canonical._node as Node | undefined; 216 198 } 217 199 218 200 /** ··· 308 290 } 309 291 310 292 /** 311 - * Sets a custom function to sanitize the symbol's name. 312 - * 313 - * @param fn — The name sanitizer function to apply. 314 - */ 315 - setNameSanitizer(fn: SymbolNameSanitizer): void { 316 - this.assertCanonical(); 317 - this._nameSanitizer = fn; 318 - } 319 - 320 - /** 321 293 * Binds the node that defines this symbol. 322 294 * 323 295 * This may only be set once. 324 296 */ 325 - setNode(node: INode): void { 297 + setNode(node: Node): void { 326 298 this.assertCanonical(); 327 299 if (this._node && this._node !== node) { 328 300 const message = `Symbol ${this.canonical.toString()} is already bound to a different node.`;
-2
packages/codegen-core/src/symbols/types.d.ts
··· 14 14 | 'type' 15 15 | 'var'; 16 16 17 - export type SymbolNameSanitizer = (name: string) => string; 18 - 19 17 export type ISymbolIn = { 20 18 /** 21 19 * Array of file names (without extensions) from which this symbol is re-exported.
+79 -71
packages/openapi-ts/src/plugins/@hey-api/sdk/model/resource.ts
··· 120 120 const { node, symbol: symbolClass } = this.classToNode(plugin); 121 121 122 122 if (this.isRoot && plugin.config.instance) { 123 - node.do(...this.rootClassToNode(symbolClass, dependencies, plugin)); 123 + this.enrichRootClass({ 124 + dependencies, 125 + node, 126 + plugin, 127 + symbol: symbolClass, 128 + }); 124 129 } 125 130 126 131 this.operations.forEach((event, index) => { ··· 286 291 return { node, symbol }; 287 292 } 288 293 294 + private enrichRootClass(args: { 295 + dependencies: Array<ReturnType<typeof $.class>>; 296 + node: ReturnType<typeof $.class>; 297 + plugin: HeyApiSdkPlugin['Instance']; 298 + symbol: Symbol; 299 + }): void { 300 + const { dependencies, node, plugin, symbol } = args; 301 + const symbolClient = plugin.symbol('HeyApiClient', { 302 + meta: { 303 + category: 'utility', 304 + resource: 'class', 305 + resourceId: 'HeyApiClient', 306 + tool: 'sdk', 307 + }, 308 + }); 309 + dependencies.push(createClientClass({ plugin, symbol: symbolClient })); 310 + const symbolRegistry = plugin.symbol('HeyApiRegistry', { 311 + meta: { 312 + category: 'utility', 313 + resource: 'class', 314 + resourceId: 'HeyApiRegistry', 315 + tool: 'sdk', 316 + }, 317 + }); 318 + dependencies.push( 319 + createRegistryClass({ 320 + plugin, 321 + sdkSymbol: symbol, 322 + symbol: symbolRegistry, 323 + }), 324 + ); 325 + const isClientRequired = 326 + !plugin.config.client || !plugin.getSymbol({ category: 'client' }); 327 + const registry = plugin.symbol('__registry'); 328 + node.accessPattern = (node) => 329 + $(node.name).attr(registry).attr('get').call(); 330 + node.do( 331 + $.field(registry, (f) => 332 + f 333 + .public() 334 + .static() 335 + .readonly() 336 + .assign($.new(symbolRegistry).generic(symbol)), 337 + ), 338 + $.newline(), 339 + $.init((i) => 340 + i 341 + .param('args', (p) => 342 + p.required(isClientRequired).type( 343 + $.type 344 + .object() 345 + .prop('client', (p) => 346 + p.required(isClientRequired).type( 347 + plugin.referenceSymbol({ 348 + category: 'external', 349 + resource: 'client.Client', 350 + }), 351 + ), 352 + ) 353 + .prop('key', (p) => p.optional().type('string')), 354 + ), 355 + ) 356 + .do( 357 + $('super').call('args'), 358 + $(symbol) 359 + .attr(registry) 360 + .attr('set') 361 + .call('this', $('args').attr('key').required(isClientRequired)), 362 + ), 363 + ), 364 + ); 365 + } 366 + 289 367 private implementFn< 290 368 T extends ReturnType<typeof $.func | typeof $.method>, 291 369 >(args: { ··· 351 429 ) 352 430 .params(...opParameters.parameters) 353 431 .do(...statements) as T; 354 - } 355 - 356 - private rootClassToNode( 357 - symbol: Symbol, 358 - dependencies: Array<ReturnType<typeof $.class>>, 359 - plugin: HeyApiSdkPlugin['Instance'], 360 - ): ReadonlyArray< 361 - ReturnType<typeof $.field | typeof $.init | typeof $.newline> 362 - > { 363 - const symbolClient = plugin.symbol('HeyApiClient', { 364 - meta: { 365 - category: 'utility', 366 - resource: 'class', 367 - resourceId: 'HeyApiClient', 368 - tool: 'sdk', 369 - }, 370 - }); 371 - dependencies.push(createClientClass({ plugin, symbol: symbolClient })); 372 - const symbolRegistry = plugin.symbol('HeyApiRegistry', { 373 - meta: { 374 - category: 'utility', 375 - resource: 'class', 376 - resourceId: 'HeyApiRegistry', 377 - tool: 'sdk', 378 - }, 379 - }); 380 - dependencies.push( 381 - createRegistryClass({ 382 - plugin, 383 - sdkSymbol: symbol, 384 - symbol: symbolRegistry, 385 - }), 386 - ); 387 - const isClientRequired = 388 - !plugin.config.client || !plugin.getSymbol({ category: 'client' }); 389 - return [ 390 - $.field('__registry', (f) => 391 - f 392 - .public() 393 - .static() 394 - .readonly() 395 - .assign($.new(symbolRegistry).generic(symbol)), 396 - ), 397 - $.newline(), 398 - $.init((i) => 399 - i 400 - .param('args', (p) => 401 - p.required(isClientRequired).type( 402 - $.type 403 - .object() 404 - .prop('client', (p) => 405 - p.required(isClientRequired).type( 406 - plugin.referenceSymbol({ 407 - category: 'external', 408 - resource: 'client.Client', 409 - }), 410 - ), 411 - ) 412 - .prop('key', (p) => p.optional().type('string')), 413 - ), 414 - ) 415 - .do( 416 - $('super').call('args'), 417 - $(symbol) 418 - .attr('__registry') 419 - .attr('set') 420 - .call('this', $('args').attr('key').required(isClientRequired)), 421 - ), 422 - ), 423 - ]; 424 432 } 425 433 }
+5 -6
packages/openapi-ts/src/plugins/@hey-api/sdk/model/structure.ts
··· 7 7 private _flat: boolean; 8 8 /** Name of the SDK. If empty, we fallback to operation tags. */ 9 9 private _name: string; 10 - 11 10 /** Root resources mapped by their names. */ 12 - roots: Map<string, SdkResourceModel> = new Map(); 11 + private _roots: Map<string, SdkResourceModel> = new Map(); 13 12 14 13 constructor( 15 14 name: string, ··· 52 51 * @returns The root resource instance 53 52 */ 54 53 root(name: string): SdkResourceModel { 55 - if (!this.roots.has(name)) { 56 - this.roots.set(name, new SdkResourceModel(name)); 54 + if (!this._roots.has(name)) { 55 + this._roots.set(name, new SdkResourceModel(name)); 57 56 } 58 - return this.roots.get(name)!; 57 + return this._roots.get(name)!; 59 58 } 60 59 61 60 /** ··· 64 63 * Yields all resources in the structure. 65 64 */ 66 65 *walk(): Generator<SdkResourceModel> { 67 - for (const resource of this.roots.values()) { 66 + for (const resource of this._roots.values()) { 68 67 yield* resource.walk(); 69 68 } 70 69 }
-2
packages/openapi-ts/src/plugins/@hey-api/sdk/shared/class.ts
··· 4 4 5 5 import type { HeyApiSdkPlugin } from '../types'; 6 6 7 - export const registryName = '__registry'; 8 - 9 7 export const createRegistryClass = ({ 10 8 plugin, 11 9 sdkSymbol,
+9 -4
packages/openapi-ts/src/plugins/@pinia/colada/mutationOptions.ts
··· 12 12 export const createMutationOptions = ({ 13 13 operation, 14 14 plugin, 15 - queryFn, 16 15 }: { 17 16 operation: IR.OperationObject; 18 17 plugin: PiniaColadaPlugin['Instance']; 19 - queryFn: ReturnType<typeof $.expr | typeof $.call | typeof $.attr>; 20 18 }): void => { 21 19 const symbolMutationOptionsType = plugin.referenceSymbol({ 22 20 category: 'external', ··· 31 29 const options = plugin.symbol('options'); 32 30 const fnOptions = plugin.symbol('vars'); 33 31 34 - const awaitSdkFn = $.lazy(() => 35 - $(queryFn) 32 + const awaitSdkFn = $.lazy((ctx) => 33 + ctx 34 + .getAccess<ReturnType<typeof $>>( 35 + plugin.referenceSymbol({ 36 + category: 'sdk', 37 + resource: 'operation', 38 + resourceId: operation.id, 39 + }), 40 + ) 36 41 .call( 37 42 $.object() 38 43 .pretty()
+9 -4
packages/openapi-ts/src/plugins/@pinia/colada/queryOptions.ts
··· 23 23 export const createQueryOptions = ({ 24 24 operation, 25 25 plugin, 26 - queryFn, 27 26 }: { 28 27 operation: IR.OperationObject; 29 28 plugin: PiniaColadaPlugin['Instance']; 30 - queryFn: ReturnType<typeof $.expr | typeof $.call | typeof $.attr>; 31 29 }): void => { 32 30 if (hasOperationSse({ operation })) { 33 31 return; ··· 88 86 const client = getClientPlugin(plugin.context.config); 89 87 const isNuxtClient = client.name === '@hey-api/client-nuxt'; 90 88 const typeData = getPublicTypeData({ isNuxtClient, operation, plugin }); 91 - const awaitSdkFn = $.lazy(() => 92 - $(queryFn) 89 + const awaitSdkFn = $.lazy((ctx) => 90 + ctx 91 + .getAccess<ReturnType<typeof $>>( 92 + plugin.referenceSymbol({ 93 + category: 'sdk', 94 + resource: 'operation', 95 + resourceId: operation.id, 96 + }), 97 + ) 93 98 .call( 94 99 $.object() 95 100 .spread(optionsParamName)
+7 -50
packages/openapi-ts/src/plugins/@pinia/colada/v0/plugin.ts
··· 1 - import { registryName } from '~/plugins/@hey-api/sdk/shared/class'; 2 - import { operationClasses } from '~/plugins/@hey-api/sdk/shared/operation'; 3 - import { $ } from '~/ts-dsl'; 4 - import { toCase } from '~/utils/to-case'; 5 - 6 1 import { createMutationOptions } from '../mutationOptions'; 7 2 import { createQueryOptions } from '../queryOptions'; 8 3 import type { PiniaColadaPlugin } from '../types'; 9 4 10 5 export const handlerV0: PiniaColadaPlugin['Handler'] = ({ plugin }) => { 11 - plugin.registerSymbol({ 6 + plugin.symbol('defineQueryOptions', { 12 7 external: plugin.name, 13 8 meta: { 14 9 category: 'external', 15 10 resource: `${plugin.name}.defineQueryOptions`, 16 11 }, 17 - name: 'defineQueryOptions', 18 12 }); 19 - plugin.registerSymbol({ 13 + plugin.symbol('UseMutationOptions', { 20 14 external: plugin.name, 21 15 kind: 'type', 22 16 meta: { 23 17 category: 'external', 24 18 resource: `${plugin.name}.UseMutationOptions`, 25 19 }, 26 - name: 'UseMutationOptions', 27 20 }); 28 - plugin.registerSymbol({ 21 + plugin.symbol('UseQueryOptions', { 29 22 external: plugin.name, 30 23 kind: 'type', 31 24 meta: { 32 25 category: 'external', 33 26 resource: `${plugin.name}.UseQueryOptions`, 34 27 }, 35 - name: 'UseQueryOptions', 36 28 }); 37 - plugin.registerSymbol({ 29 + plugin.symbol('_JSONValue', { 38 30 external: plugin.name, 39 31 kind: 'type', 40 32 meta: { 41 33 category: 'external', 42 34 resource: `${plugin.name}._JSONValue`, 43 35 }, 44 - name: '_JSONValue', 45 36 }); 46 - plugin.registerSymbol({ 37 + plugin.symbol('AxiosError', { 47 38 external: 'axios', 48 39 kind: 'type', 49 40 meta: { 50 41 category: 'external', 51 42 resource: 'axios.AxiosError', 52 43 }, 53 - name: 'AxiosError', 54 44 }); 55 - 56 - const sdkPlugin = plugin.getPluginOrThrow('@hey-api/sdk'); 57 45 58 46 plugin.forEach( 59 47 'operation', 60 48 ({ operation }) => { 61 - const classes = sdkPlugin.config.asClass 62 - ? operationClasses({ operation, plugin: sdkPlugin }) 63 - : undefined; 64 - const entry = classes ? classes.values().next().value : undefined; 65 - // TODO: this should use class graph to determine correct path string 66 - // as it's really easy to break once we change the class casing 67 - let queryFn: ReturnType<typeof $.expr | typeof $.call | typeof $.attr>; 68 - if (entry) { 69 - const symbolClass = plugin.referenceSymbol({ 70 - category: 'utility', 71 - resource: 'class', 72 - resourceId: entry.path[0], 73 - tool: 'sdk', 74 - }); 75 - queryFn = $(symbolClass).$if(sdkPlugin.config.instance, (e) => 76 - e.attr(registryName).attr('get').call(), 77 - ); 78 - for (const className of entry.path.slice(1)) { 79 - const cls = toCase(className, 'camelCase'); 80 - queryFn = queryFn.attr(cls); 81 - } 82 - queryFn = queryFn.attr(entry.methodName); 83 - } else { 84 - const symbol = plugin.referenceSymbol({ 85 - category: 'sdk', 86 - resource: 'operation', 87 - resourceId: operation.id, 88 - }); 89 - queryFn = $(symbol); 90 - } 91 - 92 49 if (plugin.hooks.operation.isQuery(operation)) { 93 50 if (plugin.config.queryOptions.enabled) { 94 - createQueryOptions({ operation, plugin, queryFn }); 51 + createQueryOptions({ operation, plugin }); 95 52 } 96 53 } 97 54 98 55 if (plugin.hooks.operation.isMutation(operation)) { 99 56 if (plugin.config.mutationOptions.enabled) { 100 - createMutationOptions({ operation, plugin, queryFn }); 57 + createMutationOptions({ operation, plugin }); 101 58 } 102 59 } 103 60 },
+9 -4
packages/openapi-ts/src/plugins/@tanstack/query-core/v5/infiniteQueryOptions.ts
··· 106 106 export const createInfiniteQueryOptions = ({ 107 107 operation, 108 108 plugin, 109 - queryFn, 110 109 }: { 111 110 operation: IR.OperationObject; 112 111 plugin: PluginInstance; 113 - queryFn: ReturnType<typeof $.expr | typeof $.call | typeof $.attr>; 114 112 }): void => { 115 113 const pagination = operationPagination({ 116 114 context: plugin.context, ··· 198 196 }); 199 197 plugin.node(node); 200 198 201 - const awaitSdkFn = $.lazy(() => 202 - $(queryFn) 199 + const awaitSdkFn = $.lazy((ctx) => 200 + ctx 201 + .getAccess<ReturnType<typeof $>>( 202 + plugin.referenceSymbol({ 203 + category: 'sdk', 204 + resource: 'operation', 205 + resourceId: operation.id, 206 + }), 207 + ) 203 208 .call( 204 209 $.object() 205 210 .spread('options')
+9 -4
packages/openapi-ts/src/plugins/@tanstack/query-core/v5/mutationOptions.ts
··· 11 11 export const createMutationOptions = ({ 12 12 operation, 13 13 plugin, 14 - queryFn, 15 14 }: { 16 15 operation: IR.OperationObject; 17 16 plugin: PluginInstance; 18 - queryFn: ReturnType<typeof $.expr | typeof $.call | typeof $.attr>; 19 17 }): void => { 20 18 const symbolMutationOptionsType = plugin.referenceSymbol({ 21 19 category: 'external', ··· 30 28 31 29 const fnOptions = 'fnOptions'; 32 30 33 - const awaitSdkFn = $.lazy(() => 34 - $(queryFn) 31 + const awaitSdkFn = $.lazy((ctx) => 32 + ctx 33 + .getAccess<ReturnType<typeof $>>( 34 + plugin.referenceSymbol({ 35 + category: 'sdk', 36 + resource: 'operation', 37 + resourceId: operation.id, 38 + }), 39 + ) 35 40 .call( 36 41 $.object() 37 42 .spread('options')
+10 -55
packages/openapi-ts/src/plugins/@tanstack/query-core/v5/plugin.ts
··· 1 - import { registryName } from '~/plugins/@hey-api/sdk/shared/class'; 2 - import { operationClasses } from '~/plugins/@hey-api/sdk/shared/operation'; 3 - import { $ } from '~/ts-dsl'; 4 - import { toCase } from '~/utils/to-case'; 5 - 6 1 import type { PluginHandler } from '../types'; 7 2 import { createInfiniteQueryOptions } from './infiniteQueryOptions'; 8 3 import { createMutationOptions } from './mutationOptions'; ··· 10 5 import { createUseQuery } from './useQuery'; 11 6 12 7 export const handlerV5: PluginHandler = ({ plugin }) => { 13 - plugin.registerSymbol({ 8 + plugin.symbol('DefaultError', { 14 9 external: plugin.name, 15 10 kind: 'type', 16 11 meta: { 17 12 category: 'external', 18 13 resource: `${plugin.name}.DefaultError`, 19 14 }, 20 - name: 'DefaultError', 21 15 }); 22 - plugin.registerSymbol({ 16 + plugin.symbol('InfiniteData', { 23 17 external: plugin.name, 24 18 kind: 'type', 25 19 meta: { 26 20 category: 'external', 27 21 resource: `${plugin.name}.InfiniteData`, 28 22 }, 29 - name: 'InfiniteData', 30 23 }); 31 24 const mutationsType = 32 25 plugin.name === '@tanstack/angular-query-experimental' || ··· 34 27 plugin.name === '@tanstack/solid-query' 35 28 ? 'MutationOptions' 36 29 : 'UseMutationOptions'; 37 - plugin.registerSymbol({ 30 + plugin.symbol(mutationsType, { 38 31 external: plugin.name, 39 32 kind: 'type', 40 33 meta: { 41 34 category: 'external', 42 35 resource: `${plugin.name}.MutationOptions`, 43 36 }, 44 - name: mutationsType, 45 37 }); 46 - plugin.registerSymbol({ 38 + plugin.symbol('infiniteQueryOptions', { 47 39 external: plugin.name, 48 40 meta: { 49 41 category: 'external', 50 42 resource: `${plugin.name}.infiniteQueryOptions`, 51 43 }, 52 - name: 'infiniteQueryOptions', 53 44 }); 54 - plugin.registerSymbol({ 45 + plugin.symbol('queryOptions', { 55 46 external: plugin.name, 56 47 meta: { 57 48 category: 'external', 58 49 resource: `${plugin.name}.queryOptions`, 59 50 }, 60 - name: 'queryOptions', 61 51 }); 62 - plugin.registerSymbol({ 52 + plugin.symbol('useQuery', { 63 53 external: plugin.name, 64 54 meta: { 65 55 category: 'external', 66 56 resource: `${plugin.name}.useQuery`, 67 57 }, 68 - name: 'useQuery', 69 58 }); 70 - plugin.registerSymbol({ 59 + plugin.symbol('AxiosError', { 71 60 external: 'axios', 72 61 kind: 'type', 73 62 meta: { 74 63 category: 'external', 75 64 resource: 'axios.AxiosError', 76 65 }, 77 - name: 'AxiosError', 78 66 }); 79 67 80 - const sdkPlugin = plugin.getPluginOrThrow('@hey-api/sdk'); 81 - 82 68 plugin.forEach( 83 69 'operation', 84 70 ({ operation }) => { 85 - const classes = sdkPlugin.config.asClass 86 - ? operationClasses({ operation, plugin: sdkPlugin }) 87 - : undefined; 88 - const entry = classes ? classes.values().next().value : undefined; 89 - // TODO: this should use class graph to determine correct path string 90 - // as it's really easy to break once we change the class casing 91 - let queryFn: ReturnType<typeof $.expr | typeof $.call | typeof $.attr>; 92 - if (entry) { 93 - const symbolClass = plugin.referenceSymbol({ 94 - category: 'utility', 95 - resource: 'class', 96 - resourceId: entry.path[0], 97 - tool: 'sdk', 98 - }); 99 - queryFn = $(symbolClass).$if(sdkPlugin.config.instance, (e) => 100 - e.attr(registryName).attr('get').call(), 101 - ); 102 - for (const className of entry.path.slice(1)) { 103 - const cls = toCase(className, 'camelCase'); 104 - queryFn = queryFn.attr(cls); 105 - } 106 - queryFn = queryFn.attr(entry.methodName); 107 - } else { 108 - const symbol = plugin.referenceSymbol({ 109 - category: 'sdk', 110 - resource: 'operation', 111 - resourceId: operation.id, 112 - }); 113 - queryFn = $(symbol); 114 - } 115 - 116 71 if (plugin.hooks.operation.isQuery(operation)) { 117 72 if (plugin.config.queryOptions.enabled) { 118 - createQueryOptions({ operation, plugin, queryFn }); 73 + createQueryOptions({ operation, plugin }); 119 74 } 120 75 121 76 if (plugin.config.infiniteQueryOptions.enabled) { 122 - createInfiniteQueryOptions({ operation, plugin, queryFn }); 77 + createInfiniteQueryOptions({ operation, plugin }); 123 78 } 124 79 125 80 if ('useQuery' in plugin.config && plugin.config.useQuery.enabled) { ··· 129 84 130 85 if (plugin.hooks.operation.isMutation(operation)) { 131 86 if (plugin.config.mutationOptions.enabled) { 132 - createMutationOptions({ operation, plugin, queryFn }); 87 + createMutationOptions({ operation, plugin }); 133 88 } 134 89 } 135 90 },
+9 -4
packages/openapi-ts/src/plugins/@tanstack/query-core/v5/queryOptions.ts
··· 22 22 export const createQueryOptions = ({ 23 23 operation, 24 24 plugin, 25 - queryFn, 26 25 }: { 27 26 operation: IR.OperationObject; 28 27 plugin: PluginInstance; 29 - queryFn: ReturnType<typeof $.expr | typeof $.call | typeof $.attr>; 30 28 }): void => { 31 29 if (hasOperationSse({ operation })) { 32 30 return; ··· 69 67 70 68 const typeResponse = useTypeResponse({ operation, plugin }); 71 69 72 - const awaitSdkFn = $.lazy(() => 73 - $(queryFn) 70 + const awaitSdkFn = $.lazy((ctx) => 71 + ctx 72 + .getAccess<ReturnType<typeof $>>( 73 + plugin.referenceSymbol({ 74 + category: 'sdk', 75 + resource: 'operation', 76 + resourceId: operation.id, 77 + }), 78 + ) 74 79 .call( 75 80 $.object() 76 81 .spread(optionsParamName)
+12 -8
packages/openapi-ts/src/plugins/shared/utils/instance.ts
··· 25 25 import type { Hooks } from '~/parser/types/hooks'; 26 26 import type { Plugin } from '~/plugins'; 27 27 import type { PluginConfigMap } from '~/plugins/config'; 28 + import type { TsDsl } from '~/ts-dsl'; 28 29 import { jsonPointerToPath } from '~/utils/ref'; 29 30 30 31 import type { BaseEvent, WalkEvent } from '../types/instance'; ··· 334 335 return result as T extends number ? void : number; 335 336 } 336 337 337 - querySymbol(filter: SymbolMeta): Symbol | undefined { 338 - return this.gen.symbols.query(filter)[0]; 338 + querySymbol(filter: SymbolMeta): Symbol<TsDsl> | undefined { 339 + return this.gen.symbols.query(filter)[0] as Symbol<TsDsl> | undefined; 339 340 } 340 341 341 - referenceSymbol(meta: SymbolMeta): Symbol { 342 - return this.gen.symbols.reference(meta); 342 + referenceSymbol(meta: SymbolMeta): Symbol<TsDsl> { 343 + return this.gen.symbols.reference(meta) as Symbol<TsDsl>; 343 344 } 344 345 345 346 /** 346 347 * @deprecated use `plugin.symbol()` instead 347 348 */ 348 - registerSymbol(symbol: SymbolIn): Symbol { 349 - return this.symbol(symbol.name, symbol); 349 + registerSymbol(symbol: SymbolIn): Symbol<TsDsl> { 350 + return this.symbol(symbol.name, symbol) as Symbol<TsDsl>; 350 351 } 351 352 352 353 /** ··· 362 363 } 363 364 } 364 365 365 - symbol(name: SymbolIn['name'], symbol?: Omit<SymbolIn, 'name'>): Symbol { 366 + symbol( 367 + name: SymbolIn['name'], 368 + symbol?: Omit<SymbolIn, 'name'>, 369 + ): Symbol<TsDsl> { 366 370 const symbolIn: SymbolIn = { 367 371 ...symbol, 368 372 exportFrom: ··· 386 390 for (const hook of this.eventHooks['symbol:register:after']) { 387 391 hook({ plugin: this, symbol: symbolOut }); 388 392 } 389 - return symbolOut; 393 + return symbolOut as Symbol<TsDsl>; 390 394 } 391 395 392 396 /**
+3 -41
packages/openapi-ts/src/plugins/swr/v2/plugin.ts
··· 1 - import { registryName } from '~/plugins/@hey-api/sdk/shared/class'; 2 - import { operationClasses } from '~/plugins/@hey-api/sdk/shared/operation'; 3 - import { $ } from '~/ts-dsl'; 4 - import { toCase } from '~/utils/to-case'; 5 - 6 1 import type { SwrPlugin } from '../types'; 7 2 import { createUseSwr } from './useSwr'; 8 3 ··· 18 13 name: 'useSWR', 19 14 }); 20 15 21 - const sdkPlugin = plugin.getPluginOrThrow('@hey-api/sdk'); 22 - 23 16 plugin.forEach( 24 17 'operation', 25 18 ({ operation }) => { 26 - const classes = sdkPlugin.config.asClass 27 - ? operationClasses({ operation, plugin: sdkPlugin }) 28 - : undefined; 29 - const entry = classes ? classes.values().next().value : undefined; 30 - // TODO: this should use class graph to determine correct path string 31 - // as it's really easy to break once we change the class casing 32 - let queryFn: ReturnType<typeof $.expr | typeof $.call | typeof $.attr>; 33 - if (entry) { 34 - const symbolClass = plugin.referenceSymbol({ 35 - category: 'utility', 36 - resource: 'class', 37 - resourceId: entry.path[0], 38 - tool: 'sdk', 39 - }); 40 - queryFn = $(symbolClass).$if(sdkPlugin.config.instance, (e) => 41 - e.attr(registryName).attr('get').call(), 42 - ); 43 - for (const className of entry.path.slice(1)) { 44 - const cls = toCase(className, 'camelCase'); 45 - queryFn = queryFn.attr(cls); 46 - } 47 - queryFn = queryFn.attr(entry.methodName); 48 - } else { 49 - const symbol = plugin.referenceSymbol({ 50 - category: 'sdk', 51 - resource: 'operation', 52 - resourceId: operation.id, 53 - }); 54 - queryFn = $(symbol); 55 - } 56 - 57 19 if (plugin.hooks.operation.isQuery(operation)) { 58 20 // if (plugin.config.queryOptions.enabled) { 59 - // createQueryOptions({ operation, plugin, queryFn }); 21 + // createQueryOptions({ operation, plugin }); 60 22 // } 61 23 62 24 // if (plugin.config.infiniteQueryOptions.enabled) { 63 - // createInfiniteQueryOptions({ operation, plugin, queryFn }); 25 + // createInfiniteQueryOptions({ operation, plugin }); 64 26 // } 65 27 66 28 if (plugin.config.useSwr.enabled) { 67 - createUseSwr({ operation, plugin, queryFn }); 29 + createUseSwr({ operation, plugin }); 68 30 } 69 31 } 70 32 },
+9 -4
packages/openapi-ts/src/plugins/swr/v2/useSwr.ts
··· 12 12 export const createUseSwr = ({ 13 13 operation, 14 14 plugin, 15 - queryFn, 16 15 }: { 17 16 operation: IR.OperationObject; 18 17 plugin: SwrPlugin['Instance']; 19 - queryFn: ReturnType<typeof $.expr | typeof $.call | typeof $.attr>; 20 18 }): void => { 21 19 if (hasOperationSse({ operation })) { 22 20 return; ··· 33 31 }), 34 32 }); 35 33 36 - const awaitSdkFn = $.lazy(() => 37 - $(queryFn) 34 + const awaitSdkFn = $.lazy((ctx) => 35 + ctx 36 + .getAccess<ReturnType<typeof $>>( 37 + plugin.referenceSymbol({ 38 + category: 'sdk', 39 + resource: 'operation', 40 + resourceId: operation.id, 41 + }), 42 + ) 38 43 .call($.object().prop('throwOnError', $.literal(true))) 39 44 .await(), 40 45 );
+44 -2
packages/openapi-ts/src/ts-dsl/base.ts
··· 1 1 // TODO: symbol should be protected, but needs to be public to satisfy types 2 2 import type { 3 + AccessPatternContext, 4 + AccessPatternOptions, 3 5 AnalysisContext, 4 6 AstContext, 5 7 File, 6 8 FromRef, 7 9 Language, 8 10 Node, 11 + NodeName, 12 + NodeNameSanitizer, 13 + NodeRole, 14 + NodeScope, 15 + Ref, 16 + StructuralRelationship, 9 17 Symbol, 10 18 } from '@hey-api/codegen-core'; 11 19 import { ··· 14 22 isRef, 15 23 isSymbol, 16 24 nodeBrand, 25 + ref, 17 26 } from '@hey-api/codegen-core'; 18 27 import ts from 'typescript'; 19 28 20 29 export type MaybeArray<T> = T | ReadonlyArray<T>; 21 30 22 31 export abstract class TsDsl<T extends ts.Node = ts.Node> implements Node<T> { 32 + accessPattern?( 33 + node: this, 34 + options: AccessPatternOptions, 35 + ctx: AccessPatternContext<this>, 36 + ): unknown | undefined; 23 37 // eslint-disable-next-line @typescript-eslint/no-unused-vars 24 38 analyze(_: AnalysisContext): void {} 25 39 exported?: boolean; 26 40 file?: File; 41 + get name(): Node['name'] { 42 + return { 43 + ...this._name, 44 + set: (value) => { 45 + this._name = ref(value); 46 + }, 47 + toString: () => (this._name ? this.$name(this._name) : ''), 48 + } as Node['name']; 49 + } 50 + readonly nameSanitizer?: NodeNameSanitizer; 27 51 language: Language = 'typescript'; 28 52 parent?: Node; 29 - root?: Node; 53 + role?: NodeRole; 54 + root: boolean = false; 55 + scope?: NodeScope = 'value'; 56 + structuralChildren?: Map<TsDsl, StructuralRelationship>; 57 + structuralParents?: Map<TsDsl, StructuralRelationship>; 30 58 symbol?: Symbol; 31 59 // eslint-disable-next-line @typescript-eslint/no-unused-vars 32 60 toAst(_: AstContext): T { ··· 35 63 readonly '~brand' = nodeBrand; 36 64 37 65 /** Branding property to identify the DSL class at runtime. */ 38 - abstract readonly '~dsl': string; 66 + abstract readonly '~dsl': string & {}; 39 67 40 68 /** Conditionally applies a callback to this builder. */ 41 69 $if<T extends TsDsl, V, R extends TsDsl = T>( ··· 120 148 ) as T extends string ? ts.Identifier : T; 121 149 } 122 150 151 + protected $name(name: Ref<NodeName>): string { 152 + const value = fromRef(name); 153 + if (isSymbol(value)) { 154 + try { 155 + return value.finalName; 156 + } catch { 157 + return value.name; 158 + } 159 + } 160 + return String(value); 161 + } 162 + 123 163 protected $node<I>(ctx: AstContext, value: I): NodeOfMaybe<I> { 124 164 if (value === undefined) { 125 165 return undefined as NodeOfMaybe<I>; ··· 176 216 } 177 217 return this.unwrap(ctx, value as any) as TypeOfMaybe<I>; 178 218 } 219 + 220 + private _name?: Ref<NodeName>; 179 221 180 222 /** Unwraps nested nodes into raw TypeScript AST. */ 181 223 private unwrap<I>(
+10 -13
packages/openapi-ts/src/ts-dsl/decl/class.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 + NodeName, 5 + NodeRole, 4 6 Ref, 5 - Symbol, 6 7 } from '@hey-api/codegen-core'; 7 8 import { isSymbol, ref } from '@hey-api/codegen-core'; 8 9 import ts from 'typescript'; ··· 15 16 import { AbstractMixin, DefaultMixin, ExportMixin } from '../mixins/modifiers'; 16 17 import { TypeParamsMixin } from '../mixins/type-params'; 17 18 import { safeRuntimeName } from '../utils/name'; 18 - import type { FieldName } from './field'; 19 19 import { FieldTsDsl } from './field'; 20 20 import { InitTsDsl } from './init'; 21 - import type { MethodName } from './method'; 22 21 import { MethodTsDsl } from './method'; 23 22 24 - type Base = Symbol | string; 25 - type Name = Symbol | string; 26 23 type Body = Array<MaybeTsDsl<ts.ClassElement | ts.Node>>; 27 24 28 25 const Mixed = AbstractMixin( ··· 35 32 36 33 export class ClassTsDsl extends Mixed { 37 34 readonly '~dsl' = 'ClassTsDsl'; 35 + override readonly nameSanitizer = safeRuntimeName; 36 + override role?: NodeRole = 'container'; 38 37 39 - protected baseClass?: Ref<Base>; 38 + protected baseClass?: Ref<NodeName>; 40 39 protected body: Body = []; 41 - protected name: Ref<Name>; 42 40 43 - constructor(name: Name) { 41 + constructor(name: NodeName) { 44 42 super(); 45 - this.name = ref(name); 43 + this.name.set(name); 46 44 if (isSymbol(name)) { 47 45 name.setKind('class'); 48 - name.setNameSanitizer(safeRuntimeName); 49 46 name.setNode(this); 50 47 } 51 48 } ··· 76 73 } 77 74 78 75 /** Records a base class to extend from. */ 79 - extends(base?: Base): this { 76 + extends(base?: NodeName): this { 80 77 this.baseClass = base ? ref(base) : undefined; 81 78 return this; 82 79 } 83 80 84 81 /** Adds a class field. */ 85 - field(name: FieldName, fn?: (f: FieldTsDsl) => void): this { 82 + field(name: NodeName, fn?: (f: FieldTsDsl) => void): this { 86 83 const f = new FieldTsDsl(name, fn); 87 84 this.body.push(f); 88 85 return this; ··· 97 94 } 98 95 99 96 /** Adds a class method. */ 100 - method(name: MethodName, fn?: (m: MethodTsDsl) => void): this { 97 + method(name: NodeName, fn?: (m: MethodTsDsl) => void): this { 101 98 const m = new MethodTsDsl(name, fn); 102 99 this.body.push(m); 103 100 return this;
+4 -12
packages/openapi-ts/src/ts-dsl/decl/decorator.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 - Ref, 5 - Symbol, 4 + NodeName, 6 5 } from '@hey-api/codegen-core'; 7 - import { isSymbol, ref } from '@hey-api/codegen-core'; 8 6 import ts from 'typescript'; 9 7 10 8 import type { MaybeTsDsl } from '../base'; ··· 12 10 import { ArgsMixin } from '../mixins/args'; 13 11 import { safeRuntimeName } from '../utils/name'; 14 12 15 - export type DecoratorName = Symbol | string | MaybeTsDsl<ts.Expression>; 16 - 17 13 const Mixed = ArgsMixin(TsDsl<ts.Decorator>); 18 14 19 15 export class DecoratorTsDsl extends Mixed { 20 16 readonly '~dsl' = 'DecoratorTsDsl'; 21 - 22 - protected name: Ref<DecoratorName>; 17 + override readonly nameSanitizer = safeRuntimeName; 23 18 24 19 constructor( 25 - name: DecoratorName, 20 + name: NodeName, 26 21 ...args: ReadonlyArray<string | MaybeTsDsl<ts.Expression>> 27 22 ) { 28 23 super(); 29 - this.name = ref(name); 30 - if (isSymbol(name)) { 31 - name.setNameSanitizer(safeRuntimeName); 32 - } 24 + this.name.set(name); 33 25 this.args(...args); 34 26 } 35 27
+7 -10
packages/openapi-ts/src/ts-dsl/decl/enum.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 - Ref, 5 - Symbol, 4 + NodeName, 6 5 } from '@hey-api/codegen-core'; 7 - import { isSymbol, ref } from '@hey-api/codegen-core'; 6 + import { isSymbol } from '@hey-api/codegen-core'; 8 7 import ts from 'typescript'; 9 8 10 9 import type { MaybeTsDsl } from '../base'; ··· 14 13 import { safeRuntimeName } from '../utils/name'; 15 14 import { EnumMemberTsDsl } from './member'; 16 15 17 - export type EnumName = Symbol | string; 18 16 type Value = string | number | MaybeTsDsl<ts.Expression>; 19 17 type ValueFn = Value | ((m: EnumMemberTsDsl) => void); 20 18 ··· 22 20 23 21 export class EnumTsDsl extends Mixed { 24 22 readonly '~dsl' = 'EnumTsDsl'; 23 + override readonly nameSanitizer = safeRuntimeName; 25 24 26 25 private _members: Array<EnumMemberTsDsl> = []; 27 - private _name: Ref<EnumName>; 28 26 29 - constructor(name: EnumName, fn?: (e: EnumTsDsl) => void) { 27 + constructor(name: NodeName, fn?: (e: EnumTsDsl) => void) { 30 28 super(); 31 - this._name = ref(name); 29 + this.name.set(name); 32 30 if (isSymbol(name)) { 33 31 name.setKind('enum'); 34 - name.setNameSanitizer(safeRuntimeName); 35 32 name.setNode(this); 36 33 } 37 34 fn?.(this); ··· 39 36 40 37 override analyze(ctx: AnalysisContext): void { 41 38 super.analyze(ctx); 42 - ctx.analyze(this._name); 39 + ctx.analyze(this.name); 43 40 ctx.pushScope(); 44 41 try { 45 42 for (const member of this._members) { ··· 66 63 override toAst(ctx: AstContext) { 67 64 const node = ts.factory.createEnumDeclaration( 68 65 this.modifiers, 69 - this.$node(ctx, this._name) as ts.Identifier, 66 + this.$node(ctx, this.name) as ts.Identifier, 70 67 this.$node(ctx, this._members) as ReadonlyArray<ts.EnumMember>, 71 68 ); 72 69 return this.$docs(ctx, node);
+6 -9
packages/openapi-ts/src/ts-dsl/decl/field.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 - Ref, 5 - Symbol, 4 + NodeName, 5 + NodeRole, 6 6 } from '@hey-api/codegen-core'; 7 - import { ref } from '@hey-api/codegen-core'; 8 7 import ts from 'typescript'; 9 8 10 9 import { TsDsl, TypeTsDsl } from '../base'; ··· 20 19 import { OptionalMixin } from '../mixins/optional'; 21 20 import { ValueMixin } from '../mixins/value'; 22 21 import { TokenTsDsl } from '../token'; 23 - import type { TypeExprName } from '../type/expr'; 24 22 import { TypeExprTsDsl } from '../type/expr'; 25 23 26 - export type FieldName = Symbol | string; 27 - export type FieldType = TypeExprName | TypeTsDsl; 24 + export type FieldType = NodeName | TypeTsDsl; 28 25 29 26 const Mixed = DecoratorMixin( 30 27 DocMixin( ··· 44 41 45 42 export class FieldTsDsl extends Mixed { 46 43 readonly '~dsl' = 'FieldTsDsl'; 44 + override role?: NodeRole = 'accessor'; 47 45 48 - protected name: Ref<FieldName>; 49 46 protected _type?: TypeTsDsl; 50 47 51 - constructor(name: FieldName, fn?: (f: FieldTsDsl) => void) { 48 + constructor(name: NodeName, fn?: (f: FieldTsDsl) => void) { 52 49 super(); 53 - this.name = ref(name); 50 + this.name.set(name); 54 51 fn?.(this); 55 52 } 56 53
+11 -13
packages/openapi-ts/src/ts-dsl/decl/func.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 - Ref, 5 - Symbol, 4 + NodeName, 6 5 } from '@hey-api/codegen-core'; 7 - import { isSymbol, ref } from '@hey-api/codegen-core'; 6 + import { isSymbol } from '@hey-api/codegen-core'; 8 7 import ts from 'typescript'; 9 8 10 9 import { TsDsl } from '../base'; ··· 27 26 import { safeRuntimeName } from '../utils/name'; 28 27 29 28 export type FuncMode = 'arrow' | 'decl' | 'expr'; 30 - export type FuncName = Symbol | string; 31 29 32 30 const Mixed = AbstractMixin( 33 31 AsMixin( ··· 57 55 58 56 class ImplFuncTsDsl<M extends FuncMode = 'arrow'> extends Mixed { 59 57 readonly '~dsl' = 'FuncTsDsl'; 58 + override readonly nameSanitizer = safeRuntimeName; 60 59 61 60 protected mode?: FuncMode; 62 - protected name?: Ref<FuncName>; 63 61 64 62 constructor(); 65 63 constructor(fn: (f: ImplFuncTsDsl<'arrow'>) => void); 66 - constructor(name: FuncName); 67 - constructor(name: FuncName, fn: (f: ImplFuncTsDsl<'decl'>) => void); 64 + constructor(name: NodeName); 65 + constructor(name: NodeName, fn: (f: ImplFuncTsDsl<'decl'>) => void); 68 66 constructor( 69 - name?: FuncName | ((f: ImplFuncTsDsl<'arrow'>) => void), 67 + name?: NodeName | ((f: ImplFuncTsDsl<'arrow'>) => void), 70 68 fn?: (f: ImplFuncTsDsl<'decl'>) => void, 71 69 ) { 72 70 super(); ··· 75 73 name(this as unknown as FuncTsDsl<'arrow'>); 76 74 } else if (name) { 77 75 this.mode = 'decl'; 78 - this.name = ref(name); 76 + this.name.set(name); 79 77 if (isSymbol(name)) { 80 78 name.setKind('function'); 81 - name.setNameSanitizer(safeRuntimeName); 82 79 name.setNode(this); 83 80 } 84 81 fn?.(this as unknown as FuncTsDsl<'decl'>); ··· 124 121 const body = this.$node(ctx, new BlockTsDsl(...this._do).pretty()); 125 122 126 123 if (this.mode === 'decl') { 127 - if (!this.name) throw new Error('Function declaration requires a name'); 124 + const name = this.name.toString(); 125 + if (!name) throw new Error('Function declaration requires a name'); 128 126 const node = ts.factory.createFunctionDeclaration( 129 127 [...this.$decorators(ctx), ...this.modifiers], 130 128 undefined, ··· 169 167 export const FuncTsDsl = ImplFuncTsDsl as { 170 168 new (): FuncTsDsl<'arrow'>; 171 169 new (fn: (f: FuncTsDsl<'arrow'>) => void): FuncTsDsl<'arrow'>; 172 - new (name: string): FuncTsDsl<'decl'>; 173 - new (name: string, fn: (f: FuncTsDsl<'decl'>) => void): FuncTsDsl<'decl'>; 170 + new (name: NodeName): FuncTsDsl<'decl'>; 171 + new (name: NodeName, fn: (f: FuncTsDsl<'decl'>) => void): FuncTsDsl<'decl'>; 174 172 } & typeof ImplFuncTsDsl; 175 173 export type FuncTsDsl<M extends FuncMode = 'arrow'> = ImplFuncTsDsl<M>;
+7 -9
packages/openapi-ts/src/ts-dsl/decl/getter.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 - Ref, 5 - Symbol, 4 + NodeName, 5 + NodeRole, 6 6 } from '@hey-api/codegen-core'; 7 - import { ref } from '@hey-api/codegen-core'; 8 7 import ts from 'typescript'; 9 8 10 9 import { TsDsl } from '../base'; ··· 22 21 import { ParamMixin } from '../mixins/param'; 23 22 import { TypeReturnsMixin } from '../mixins/type-returns'; 24 23 import { BlockTsDsl } from '../stmt/block'; 25 - 26 - export type GetterName = Symbol | string | ts.PropertyName; 24 + import { safeAccessorName } from '../utils/name'; 27 25 28 26 const Mixed = AbstractMixin( 29 27 AsyncMixin( ··· 49 47 50 48 export class GetterTsDsl extends Mixed { 51 49 readonly '~dsl' = 'GetterTsDsl'; 52 - 53 - protected name: Ref<GetterName>; 50 + override readonly nameSanitizer = safeAccessorName; 51 + override role?: NodeRole = 'accessor'; 54 52 55 - constructor(name: GetterName, fn?: (g: GetterTsDsl) => void) { 53 + constructor(name: NodeName, fn?: (g: GetterTsDsl) => void) { 56 54 super(); 57 - this.name = ref(name); 55 + this.name.set(name); 58 56 fn?.(this); 59 57 } 60 58
+8 -5
packages/openapi-ts/src/ts-dsl/decl/member.ts
··· 1 - import type { AnalysisContext, AstContext } from '@hey-api/codegen-core'; 1 + import type { 2 + AnalysisContext, 3 + AstContext, 4 + NodeName, 5 + } from '@hey-api/codegen-core'; 2 6 import ts from 'typescript'; 3 7 4 8 import type { MaybeTsDsl } from '../base'; ··· 14 18 export class EnumMemberTsDsl extends Mixed { 15 19 readonly '~dsl' = 'EnumMemberTsDsl'; 16 20 17 - private _name: string; 18 21 private _value?: Value; 19 22 20 - constructor(name: string, value?: ValueFn) { 23 + constructor(name: NodeName, value?: ValueFn) { 21 24 super(); 22 - this._name = name; 25 + this.name.set(name); 23 26 if (typeof value === 'function') { 24 27 value(this); 25 28 } else { ··· 40 43 41 44 override toAst(ctx: AstContext) { 42 45 const node = ts.factory.createEnumMember( 43 - this.$node(ctx, safeMemberName(this._name)), 46 + this.$node(ctx, safeMemberName(this.name.toString())), 44 47 this.$node(ctx, this._value), 45 48 ); 46 49 return this.$docs(ctx, node);
+11 -9
packages/openapi-ts/src/ts-dsl/decl/method.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 - Ref, 5 - Symbol, 4 + NodeName, 5 + NodeRole, 6 6 } from '@hey-api/codegen-core'; 7 - import { ref } from '@hey-api/codegen-core'; 7 + import { isSymbol } from '@hey-api/codegen-core'; 8 8 import ts from 'typescript'; 9 9 10 10 import { TsDsl } from '../base'; ··· 25 25 import { TypeReturnsMixin } from '../mixins/type-returns'; 26 26 import { BlockTsDsl } from '../stmt/block'; 27 27 import { TokenTsDsl } from '../token'; 28 - 29 - export type MethodName = Symbol | string; 28 + import { safeAccessorName } from '../utils/name'; 30 29 31 30 const Mixed = AbstractMixin( 32 31 AsyncMixin( ··· 56 55 57 56 export class MethodTsDsl extends Mixed { 58 57 readonly '~dsl' = 'MethodTsDsl'; 59 - 60 - protected name: Ref<MethodName>; 58 + override readonly nameSanitizer = safeAccessorName; 59 + override role?: NodeRole = 'accessor'; 61 60 62 - constructor(name: MethodName, fn?: (m: MethodTsDsl) => void) { 61 + constructor(name: NodeName, fn?: (m: MethodTsDsl) => void) { 63 62 super(); 64 - this.name = ref(name); 63 + this.name.set(name); 64 + if (isSymbol(name)) { 65 + name.setNode(this); 66 + } 65 67 fn?.(this); 66 68 } 67 69
+7 -15
packages/openapi-ts/src/ts-dsl/decl/param.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 - Ref, 5 - Symbol, 4 + NodeName, 6 5 } from '@hey-api/codegen-core'; 7 - import { fromRef, isSymbolRef, ref } from '@hey-api/codegen-core'; 8 6 import ts from 'typescript'; 9 7 10 8 import { TsDsl, TypeTsDsl } from '../base'; ··· 15 13 import { TokenTsDsl } from '../token'; 16 14 import { TypeExprTsDsl } from '../type/expr'; 17 15 18 - export type ParamName = Symbol | string; 19 16 export type ParamCtor = ( 20 - name: ParamName | ((p: ParamTsDsl) => void), 17 + name: NodeName | ((p: ParamTsDsl) => void), 21 18 fn?: (p: ParamTsDsl) => void, 22 19 ) => ParamTsDsl; 23 20 ··· 28 25 export class ParamTsDsl extends Mixed { 29 26 readonly '~dsl' = 'ParamTsDsl'; 30 27 31 - protected name?: Ref<ParamName>; 32 28 protected _type?: TypeTsDsl; 33 29 34 30 constructor( 35 - name: ParamName | ((p: ParamTsDsl) => void), 31 + name: NodeName | ((p: ParamTsDsl) => void), 36 32 fn?: (p: ParamTsDsl) => void, 37 33 ) { 38 34 super(); 39 35 if (typeof name === 'function') { 40 36 name(this); 41 37 } else { 42 - this.name = ref(name); 38 + this.name.set(name); 43 39 fn?.(this); 44 40 } 45 41 } ··· 57 53 } 58 54 59 55 override toAst(ctx: AstContext) { 60 - let name: string | ReturnType<typeof this.$pattern> = this.$pattern(ctx); 61 - if (!name && this.name) { 62 - name = isSymbolRef(this.name) 63 - ? fromRef(this.name).finalName 64 - : (fromRef(this.name) as string); 65 - } 66 - if (!name) 56 + const name = this.$pattern(ctx) || this.name.toString(); 57 + if (!name) { 67 58 throw new Error( 68 59 'Param must have either a name or a destructuring pattern', 69 60 ); 61 + } 70 62 return ts.factory.createParameterDeclaration( 71 63 this.$decorators(ctx), 72 64 undefined,
+5 -9
packages/openapi-ts/src/ts-dsl/decl/setter.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 - Ref, 5 - Symbol, 4 + NodeName, 6 5 } from '@hey-api/codegen-core'; 7 - import { ref } from '@hey-api/codegen-core'; 8 6 import ts from 'typescript'; 9 7 10 8 import { TsDsl } from '../base'; ··· 21 19 } from '../mixins/modifiers'; 22 20 import { ParamMixin } from '../mixins/param'; 23 21 import { BlockTsDsl } from '../stmt/block'; 24 - 25 - export type SetterName = Symbol | string | ts.PropertyName; 22 + import { safeAccessorName } from '../utils/name'; 26 23 27 24 const Mixed = AbstractMixin( 28 25 AsyncMixin( ··· 44 41 45 42 export class SetterTsDsl extends Mixed { 46 43 readonly '~dsl' = 'SetterTsDsl'; 47 - 48 - protected name: Ref<SetterName>; 44 + override readonly nameSanitizer = safeAccessorName; 49 45 50 - constructor(name: SetterName, fn?: (s: SetterTsDsl) => void) { 46 + constructor(name: NodeName, fn?: (s: SetterTsDsl) => void) { 51 47 super(); 52 - this.name = ref(name); 48 + this.name.set(name); 53 49 fn?.(this); 54 50 } 55 51
+3 -3
packages/openapi-ts/src/ts-dsl/expr/as.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 + NodeName, 4 5 Ref, 5 - Symbol, 6 6 } from '@hey-api/codegen-core'; 7 7 import { ref } from '@hey-api/codegen-core'; 8 8 import ts from 'typescript'; ··· 13 13 import { ExprMixin } from '../mixins/expr'; 14 14 import { f } from '../utils/factories'; 15 15 16 - export type AsExpr = Symbol | string | MaybeTsDsl<ts.Expression>; 17 - export type AsType = Symbol | string | TypeTsDsl; 16 + export type AsExpr = NodeName | MaybeTsDsl<ts.Expression>; 17 + export type AsType = NodeName | TypeTsDsl; 18 18 export type AsCtor = (expr: AsExpr, type: AsType) => AsTsDsl; 19 19 20 20 const Mixed = AsMixin(ExprMixin(TsDsl<ts.AsExpression>));
+22 -18
packages/openapi-ts/src/ts-dsl/expr/attr.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 + NodeName, 4 5 Ref, 5 - Symbol, 6 6 } from '@hey-api/codegen-core'; 7 - import { fromRef, ref } from '@hey-api/codegen-core'; 7 + import { fromRef, isSymbol, ref } from '@hey-api/codegen-core'; 8 8 import ts from 'typescript'; 9 9 10 10 import type { MaybeTsDsl } from '../base'; ··· 18 18 import { regexp } from '../utils/regexp'; 19 19 import { LiteralTsDsl } from './literal'; 20 20 21 - export type AttrLeft = Symbol | string | MaybeTsDsl<ts.Expression>; 22 - export type AttrRight = Symbol | string | ts.MemberName | number; 23 - export type AttrCtor = (left: AttrLeft, right: AttrRight) => AttrTsDsl; 21 + export type AttrLeft = NodeName | MaybeTsDsl<ts.Expression>; 22 + export type AttrCtor = (left: AttrLeft, right: NodeName) => AttrTsDsl; 24 23 25 24 const Mixed = AsMixin( 26 25 ExprMixin( ··· 36 35 readonly '~dsl' = 'AttrTsDsl'; 37 36 38 37 protected left: Ref<AttrLeft>; 39 - protected right: Ref<AttrRight>; 40 38 41 - constructor(left: AttrLeft, right: AttrRight) { 39 + constructor(left: AttrLeft, right: NodeName) { 42 40 super(); 43 41 this.left = ref(left); 44 - this.right = ref(right); 42 + this.name.set(right); 45 43 } 46 44 47 45 override analyze(ctx: AnalysisContext): void { 48 46 super.analyze(ctx); 49 47 ctx.analyze(this.left); 50 - ctx.analyze(this.right); 48 + ctx.analyze(this.name); 51 49 } 52 50 53 51 override toAst(ctx: AstContext) { 54 52 const leftNode = this.$node(ctx, this.left); 55 53 regexp.typeScriptIdentifier.lastIndex = 0; 56 - if ( 57 - typeof fromRef(this.right) === 'number' || 58 - (typeof fromRef(this.right) === 'string' && 59 - !regexp.typeScriptIdentifier.test(fromRef(this.right) as string)) 60 - ) { 54 + const right = fromRef(this.name); 55 + if (!regexp.typeScriptIdentifier.test(this.name.toString())) { 56 + let value = isSymbol(right) ? right.finalName : right; 57 + if (typeof value === 'string') { 58 + if ( 59 + (value.startsWith("'") && value.endsWith("'")) || 60 + (value.startsWith('"') && value.endsWith('"')) 61 + ) { 62 + value = value.slice(1, -1); 63 + } 64 + } 61 65 if (this._optional) { 62 66 return ts.factory.createElementAccessChain( 63 67 leftNode, 64 68 this.$node(ctx, new TokenTsDsl().questionDot()), 65 - this.$node(ctx, new LiteralTsDsl(fromRef(this.right) as string)), 69 + this.$node(ctx, new LiteralTsDsl(value)), 66 70 ); 67 71 } 68 72 return ts.factory.createElementAccessExpression( 69 73 leftNode, 70 - this.$node(ctx, new LiteralTsDsl(fromRef(this.right) as string)), 74 + this.$node(ctx, new LiteralTsDsl(value)), 71 75 ); 72 76 } 73 77 if (this._optional) { 74 78 return ts.factory.createPropertyAccessChain( 75 79 leftNode, 76 80 this.$node(ctx, new TokenTsDsl().questionDot()), 77 - this.$node(ctx, this.right) as ts.MemberName, 81 + this.$node(ctx, this.name) as ts.MemberName, 78 82 ); 79 83 } 80 84 return ts.factory.createPropertyAccessExpression( 81 85 leftNode, 82 - this.$node(ctx, this.right) as ts.MemberName, 86 + this.$node(ctx, this.name) as ts.MemberName, 83 87 ); 84 88 } 85 89 }
+2 -2
packages/openapi-ts/src/ts-dsl/expr/await.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 + NodeName, 4 5 Ref, 5 - Symbol, 6 6 } from '@hey-api/codegen-core'; 7 7 import { ref } from '@hey-api/codegen-core'; 8 8 import ts from 'typescript'; ··· 12 12 import { ExprMixin } from '../mixins/expr'; 13 13 import { f } from '../utils/factories'; 14 14 15 - export type AwaitExpr = Symbol | string | MaybeTsDsl<ts.Expression>; 15 + export type AwaitExpr = NodeName | MaybeTsDsl<ts.Expression>; 16 16 export type AwaitCtor = (expr: AwaitExpr) => AwaitTsDsl; 17 17 18 18 const Mixed = ExprMixin(TsDsl<ts.AwaitExpression>);
+2 -2
packages/openapi-ts/src/ts-dsl/expr/binary.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 + NodeName, 4 5 Ref, 5 - Symbol, 6 6 } from '@hey-api/codegen-core'; 7 7 import { ref } from '@hey-api/codegen-core'; 8 8 import ts from 'typescript'; ··· 12 12 import { AsMixin } from '../mixins/as'; 13 13 import { ExprMixin } from '../mixins/expr'; 14 14 15 - type Expr = Symbol | string | MaybeTsDsl<ts.Expression>; 15 + type Expr = NodeName | MaybeTsDsl<ts.Expression>; 16 16 type Op = Operator | ts.BinaryOperator; 17 17 type Operator = 18 18 | '!='
+2 -2
packages/openapi-ts/src/ts-dsl/expr/call.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 - Symbol, 4 + NodeName, 5 5 } from '@hey-api/codegen-core'; 6 6 import ts from 'typescript'; 7 7 ··· 14 14 import { f } from '../utils/factories'; 15 15 16 16 export type CallCallee = string | MaybeTsDsl<ts.Expression>; 17 - export type CallArg = Symbol | string | MaybeTsDsl<ts.Expression>; 17 + export type CallArg = NodeName | MaybeTsDsl<ts.Expression>; 18 18 export type CallArgs = ReadonlyArray<CallArg | undefined>; 19 19 export type CallCtor = (callee: CallCallee, ...args: CallArgs) => CallTsDsl; 20 20
+2 -2
packages/openapi-ts/src/ts-dsl/expr/expr.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 + NodeName, 4 5 Ref, 5 - Symbol, 6 6 } from '@hey-api/codegen-core'; 7 7 import { ref } from '@hey-api/codegen-core'; 8 8 import type ts from 'typescript'; ··· 14 14 import { OperatorMixin } from '../mixins/operator'; 15 15 import { TypeExprMixin } from '../mixins/type-expr'; 16 16 17 - type Id = Symbol | string | MaybeTsDsl<ts.Expression>; 17 + type Id = NodeName | MaybeTsDsl<ts.Expression>; 18 18 19 19 const Mixed = AsMixin( 20 20 ExprMixin(OperatorMixin(TypeExprMixin(TsDsl<ts.Expression>))),
+2 -4
packages/openapi-ts/src/ts-dsl/expr/id.ts
··· 8 8 export class IdTsDsl extends Mixed { 9 9 readonly '~dsl' = 'IdTsDsl'; 10 10 11 - protected name: string; 12 - 13 11 constructor(name: string) { 14 12 super(); 15 - this.name = name; 13 + this.name.set(name); 16 14 } 17 15 18 16 override analyze(ctx: AnalysisContext): void { ··· 20 18 } 21 19 22 20 override toAst() { 23 - return ts.factory.createIdentifier(this.name); 21 + return ts.factory.createIdentifier(this.name.toString()); 24 22 } 25 23 }
+10 -1
packages/openapi-ts/src/ts-dsl/expr/literal.ts
··· 7 7 8 8 export type LiteralValue = string | number | boolean | bigint | null; 9 9 10 - const Mixed = AsMixin(TsDsl<ts.LiteralTypeNode['literal']>); 10 + const Mixed = AsMixin( 11 + TsDsl< 12 + | ts.BigIntLiteral 13 + | ts.BooleanLiteral 14 + | ts.NullLiteral 15 + | ts.NumericLiteral 16 + | ts.PrefixUnaryExpression 17 + | ts.StringLiteral 18 + >, 19 + ); 11 20 12 21 export class LiteralTsDsl extends Mixed { 13 22 readonly '~dsl' = 'LiteralTsDsl';
+2 -2
packages/openapi-ts/src/ts-dsl/expr/new.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 + NodeName, 4 5 Ref, 5 - Symbol, 6 6 } from '@hey-api/codegen-core'; 7 7 import { ref } from '@hey-api/codegen-core'; 8 8 import ts from 'typescript'; ··· 13 13 import { ExprMixin } from '../mixins/expr'; 14 14 import { TypeArgsMixin } from '../mixins/type-args'; 15 15 16 - export type NewExpr = Symbol | string | MaybeTsDsl<ts.Expression>; 16 + export type NewExpr = NodeName | MaybeTsDsl<ts.Expression>; 17 17 18 18 const Mixed = ArgsMixin(ExprMixin(TypeArgsMixin(TsDsl<ts.NewExpression>))); 19 19
+3 -3
packages/openapi-ts/src/ts-dsl/expr/object.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 - Symbol, 4 + NodeName, 5 5 } from '@hey-api/codegen-core'; 6 6 import ts from 'typescript'; 7 7 ··· 13 13 import { LayoutMixin } from '../mixins/layout'; 14 14 import { ObjectPropTsDsl } from './prop'; 15 15 16 - type Expr = Symbol | string | MaybeTsDsl<ts.Expression>; 17 - type Stmt = Symbol | string | MaybeTsDsl<ts.Statement>; 16 + type Expr = NodeName | MaybeTsDsl<ts.Expression>; 17 + type Stmt = NodeName | MaybeTsDsl<ts.Statement>; 18 18 type ExprFn = Expr | ((p: ObjectPropTsDsl) => void); 19 19 type StmtFn = Stmt | ((p: ObjectPropTsDsl) => void); 20 20
+5 -9
packages/openapi-ts/src/ts-dsl/expr/prop.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 + NodeName, 4 5 Ref, 5 - Symbol, 6 6 } from '@hey-api/codegen-core'; 7 7 import { ref } from '@hey-api/codegen-core'; 8 8 import ts from 'typescript'; ··· 15 15 import { safePropName } from '../utils/name'; 16 16 import { IdTsDsl } from './id'; 17 17 18 - type Expr = Symbol | string | MaybeTsDsl<ts.Expression>; 19 - type Stmt = Symbol | string | MaybeTsDsl<ts.Statement>; 18 + type Expr = NodeName | MaybeTsDsl<ts.Expression>; 19 + type Stmt = NodeName | MaybeTsDsl<ts.Statement>; 20 20 type Kind = 'computed' | 'getter' | 'prop' | 'setter' | 'spread'; 21 21 22 22 type Meta = ··· 71 71 return this.$docs(ctx, result); 72 72 } 73 73 if (this.meta.kind === 'getter') { 74 - const getter = new GetterTsDsl( 75 - this.$node(ctx, safePropName(this.meta.name)), 76 - ).do(node); 74 + const getter = new GetterTsDsl(this.meta.name).do(node); 77 75 const result = this.$node(ctx, getter); 78 76 return this.$docs(ctx, result); 79 77 } 80 78 if (this.meta.kind === 'setter') { 81 - const setter = new SetterTsDsl( 82 - this.$node(ctx, safePropName(this.meta.name)), 83 - ).do(node); 79 + const setter = new SetterTsDsl(this.meta.name).do(node); 84 80 const result = this.$node(ctx, setter); 85 81 return this.$docs(ctx, result); 86 82 }
+6 -2
packages/openapi-ts/src/ts-dsl/expr/template.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 + NodeName, 5 + NodeRole, 4 6 Ref, 5 - Symbol, 6 7 } from '@hey-api/codegen-core'; 7 8 import { fromRef, isSymbol, ref } from '@hey-api/codegen-core'; 8 9 import ts from 'typescript'; ··· 10 11 import type { MaybeTsDsl } from '../base'; 11 12 import { TsDsl } from '../base'; 12 13 13 - export type TemplatePart = Symbol | string | MaybeTsDsl<ts.Expression>; 14 + export type TemplatePart = NodeName | MaybeTsDsl<ts.Expression>; 14 15 15 16 const Mixed = TsDsl<ts.TemplateExpression | ts.NoSubstitutionTemplateLiteral>; 16 17 17 18 export class TemplateTsDsl extends Mixed { 18 19 readonly '~dsl' = 'TemplateTsDsl'; 20 + override role?: NodeRole = 'literal'; 19 21 20 22 protected parts: Array<Ref<TemplatePart>> = []; 21 23 ··· 59 61 index++; 60 62 } 61 63 normalized.push(merged); 64 + } else if (typeof current === 'number') { 65 + normalized.push(String(current)); 62 66 } else { 63 67 normalized.push(current); 64 68 }
+1
packages/openapi-ts/src/ts-dsl/index.ts
··· 358 358 export type { MaybeTsDsl, TypeTsDsl } from './base'; 359 359 export { TsDsl } from './base'; 360 360 export { TypeScriptRenderer } from './render/typescript'; 361 + export { astContext } from './utils/context'; 361 362 export { keywords } from './utils/keywords'; 362 363 export { regexp } from './utils/regexp'; 363 364 export { reserved } from './utils/reserved';
+2 -2
packages/openapi-ts/src/ts-dsl/mixins/args.ts
··· 2 2 AnalysisContext, 3 3 AstContext, 4 4 Node, 5 + NodeName, 5 6 Ref, 6 - Symbol, 7 7 } from '@hey-api/codegen-core'; 8 8 import { ref } from '@hey-api/codegen-core'; 9 9 import type ts from 'typescript'; ··· 11 11 import type { MaybeTsDsl } from '../base'; 12 12 import type { BaseCtor, MixinCtor } from './types'; 13 13 14 - type Arg = Symbol | string | MaybeTsDsl<ts.Expression>; 14 + type Arg = NodeName | MaybeTsDsl<ts.Expression>; 15 15 16 16 export interface ArgsMethods extends Node { 17 17 /** Renders the arguments into an array of `Expression`s. */
+3 -3
packages/openapi-ts/src/ts-dsl/mixins/decorator.ts
··· 2 2 AnalysisContext, 3 3 AstContext, 4 4 Node, 5 - Symbol, 5 + NodeName, 6 6 } from '@hey-api/codegen-core'; 7 7 import type ts from 'typescript'; 8 8 ··· 15 15 $decorators(ctx: AstContext): ReadonlyArray<ts.Decorator>; 16 16 /** Adds a decorator (e.g. `@sealed({ in: 'root' })`). */ 17 17 decorator( 18 - name: Symbol | string | MaybeTsDsl<ts.Expression>, 18 + name: NodeName | MaybeTsDsl<ts.Expression>, 19 19 ...args: ReadonlyArray<string | MaybeTsDsl<ts.Expression>> 20 20 ): this; 21 21 } ··· 34 34 } 35 35 36 36 protected decorator( 37 - name: Symbol | string | MaybeTsDsl<ts.Expression>, 37 + name: NodeName, 38 38 ...args: ReadonlyArray<string | MaybeTsDsl<ts.Expression>> 39 39 ): this { 40 40 this.decorators.push(new DecoratorTsDsl(name, ...args));
+2 -2
packages/openapi-ts/src/ts-dsl/mixins/operator.ts
··· 1 - import type { AnalysisContext, Node, Symbol } from '@hey-api/codegen-core'; 1 + import type { AnalysisContext, Node, NodeName } from '@hey-api/codegen-core'; 2 2 import type ts from 'typescript'; 3 3 4 4 import type { MaybeTsDsl } from '../base'; 5 5 import { BinaryTsDsl } from '../expr/binary'; 6 6 import type { BaseCtor, MixinCtor } from './types'; 7 7 8 - type Expr = Symbol | string | MaybeTsDsl<ts.Expression>; 8 + type Expr = NodeName | MaybeTsDsl<ts.Expression>; 9 9 10 10 export interface OperatorMethods extends Node { 11 11 /** Logical AND — `this && expr` */
+8 -3
packages/openapi-ts/src/ts-dsl/mixins/param.ts
··· 1 - import type { AnalysisContext, AstContext, Node } from '@hey-api/codegen-core'; 1 + import type { 2 + AnalysisContext, 3 + AstContext, 4 + Node, 5 + NodeName, 6 + } from '@hey-api/codegen-core'; 2 7 import type ts from 'typescript'; 3 8 4 9 import type { MaybeTsDsl } from '../base'; 5 - import type { ParamCtor, ParamName } from '../decl/param'; 10 + import type { ParamCtor } from '../decl/param'; 6 11 import { ParamTsDsl } from '../decl/param'; 7 12 import type { BaseCtor, MixinCtor } from './types'; 8 13 ··· 29 34 } 30 35 31 36 protected param( 32 - name: ParamName | ((p: ParamTsDsl) => void), 37 + name: NodeName | ((p: ParamTsDsl) => void), 33 38 fn?: (p: ParamTsDsl) => void, 34 39 ): this { 35 40 const p = new ParamTsDsl(name, fn);
+2 -2
packages/openapi-ts/src/ts-dsl/mixins/type-args.ts
··· 2 2 AnalysisContext, 3 3 AstContext, 4 4 Node, 5 + NodeName, 5 6 Ref, 6 - Symbol, 7 7 } from '@hey-api/codegen-core'; 8 8 import { ref } from '@hey-api/codegen-core'; 9 9 import type ts from 'typescript'; ··· 11 11 import type { MaybeTsDsl, TypeTsDsl } from '../base'; 12 12 import type { BaseCtor, MixinCtor } from './types'; 13 13 14 - type Arg = Symbol | string | MaybeTsDsl<TypeTsDsl>; 14 + type Arg = NodeName | MaybeTsDsl<TypeTsDsl>; 15 15 16 16 export interface TypeArgsMethods extends Node { 17 17 /** Returns the type arguments as an array of ts.TypeNode nodes. */
+10 -7
packages/openapi-ts/src/ts-dsl/mixins/type-params.ts
··· 2 2 AnalysisContext, 3 3 AstContext, 4 4 Node, 5 - Symbol, 5 + NodeName, 6 6 } from '@hey-api/codegen-core'; 7 - import { isSymbol } from '@hey-api/codegen-core'; 7 + import { isRef, isSymbol } from '@hey-api/codegen-core'; 8 8 import type ts from 'typescript'; 9 9 10 10 import type { MaybeTsDsl } from '../base'; ··· 19 19 /** Adds a single type parameter (e.g. `T` in `Array<T>`). */ 20 20 generic(...args: ConstructorParameters<typeof TypeParamTsDsl>): this; 21 21 /** Adds type parameters (e.g. `Map<string, T>`). */ 22 - generics( 23 - ...args: ReadonlyArray<Symbol | string | MaybeTsDsl<TypeParamTsDsl>> 24 - ): this; 22 + generics(...args: ReadonlyArray<NodeName | MaybeTsDsl<TypeParamTsDsl>>): this; 25 23 } 26 24 27 25 export function TypeParamsMixin<T extends ts.Node, TBase extends BaseCtor<T>>( ··· 46 44 } 47 45 48 46 protected generics( 49 - ...args: ReadonlyArray<Symbol | string | MaybeTsDsl<TypeParamTsDsl>> 47 + ...args: ReadonlyArray<NodeName | MaybeTsDsl<TypeParamTsDsl>> 50 48 ): this { 51 49 for (let arg of args) { 52 - if (typeof arg === 'string' || isSymbol(arg)) { 50 + if ( 51 + typeof arg === 'string' || 52 + typeof arg === 'number' || 53 + isSymbol(arg) || 54 + isRef(arg) 55 + ) { 53 56 arg = new TypeParamTsDsl(arg); 54 57 } 55 58 this._generics.push(arg);
+8 -4
packages/openapi-ts/src/ts-dsl/mixins/type-returns.ts
··· 1 - import type { AnalysisContext, AstContext, Node } from '@hey-api/codegen-core'; 1 + import type { 2 + AnalysisContext, 3 + AstContext, 4 + Node, 5 + NodeName, 6 + } from '@hey-api/codegen-core'; 2 7 import type ts from 'typescript'; 3 8 4 9 import { TypeTsDsl } from '../base'; 5 - import type { TypeExprName } from '../type/expr'; 6 10 import { TypeExprTsDsl } from '../type/expr'; 7 11 import type { BaseCtor, MixinCtor } from './types'; 8 12 ··· 10 14 /** Returns the return type node. */ 11 15 $returns(ctx: AstContext): ts.TypeNode | undefined; 12 16 /** Sets the return type. */ 13 - returns(type: TypeExprName | TypeTsDsl): this; 17 + returns(type: NodeName | TypeTsDsl): this; 14 18 } 15 19 16 20 export function TypeReturnsMixin<T extends ts.Node, TBase extends BaseCtor<T>>( ··· 24 28 ctx.analyze(this._returns); 25 29 } 26 30 27 - protected returns(type: TypeExprName | TypeTsDsl): this { 31 + protected returns(type: NodeName | TypeTsDsl): this { 28 32 this._returns = 29 33 type instanceof TypeTsDsl ? type : new TypeExprTsDsl(type); 30 34 return this;
+5 -10
packages/openapi-ts/src/ts-dsl/render/__tests__/typescript.test.ts
··· 3 3 import ts from 'typescript'; 4 4 import { describe, expect, it } from 'vitest'; 5 5 6 + import { astContext, type TsDsl } from '~/ts-dsl'; 7 + 6 8 import { TypeScriptRenderer } from '../typescript'; 7 9 import type { ModuleExport, ModuleImport } from '../utils'; 8 10 ··· 20 22 const mockCtx = ( 21 23 fileOverrides = {}, 22 24 projectOverrides = {}, 23 - ): RenderContext => ({ 24 - astContext: { 25 - getAccess(node) { 26 - return node; 27 - }, 28 - }, 25 + ): RenderContext<TsDsl> => ({ 29 26 file: mockFile(fileOverrides), 30 27 project: new Project({ 31 28 root: '/root', ··· 47 44 }); 48 45 49 46 it('renderImport() generates named and namespace imports correctly', () => { 50 - const ctx = mockCtx(); 51 47 const group: ModuleImport = { 52 48 imports: [ 53 49 { ··· 61 57 modulePath: 'foo', 62 58 namespaceImport: undefined, 63 59 }; 64 - const node = renderer['renderImport'](ctx, group); 60 + const node = renderer['renderImport'](astContext, group); 65 61 expect(ts.isImportDeclaration(node)).toBe(true); 66 62 }); 67 63 68 64 it('renderExport() generates named and namespace exports correctly', () => { 69 - const ctx = mockCtx(); 70 65 const group: ModuleExport = { 71 66 canExportAll: false, 72 67 exports: [ ··· 81 76 modulePath: 'bar', 82 77 namespaceExport: undefined, 83 78 }; 84 - const node = renderer['renderExport'](ctx, group); 79 + const node = renderer['renderExport'](astContext, group); 85 80 expect(ts.isExportDeclaration(node)).toBe(true); 86 81 }); 87 82 });
+15 -11
packages/openapi-ts/src/ts-dsl/render/typescript.ts
··· 1 - import type { RenderContext, Renderer } from '@hey-api/codegen-core'; 1 + import type { 2 + AstContext, 3 + RenderContext, 4 + Renderer, 5 + } from '@hey-api/codegen-core'; 2 6 import ts from 'typescript'; 3 7 4 - import { $ } from '~/ts-dsl'; 8 + import type { TsDsl } from '~/ts-dsl'; 9 + import { $, astContext } from '~/ts-dsl'; 5 10 6 11 import type { 7 12 ModuleExport, ··· 44 49 this.resolveModuleName = args.resolveModuleName; 45 50 } 46 51 47 - render(ctx: RenderContext): string { 52 + render(ctx: RenderContext<TsDsl>): string { 48 53 let text = ''; 49 54 const header = '// This file is auto-generated by @hey-api/openapi-ts'; 50 55 text += `${header}\n`; ··· 53 58 for (const group of this.getImports(ctx)) { 54 59 if (imports) imports += '\n'; 55 60 for (const imp of group) { 56 - imports += `${nodeToString(this.renderImport(ctx, imp))}\n`; 61 + imports += `${nodeToString(this.renderImport(astContext, imp))}\n`; 57 62 } 58 63 } 59 64 text = `${text}${text && imports ? '\n' : ''}${imports}`; ··· 61 66 let nodes = ''; 62 67 for (const node of ctx.file.nodes) { 63 68 if (nodes) nodes += '\n'; 64 - // @ts-expect-error 65 - nodes += `${nodeToString(node.toAst(ctx.astContext))}\n`; 69 + nodes += `${nodeToString(node.toAst(astContext))}\n`; 66 70 } 67 71 text = `${text}${text && nodes ? '\n' : ''}${nodes}`; 68 72 ··· 70 74 for (const group of this.getExports(ctx)) { 71 75 if ((!exports && nodes) || exports) exports += '\n'; 72 76 for (const exp of group) { 73 - exports += `${nodeToString(this.renderExport(ctx, exp))}\n`; 77 + exports += `${nodeToString(this.renderExport(astContext, exp))}\n`; 74 78 } 75 79 } 76 80 text = `${text}${text && exports ? '\n' : ''}${exports}`; ··· 235 239 } 236 240 237 241 private renderExport( 238 - ctx: RenderContext, 242 + ctx: AstContext, 239 243 group: ModuleExport, 240 244 ): ts.ExportDeclaration { 241 245 const specifiers = group.exports.map((exp) => { ··· 257 261 undefined, 258 262 group.isTypeOnly, 259 263 exportClause, 260 - $.literal(group.modulePath).toAst(ctx.astContext), 264 + $.literal(group.modulePath).toAst(ctx), 261 265 ); 262 266 } 263 267 264 268 private renderImport( 265 - ctx: RenderContext, 269 + ctx: AstContext, 266 270 group: ModuleImport, 267 271 ): ts.ImportDeclaration { 268 272 const specifiers = group.imports.map((imp) => { ··· 285 289 return ts.factory.createImportDeclaration( 286 290 undefined, 287 291 importClause, 288 - $.literal(group.modulePath).toAst(ctx.astContext), 292 + $.literal(group.modulePath).toAst(ctx), 289 293 ); 290 294 } 291 295 }
+2 -2
packages/openapi-ts/src/ts-dsl/stmt/return.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 + NodeName, 4 5 Ref, 5 - Symbol, 6 6 } from '@hey-api/codegen-core'; 7 7 import { ref } from '@hey-api/codegen-core'; 8 8 import ts from 'typescript'; ··· 11 11 import { TsDsl } from '../base'; 12 12 import { f } from '../utils/factories'; 13 13 14 - export type ReturnExpr = Symbol | string | MaybeTsDsl<ts.Expression>; 14 + export type ReturnExpr = NodeName | MaybeTsDsl<ts.Expression>; 15 15 export type ReturnCtor = (expr?: ReturnExpr) => ReturnTsDsl; 16 16 17 17 const Mixed = TsDsl<ts.ReturnStatement>;
+3 -5
packages/openapi-ts/src/ts-dsl/stmt/try.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 - Symbol, 4 + NodeName, 5 5 } from '@hey-api/codegen-core'; 6 6 import ts from 'typescript'; 7 7 ··· 11 11 12 12 const Mixed = TsDsl<ts.TryStatement>; 13 13 14 - type CatchParam = Symbol | string; 15 - 16 14 export class TryTsDsl extends Mixed { 17 15 readonly '~dsl' = 'TryTsDsl'; 18 16 19 17 protected _catch?: Array<DoExpr>; 20 - protected _catchArg?: CatchParam; 18 + protected _catchArg?: NodeName; 21 19 protected _finally?: Array<DoExpr>; 22 20 protected _try?: Array<DoExpr>; 23 21 ··· 65 63 return this; 66 64 } 67 65 68 - catchArg(arg: CatchParam): this { 66 + catchArg(arg: NodeName): this { 69 67 this._catchArg = arg; 70 68 return this; 71 69 }
+5 -9
packages/openapi-ts/src/ts-dsl/stmt/var.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 - Ref, 5 - Symbol, 4 + NodeName, 6 5 } from '@hey-api/codegen-core'; 7 - import { isSymbol, ref } from '@hey-api/codegen-core'; 6 + import { isSymbol } from '@hey-api/codegen-core'; 8 7 import ts from 'typescript'; 9 8 10 9 import { TsDsl, TypeTsDsl } from '../base'; ··· 15 14 import { ValueMixin } from '../mixins/value'; 16 15 import { TypeExprTsDsl } from '../type/expr'; 17 16 import { safeRuntimeName } from '../utils/name'; 18 - 19 - export type VarName = Symbol | string; 20 17 21 18 const Mixed = DefaultMixin( 22 19 DocMixin( ··· 28 25 29 26 export class VarTsDsl extends Mixed { 30 27 readonly '~dsl' = 'VarTsDsl'; 28 + override readonly nameSanitizer = safeRuntimeName; 31 29 32 30 protected kind: ts.NodeFlags = ts.NodeFlags.None; 33 - protected name?: Ref<VarName>; 34 31 protected _type?: TypeTsDsl; 35 32 36 - constructor(name?: VarName) { 33 + constructor(name?: NodeName) { 37 34 super(); 38 - if (name) this.name = ref(name); 35 + if (name) this.name.set(name); 39 36 if (isSymbol(name)) { 40 37 name.setKind('var'); 41 - name.setNameSanitizer(safeRuntimeName); 42 38 name.setNode(this); 43 39 } 44 40 }
+8 -9
packages/openapi-ts/src/ts-dsl/type/alias.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 - Ref, 5 - Symbol, 4 + NodeName, 5 + NodeScope, 6 6 } from '@hey-api/codegen-core'; 7 - import { fromRef, isSymbol, ref } from '@hey-api/codegen-core'; 7 + import { isSymbol } from '@hey-api/codegen-core'; 8 8 import ts from 'typescript'; 9 9 10 10 import type { MaybeTsDsl } from '../base'; ··· 14 14 import { TypeParamsMixin } from '../mixins/type-params'; 15 15 import { safeTypeName } from '../utils/name'; 16 16 17 - type Name = Symbol | string; 18 17 type Value = MaybeTsDsl<ts.TypeNode>; 19 18 20 19 const Mixed = DocMixin( ··· 23 22 24 23 export class TypeAliasTsDsl extends Mixed { 25 24 readonly '~dsl' = 'TypeAliasTsDsl'; 25 + override readonly nameSanitizer = safeTypeName; 26 + override scope: NodeScope = 'type'; 26 27 27 - protected name: Ref<Name>; 28 28 protected value?: Value; 29 29 30 - constructor(name: Name, fn?: (t: TypeAliasTsDsl) => void) { 30 + constructor(name: NodeName, fn?: (t: TypeAliasTsDsl) => void) { 31 31 super(); 32 - this.name = ref(name); 32 + this.name.set(name); 33 33 if (isSymbol(name)) { 34 34 name.setKind('type'); 35 - name.setNameSanitizer(safeTypeName); 36 35 name.setNode(this); 37 36 } 38 37 fn?.(this); ··· 53 52 override toAst(ctx: AstContext) { 54 53 if (!this.value) 55 54 throw new Error( 56 - `Type alias '${fromRef(this.name)}' is missing a type definition`, 55 + `Type alias '${this.name.toString()}' is missing a type definition`, 57 56 ); 58 57 const node = ts.factory.createTypeAliasDeclaration( 59 58 this.modifiers,
+4 -2
packages/openapi-ts/src/ts-dsl/type/and.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 + NodeName, 5 + NodeScope, 4 6 Ref, 5 - Symbol, 6 7 } from '@hey-api/codegen-core'; 7 8 import { ref } from '@hey-api/codegen-core'; 8 9 import ts from 'typescript'; ··· 10 11 import type { TypeTsDsl } from '../base'; 11 12 import { TsDsl } from '../base'; 12 13 13 - type Type = Symbol | string | ts.TypeNode | TypeTsDsl; 14 + type Type = NodeName | ts.TypeNode | TypeTsDsl; 14 15 15 16 const Mixed = TsDsl<ts.IntersectionTypeNode>; 16 17 17 18 export class TypeAndTsDsl extends Mixed { 18 19 readonly '~dsl' = 'TypeAndTsDsl'; 20 + override scope: NodeScope = 'type'; 19 21 20 22 protected _types: Array<Ref<Type>> = []; 21 23
+5 -3
packages/openapi-ts/src/ts-dsl/type/attr.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 + NodeName, 5 + NodeScope, 4 6 Ref, 5 - Symbol, 6 7 } from '@hey-api/codegen-core'; 7 8 import { isRef, ref } from '@hey-api/codegen-core'; 8 9 import ts from 'typescript'; ··· 11 12 import { TsDsl } from '../base'; 12 13 import { TypeExprMixin } from '../mixins/type-expr'; 13 14 14 - type Base = Symbol | string | MaybeTsDsl<ts.EntityName>; 15 - type Right = Symbol | string | ts.Identifier; 15 + type Base = NodeName | MaybeTsDsl<ts.EntityName>; 16 + type Right = NodeName | ts.Identifier; 16 17 17 18 const Mixed = TypeExprMixin(TsDsl<ts.QualifiedName>); 18 19 19 20 export class TypeAttrTsDsl extends Mixed { 20 21 readonly '~dsl' = 'TypeAttrTsDsl'; 22 + override scope: NodeScope = 'type'; 21 23 22 24 protected _base?: Ref<Base>; 23 25 protected _right!: Ref<Right>;
+8 -7
packages/openapi-ts/src/ts-dsl/type/expr.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 + NodeName, 5 + NodeScope, 4 6 Ref, 5 - Symbol, 6 7 } from '@hey-api/codegen-core'; 7 8 import { isNode, ref } from '@hey-api/codegen-core'; 8 9 import ts from 'typescript'; ··· 13 14 import { f } from '../utils/factories'; 14 15 import { TypeAttrTsDsl } from './attr'; 15 16 16 - export type TypeExprName = Symbol | string; 17 - export type TypeExprExpr = TypeExprName | TypeAttrTsDsl; 17 + export type TypeExprExpr = NodeName | TypeAttrTsDsl; 18 18 export type TypeExprFn = (t: TypeExprTsDsl) => void; 19 19 export type TypeExprCtor = ( 20 - nameOrFn?: TypeExprName | TypeExprFn, 20 + nameOrFn?: NodeName | TypeExprFn, 21 21 fn?: TypeExprFn, 22 22 ) => TypeExprTsDsl; 23 23 ··· 25 25 26 26 export class TypeExprTsDsl extends Mixed { 27 27 readonly '~dsl' = 'TypeExprTsDsl'; 28 + override scope: NodeScope = 'type'; 28 29 29 30 protected _exprInput?: Ref<TypeExprExpr>; 30 31 31 32 constructor(); 32 33 constructor(fn: TypeExprFn); 33 - constructor(name: TypeExprName); 34 - constructor(name: TypeExprName, fn?: TypeExprFn); 35 - constructor(name?: TypeExprName | TypeExprFn, fn?: TypeExprFn) { 34 + constructor(name: NodeName); 35 + constructor(name: NodeName, fn?: TypeExprFn); 36 + constructor(name?: NodeName | TypeExprFn, fn?: TypeExprFn) { 36 37 super(); 37 38 if (typeof name === 'function') { 38 39 name(this);
+6 -1
packages/openapi-ts/src/ts-dsl/type/func.ts
··· 1 - import type { AnalysisContext, AstContext } from '@hey-api/codegen-core'; 1 + import type { 2 + AnalysisContext, 3 + AstContext, 4 + NodeScope, 5 + } from '@hey-api/codegen-core'; 2 6 import ts from 'typescript'; 3 7 4 8 import { TsDsl } from '../base'; ··· 13 17 14 18 export class TypeFuncTsDsl extends Mixed { 15 19 readonly '~dsl' = 'TypeFuncTsDsl'; 20 + override scope: NodeScope = 'type'; 16 21 17 22 override analyze(ctx: AnalysisContext): void { 18 23 super.analyze(ctx);
+12 -9
packages/openapi-ts/src/ts-dsl/type/idx-sig.ts
··· 1 - import type { AnalysisContext, AstContext } from '@hey-api/codegen-core'; 1 + import type { 2 + AnalysisContext, 3 + AstContext, 4 + NodeName, 5 + NodeScope, 6 + } from '@hey-api/codegen-core'; 2 7 import ts from 'typescript'; 3 8 4 9 import type { MaybeTsDsl } from '../base'; ··· 6 11 import { DocMixin } from '../mixins/doc'; 7 12 import { ReadonlyMixin } from '../mixins/modifiers'; 8 13 9 - export type TypeIdxSigName = string; 10 14 export type TypeIdxSigType = string | MaybeTsDsl<ts.TypeNode>; 11 15 12 16 const Mixed = DocMixin(ReadonlyMixin(TsDsl<ts.IndexSignatureDeclaration>)); 13 17 14 18 export class TypeIdxSigTsDsl extends Mixed { 15 19 readonly '~dsl' = 'TypeIdxSigTsDsl'; 20 + override scope: NodeScope = 'type'; 16 21 17 22 protected _key?: TypeIdxSigType; 18 - protected _name: TypeIdxSigName; 19 23 protected _type?: TypeIdxSigType; 20 24 21 - constructor(name: TypeIdxSigName, fn?: (i: TypeIdxSigTsDsl) => void) { 25 + constructor(name: NodeName, fn?: (i: TypeIdxSigTsDsl) => void) { 22 26 super(); 23 - this._name = name; 27 + this.name.set(name); 24 28 fn?.(this); 25 29 } 26 30 ··· 55 59 ts.factory.createParameterDeclaration( 56 60 undefined, 57 61 undefined, 58 - this._name, 62 + this.$node(ctx, this.name) as ts.BindingName, 59 63 undefined, 60 64 this.$type(ctx, this._key), 61 65 ), ··· 67 71 68 72 $validate(): asserts this is this & { 69 73 _key: TypeIdxSigType; 70 - _name: TypeIdxSigName; 71 74 _type: TypeIdxSigType; 72 75 } { 73 76 const missing = this.missingRequiredCalls(); 74 77 if (missing.length === 0) return; 78 + const name = this.name.toString(); 75 79 throw new Error( 76 - `Index signature${this._name ? ` "${this._name}"` : ''} missing ${missing.join(' and ')}`, 80 + `Index signature${name ? ` "${name}"` : ''} missing ${missing.join(' and ')}`, 77 81 ); 78 82 } 79 83 80 84 private missingRequiredCalls(): ReadonlyArray<string> { 81 85 const missing: Array<string> = []; 82 86 if (!this._key) missing.push('.key()'); 83 - if (!this._name) missing.push('.name()'); 84 87 if (!this._type) missing.push('.\u200Btype()'); 85 88 return missing; 86 89 }
+6 -1
packages/openapi-ts/src/ts-dsl/type/idx.ts
··· 1 - import type { AnalysisContext, AstContext } from '@hey-api/codegen-core'; 1 + import type { 2 + AnalysisContext, 3 + AstContext, 4 + NodeScope, 5 + } from '@hey-api/codegen-core'; 2 6 import ts from 'typescript'; 3 7 4 8 import type { MaybeTsDsl } from '../base'; ··· 14 18 15 19 export class TypeIdxTsDsl extends Mixed { 16 20 readonly '~dsl' = 'TypeIdxTsDsl'; 21 + override scope: NodeScope = 'type'; 17 22 18 23 protected _base!: Base; 19 24 protected _index!: Index;
+6 -1
packages/openapi-ts/src/ts-dsl/type/literal.ts
··· 1 - import type { AnalysisContext, AstContext } from '@hey-api/codegen-core'; 1 + import type { 2 + AnalysisContext, 3 + AstContext, 4 + NodeScope, 5 + } from '@hey-api/codegen-core'; 2 6 import ts from 'typescript'; 3 7 4 8 import { TsDsl } from '../base'; ··· 8 12 9 13 export class TypeLiteralTsDsl extends Mixed { 10 14 readonly '~dsl' = 'TypeLiteralTsDsl'; 15 + override scope: NodeScope = 'type'; 11 16 12 17 protected value: string | number | boolean | null; 13 18
+12 -14
packages/openapi-ts/src/ts-dsl/type/mapped.ts
··· 1 - import type { AnalysisContext, AstContext } from '@hey-api/codegen-core'; 1 + import type { 2 + AnalysisContext, 3 + AstContext, 4 + NodeName, 5 + NodeScope, 6 + } from '@hey-api/codegen-core'; 2 7 import ts from 'typescript'; 3 8 4 9 import type { MaybeTsDsl } from '../base'; ··· 9 14 10 15 export class TypeMappedTsDsl extends Mixed { 11 16 readonly '~dsl' = 'TypeMappedTsDsl'; 17 + override scope: NodeScope = 'type'; 12 18 13 19 protected questionToken?: TokenTsDsl< 14 20 | ts.SyntaxKind.QuestionToken ··· 21 27 | ts.SyntaxKind.PlusToken 22 28 >; 23 29 protected _key?: string | MaybeTsDsl<ts.TypeNode>; 24 - protected _name?: string; 25 30 protected _type?: string | MaybeTsDsl<ts.TypeNode>; 26 31 27 - constructor(name?: string) { 32 + constructor(name?: NodeName) { 28 33 super(); 29 - this.name(name); 34 + if (name) this.name.set(name); 30 35 } 31 36 32 37 override analyze(ctx: AnalysisContext): void { ··· 54 59 return this; 55 60 } 56 61 57 - /** Sets the parameter name: `{ [Name in keyof T]: U }` */ 58 - name(name?: string): this { 59 - this._name = name; 60 - return this; 61 - } 62 - 63 62 /** Makes `[K in X]?:` optional. */ 64 63 optional(): this { 65 64 this.questionToken = new TokenTsDsl().optional(); ··· 90 89 this.$node(ctx, this.readonlyToken), 91 90 ts.factory.createTypeParameterDeclaration( 92 91 undefined, 93 - this._name, 92 + this.$node(ctx, this.name) as ts.Identifier, 94 93 this.$type(ctx, this._key), 95 94 undefined, 96 95 ), ··· 103 102 104 103 $validate(): asserts this is this & { 105 104 _key: string | MaybeTsDsl<ts.TypeNode>; 106 - _name: string; 107 105 _type: string | MaybeTsDsl<ts.TypeNode>; 108 106 } { 109 107 const missing = this.missingRequiredCalls(); 110 108 if (missing.length === 0) return; 109 + const name = this.name.toString(); 111 110 throw new Error( 112 - `Mapped type${this._name ? ` "${this._name}"` : ''} missing ${missing.join(' and ')}`, 111 + `Mapped type${name ? ` "${name}"` : ''} missing ${missing.join(' and ')}`, 113 112 ); 114 113 } 115 114 116 115 private missingRequiredCalls(): ReadonlyArray<string> { 117 116 const missing: Array<string> = []; 118 117 if (!this._key) missing.push('.key()'); 119 - if (!this._name) missing.push('.name()'); 120 118 if (!this._type) missing.push('.\u200Btype()'); 121 119 return missing; 122 120 }
+6 -1
packages/openapi-ts/src/ts-dsl/type/object.ts
··· 1 - import type { AnalysisContext, AstContext } from '@hey-api/codegen-core'; 1 + import type { 2 + AnalysisContext, 3 + AstContext, 4 + NodeScope, 5 + } from '@hey-api/codegen-core'; 2 6 import ts from 'typescript'; 3 7 4 8 import { TsDsl } from '../base'; ··· 9 13 10 14 export class TypeObjectTsDsl extends Mixed { 11 15 readonly '~dsl' = 'TypeObjectTsDsl'; 16 + override scope: NodeScope = 'type'; 12 17 13 18 protected props: Array<TypePropTsDsl | TypeIdxSigTsDsl> = []; 14 19
+6 -1
packages/openapi-ts/src/ts-dsl/type/operator.ts
··· 1 - import type { AnalysisContext, AstContext } from '@hey-api/codegen-core'; 1 + import type { 2 + AnalysisContext, 3 + AstContext, 4 + NodeScope, 5 + } from '@hey-api/codegen-core'; 2 6 import ts from 'typescript'; 3 7 4 8 import type { MaybeTsDsl } from '../base'; ··· 28 32 */ 29 33 export class TypeOperatorTsDsl extends Mixed { 30 34 readonly '~dsl' = 'TypeOperatorTsDsl'; 35 + override scope: NodeScope = 'type'; 31 36 32 37 protected _op?: Op; 33 38 protected _type?: Type;
+4 -2
packages/openapi-ts/src/ts-dsl/type/or.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 + NodeName, 5 + NodeScope, 4 6 Ref, 5 - Symbol, 6 7 } from '@hey-api/codegen-core'; 7 8 import { ref } from '@hey-api/codegen-core'; 8 9 import ts from 'typescript'; ··· 10 11 import type { TypeTsDsl } from '../base'; 11 12 import { TsDsl } from '../base'; 12 13 13 - type Type = Symbol | string | ts.TypeNode | TypeTsDsl; 14 + type Type = NodeName | ts.TypeNode | TypeTsDsl; 14 15 15 16 const Mixed = TsDsl<ts.UnionTypeNode>; 16 17 17 18 export class TypeOrTsDsl extends Mixed { 18 19 readonly '~dsl' = 'TypeOrTsDsl'; 20 + override scope: NodeScope = 'type'; 19 21 20 22 protected _types: Array<Ref<Type>> = []; 21 23
+8 -7
packages/openapi-ts/src/ts-dsl/type/param.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 + NodeName, 5 + NodeScope, 4 6 Ref, 5 - Symbol, 6 7 } from '@hey-api/codegen-core'; 7 8 import { ref } from '@hey-api/codegen-core'; 8 9 import ts from 'typescript'; ··· 10 11 import type { MaybeTsDsl, TypeTsDsl } from '../base'; 11 12 import { TsDsl } from '../base'; 12 13 13 - export type TypeParamName = Symbol | string; 14 - export type TypeParamExpr = Symbol | string | boolean | MaybeTsDsl<TypeTsDsl>; 14 + export type TypeParamExpr = NodeName | boolean | MaybeTsDsl<TypeTsDsl>; 15 15 16 16 const Mixed = TsDsl<ts.TypeParameterDeclaration>; 17 17 18 18 export class TypeParamTsDsl extends Mixed { 19 19 readonly '~dsl' = 'TypeParamTsDsl'; 20 + override scope: NodeScope = 'type'; 20 21 21 22 protected constraint?: Ref<TypeParamExpr>; 22 23 protected defaultValue?: Ref<TypeParamExpr>; 23 - protected name?: Ref<TypeParamName>; 24 24 25 - constructor(name?: TypeParamName, fn?: (name: TypeParamTsDsl) => void) { 25 + constructor(name?: NodeName, fn?: (name: TypeParamTsDsl) => void) { 26 26 super(); 27 - if (name) this.name = ref(name); 27 + if (name) this.name.set(name); 28 28 fn?.(this); 29 29 } 30 30 ··· 46 46 } 47 47 48 48 override toAst(ctx: AstContext) { 49 - if (!this.name) throw new Error('Missing type name'); 49 + const name = this.name.toString(); 50 + if (!name) throw new Error('Missing type name'); 50 51 return ts.factory.createTypeParameterDeclaration( 51 52 undefined, 52 53 this.$node(ctx, this.name) as ts.Identifier,
+10 -9
packages/openapi-ts/src/ts-dsl/type/prop.ts
··· 1 1 import type { 2 2 AnalysisContext, 3 3 AstContext, 4 + NodeName, 5 + NodeScope, 4 6 Ref, 5 - Symbol, 6 7 } from '@hey-api/codegen-core'; 7 8 import { ref } from '@hey-api/codegen-core'; 8 9 import ts from 'typescript'; ··· 15 16 import { TokenTsDsl } from '../token'; 16 17 import { safePropName } from '../utils/name'; 17 18 18 - export type TypePropName = string; 19 - export type TypePropType = Symbol | string | MaybeTsDsl<ts.TypeNode>; 19 + export type TypePropType = NodeName | MaybeTsDsl<ts.TypeNode>; 20 20 21 21 const Mixed = DocMixin(OptionalMixin(ReadonlyMixin(TsDsl<ts.TypeElement>))); 22 22 23 23 export class TypePropTsDsl extends Mixed { 24 24 readonly '~dsl' = 'TypePropTsDsl'; 25 + override scope: NodeScope = 'type'; 25 26 26 - protected name: TypePropName; 27 27 protected _type?: Ref<TypePropType>; 28 28 29 - constructor(name: TypePropName, fn: (p: TypePropTsDsl) => void) { 29 + constructor(name: NodeName, fn: (p: TypePropTsDsl) => void) { 30 30 super(); 31 - this.name = name; 31 + this.name.set(name); 32 32 fn(this); 33 33 } 34 34 ··· 44 44 } 45 45 46 46 override toAst(ctx: AstContext) { 47 - if (!this._type) { 48 - throw new Error(`Type not specified for property '${this.name}'`); 47 + const name = this.name.toString(); 48 + if (!this._type || !name) { 49 + throw new Error(`Type not specified for property '${name}'`); 49 50 } 50 51 const node = ts.factory.createPropertySignature( 51 52 this.modifiers, 52 - this.$node(ctx, safePropName(this.name)), 53 + this.$node(ctx, safePropName(name)), 53 54 this._optional ? this.$node(ctx, new TokenTsDsl().optional()) : undefined, 54 55 this.$type(ctx, this._type), 55 56 );
+6 -1
packages/openapi-ts/src/ts-dsl/type/query.ts
··· 1 - import type { AnalysisContext, AstContext } from '@hey-api/codegen-core'; 1 + import type { 2 + AnalysisContext, 3 + AstContext, 4 + NodeScope, 5 + } from '@hey-api/codegen-core'; 2 6 import ts from 'typescript'; 3 7 4 8 import type { MaybeTsDsl, TypeTsDsl } from '../base'; ··· 13 17 14 18 export class TypeQueryTsDsl extends Mixed { 15 19 readonly '~dsl' = 'TypeQueryTsDsl'; 20 + override scope: NodeScope = 'type'; 16 21 17 22 protected _expr: TypeQueryExpr; 18 23
+6 -1
packages/openapi-ts/src/ts-dsl/type/template.ts
··· 1 - import type { AnalysisContext, AstContext } from '@hey-api/codegen-core'; 1 + import type { 2 + AnalysisContext, 3 + AstContext, 4 + NodeScope, 5 + } from '@hey-api/codegen-core'; 2 6 import ts from 'typescript'; 3 7 4 8 import type { MaybeTsDsl } from '../base'; ··· 8 12 9 13 export class TypeTemplateTsDsl extends Mixed { 10 14 readonly '~dsl' = 'TypeTemplateTsDsl'; 15 + override scope: NodeScope = 'type'; 11 16 12 17 protected parts: Array<string | MaybeTsDsl<ts.TypeNode>> = []; 13 18
+6 -1
packages/openapi-ts/src/ts-dsl/type/tuple.ts
··· 1 - import type { AnalysisContext, AstContext } from '@hey-api/codegen-core'; 1 + import type { 2 + AnalysisContext, 3 + AstContext, 4 + NodeScope, 5 + } from '@hey-api/codegen-core'; 2 6 import ts from 'typescript'; 3 7 4 8 import type { TypeTsDsl } from '../base'; ··· 8 12 9 13 export class TypeTupleTsDsl extends Mixed { 10 14 readonly '~dsl' = 'TypeTupleTsDsl'; 15 + override scope: NodeScope = 'type'; 11 16 12 17 protected _elements: Array<string | ts.TypeNode | TypeTsDsl> = []; 13 18
+113
packages/openapi-ts/src/ts-dsl/utils/context.ts
··· 1 + import type { 2 + AccessPatternOptions, 3 + AstContext, 4 + NodeScope, 5 + Symbol, 6 + } from '@hey-api/codegen-core'; 7 + import { isSymbol } from '@hey-api/codegen-core'; 8 + 9 + import { $ } from '~/ts-dsl'; 10 + 11 + import type { TsDsl } from '../base'; 12 + 13 + const getScope = (node: TsDsl): NodeScope => node.scope ?? 'value'; 14 + 15 + function traverseStructuralParent(node: TsDsl): boolean { 16 + if (node.role === 'literal') { 17 + return false; 18 + } 19 + return true; 20 + } 21 + 22 + function foldAccessChain<Node extends TsDsl = TsDsl>( 23 + chain: ReadonlyArray<Node>, 24 + ): ReadonlyArray<Node> { 25 + const folded: Array<Node> = []; 26 + 27 + for (const node of chain) { 28 + if (folded.length === 0) { 29 + if (node.role === 'container') { 30 + folded.push(node); 31 + } 32 + } else if (node.role === 'accessor') { 33 + folded.push(node); 34 + } 35 + } 36 + 37 + return folded; 38 + } 39 + 40 + function getAccessChain<Node extends TsDsl = TsDsl>( 41 + node: Node, 42 + ): ReadonlyArray<Node> { 43 + const chain: Array<TsDsl> = []; 44 + const scope: NodeScope = getScope(node); 45 + const visited = new Set<TsDsl>(); 46 + 47 + let current: TsDsl | undefined = node; 48 + while (current) { 49 + if (visited.has(current)) break; 50 + visited.add(current); 51 + 52 + chain.unshift(current); 53 + 54 + let foundParent = false; 55 + for (const [parent] of current.structuralParents || []) { 56 + if (getScope(parent) === scope && traverseStructuralParent(parent)) { 57 + current = parent; 58 + foundParent = true; 59 + break; 60 + } 61 + } 62 + 63 + if (!foundParent) break; 64 + } 65 + 66 + // trim any unreachable nodes before root 67 + const rootIndex = chain.findIndex((node) => node.root); 68 + if (rootIndex !== -1) { 69 + chain.splice(0, rootIndex); 70 + } 71 + 72 + return foldAccessChain(chain) as ReadonlyArray<Node>; 73 + } 74 + 75 + export const astContext: AstContext = { 76 + getAccess<T = unknown>( 77 + to: TsDsl | Symbol<TsDsl>, 78 + options?: AccessPatternOptions, 79 + ): T { 80 + const node = isSymbol(to) ? to.node! : to; 81 + const chain = getAccessChain(node); 82 + if (chain.length === 0) return node as T; 83 + 84 + let result!: ReturnType<typeof $.expr | typeof $.attr>; 85 + 86 + for (let index = 0; index < chain.length; index++) { 87 + const currentNode = chain[index]!; 88 + 89 + const transformed = currentNode.accessPattern?.( 90 + currentNode, 91 + { 92 + context: 'runtime', 93 + ...options, 94 + }, 95 + { 96 + chain, 97 + index, 98 + isLeaf: index === chain.length - 1, 99 + isRoot: index === 0, 100 + length: chain.length, 101 + }, 102 + ) as typeof result | undefined; 103 + 104 + if (index === 0) { 105 + result = transformed || $(currentNode.name); 106 + } else { 107 + result = result.attr(transformed?.name || currentNode.name); 108 + } 109 + } 110 + 111 + return result as T; 112 + }, 113 + };
+2 -6
packages/openapi-ts/src/ts-dsl/utils/lazy.ts
··· 2 2 import type ts from 'typescript'; 3 3 4 4 import { TsDsl } from '../base'; 5 + import { astContext } from './context'; 5 6 6 7 export type LazyThunk<T extends ts.Node> = (ctx: AstContext) => TsDsl<T>; 7 8 ··· 17 18 18 19 override analyze(ctx: AnalysisContext): void { 19 20 super.analyze(ctx); 20 - const astContext: AstContext = { 21 - getAccess(node) { 22 - return node; 23 - }, 24 - }; 25 21 ctx.analyze(this._thunk(astContext)); 26 22 } 27 23 28 - override toAst(ctx: AstContext) { 24 + override toAst(ctx: AstContext): T { 29 25 return this._thunk(ctx).toAst(ctx); 30 26 } 31 27 }
+26 -8
packages/openapi-ts/src/ts-dsl/utils/name.ts
··· 7 7 import type { ReservedList } from './reserved'; 8 8 import { reserved } from './reserved'; 9 9 10 - export const safeMemberName = (name: string): TsDsl<ts.PropertyName> => { 10 + export const safeAccessorName = (name: string): string => { 11 + regexp.number.lastIndex = 0; 12 + if (regexp.number.test(name)) { 13 + return name.startsWith('-') ? `'${name}'` : name; 14 + } 15 + 16 + regexp.typeScriptIdentifier.lastIndex = 0; 17 + if (regexp.typeScriptIdentifier.test(name)) { 18 + return name; 19 + } 20 + return `'${name}'`; 21 + }; 22 + 23 + export const safeMemberName = ( 24 + name: string, 25 + ): TsDsl<ts.StringLiteral> | IdTsDsl => { 11 26 regexp.typeScriptIdentifier.lastIndex = 0; 12 27 if (regexp.typeScriptIdentifier.test(name)) { 13 28 return new IdTsDsl(name); 14 29 } 15 - return new LiteralTsDsl(name) as TsDsl<ts.PropertyName>; 30 + return new LiteralTsDsl(name) as TsDsl<ts.StringLiteral>; 16 31 }; 17 32 18 - export const safePropName = (name: string): TsDsl<ts.PropertyName> => { 33 + export const safePropName = ( 34 + name: string, 35 + ): TsDsl<ts.StringLiteral | ts.NumericLiteral> | IdTsDsl => { 19 36 regexp.number.lastIndex = 0; 20 37 if (regexp.number.test(name)) { 21 38 return name.startsWith('-') 22 - ? (new LiteralTsDsl(name) as TsDsl<ts.PropertyName>) 23 - : (new LiteralTsDsl(Number(name)) as TsDsl<ts.PropertyName>); 39 + ? (new LiteralTsDsl(name) as TsDsl<ts.StringLiteral>) 40 + : (new LiteralTsDsl(Number(name)) as TsDsl<ts.NumericLiteral>); 24 41 } 25 42 26 43 regexp.typeScriptIdentifier.lastIndex = 0; ··· 28 45 return new IdTsDsl(name); 29 46 } 30 47 31 - return new LiteralTsDsl(name) as TsDsl<ts.PropertyName>; 48 + return new LiteralTsDsl(name) as TsDsl<ts.StringLiteral>; 32 49 }; 33 50 34 51 const safeName = (name: string, reserved: ReservedList): string => { ··· 58 75 return sanitized || '_'; 59 76 }; 60 77 61 - export const safeRuntimeName = (name: string) => 78 + export const safeRuntimeName = (name: string): string => 62 79 safeName(name, reserved.runtime); 63 80 64 - export const safeTypeName = (name: string) => safeName(name, reserved.type); 81 + export const safeTypeName = (name: string): string => 82 + safeName(name, reserved.type);