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

Merge pull request #2924 from hey-api/fix/transformer-plugin-reference

fix(transformers): do not reference undefined transformers

authored by

Lubos and committed by
GitHub
0e03eb4f 2e237c35

+58 -29
+5
.changeset/brave-colts-eat.md
··· 1 + --- 2 + '@hey-api/openapi-ts': patch 3 + --- 4 + 5 + fix(transformers): do not reference undefined transformers
+53 -29
packages/openapi-ts/src/plugins/@hey-api/transformers/plugin.ts
··· 11 11 12 12 const dataVariableName = 'data'; 13 13 14 + // Track symbols that are currently being built so recursive references 15 + // can emit calls to transformers that will be implemented later. 16 + const buildingSymbols = new Set<number>(); 17 + 14 18 const ensureStatements = ( 15 19 nodes: Array<ts.Expression | ts.Statement>, 16 20 ): Array<ts.Statement> => ··· 65 69 resourceId: schema.$ref, 66 70 }; 67 71 68 - if (!plugin.getSymbol(query)) { 69 - // TODO: remove 70 - // create each schema response transformer only once 72 + let symbol = plugin.getSymbol(query); 71 73 72 - // Register symbol early to prevent infinite recursion with self-referential schemas 73 - const symbol = plugin.registerSymbol({ 74 + if (!symbol) { 75 + // Register a placeholder symbol immediately and set its value to null 76 + // as a stop token to prevent infinite recursion for self-referential 77 + // schemas. We also mark this symbol as "building" so that nested 78 + // references to it can emit calls that will be implemented later. 79 + symbol = plugin.registerSymbol({ 74 80 meta: query, 75 81 name: buildName({ 76 82 config: { ··· 80 86 name: refToName(schema.$ref), 81 87 }), 82 88 }); 89 + plugin.setSymbolValue(symbol, null); 90 + } 83 91 84 - const refSchema = plugin.context.resolveIrRef<IR.SchemaObject>( 85 - schema.$ref, 86 - ); 87 - const nodes = schemaResponseTransformerNodes({ 88 - plugin, 89 - schema: refSchema, 90 - }); 91 - if (nodes.length) { 92 - const node = tsc.constVariable({ 93 - expression: tsc.arrowFunction({ 94 - async: false, 95 - multiLine: true, 96 - parameters: [ 97 - { 98 - name: dataVariableName, 99 - // TODO: parser - add types, generate types without transforms 100 - type: tsc.keywordTypeNode({ keyword: 'any' }), 101 - }, 102 - ], 103 - statements: ensureStatements(nodes), 104 - }), 105 - name: symbol.placeholder, 92 + // Only compute the implementation if the symbol isn't already being built. 93 + // This prevents infinite recursion on self-referential schemas. We still 94 + // allow emitting a call when the symbol is currently being built so 95 + // parent nodes can reference the transformer that will be emitted later. 96 + const existingValue = plugin.gen.symbols.getValue(symbol.id); 97 + if (!existingValue && !buildingSymbols.has(symbol.id)) { 98 + buildingSymbols.add(symbol.id); 99 + try { 100 + const refSchema = plugin.context.resolveIrRef<IR.SchemaObject>( 101 + schema.$ref, 102 + ); 103 + const nodes = schemaResponseTransformerNodes({ 104 + plugin, 105 + schema: refSchema, 106 106 }); 107 - plugin.setSymbolValue(symbol, node); 107 + 108 + if (nodes.length) { 109 + const node = tsc.constVariable({ 110 + expression: tsc.arrowFunction({ 111 + async: false, 112 + multiLine: true, 113 + parameters: [ 114 + { 115 + name: dataVariableName, 116 + // TODO: parser - add types, generate types without transforms 117 + type: tsc.keywordTypeNode({ keyword: 'any' }), 118 + }, 119 + ], 120 + statements: ensureStatements(nodes), 121 + }), 122 + name: symbol.placeholder, 123 + }); 124 + plugin.setSymbolValue(symbol, node); 125 + } 126 + } finally { 127 + buildingSymbols.delete(symbol.id); 108 128 } 109 129 } 110 130 111 - if (plugin.isSymbolRegistered(query)) { 131 + // Only emit a call if the symbol has a value (implementation) OR the 132 + // symbol is currently being built (recursive reference) — in the 133 + // latter case we allow emitting a call that will be implemented later. 134 + const currentValue = plugin.gen.symbols.getValue(symbol.id); 135 + if (currentValue || buildingSymbols.has(symbol.id)) { 112 136 const ref = plugin.referenceSymbol(query); 113 137 const callExpression = tsc.callExpression({ 114 138 functionName: ref.placeholder,