Skip to content

Commit 0e03eb4

Browse files
authored
Merge pull request #2924 from hey-api/fix/transformer-plugin-reference
fix(transformers): do not reference undefined transformers
2 parents 2e237c3 + b3f4f91 commit 0e03eb4

File tree

2 files changed

+58
-29
lines changed

2 files changed

+58
-29
lines changed

.changeset/brave-colts-eat.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@hey-api/openapi-ts': patch
3+
---
4+
5+
fix(transformers): do not reference undefined transformers

packages/openapi-ts/src/plugins/@hey-api/transformers/plugin.ts

Lines changed: 53 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ import type { HeyApiTransformersPlugin } from './types';
1111

1212
const dataVariableName = 'data';
1313

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+
1418
const ensureStatements = (
1519
nodes: Array<ts.Expression | ts.Statement>,
1620
): Array<ts.Statement> =>
@@ -65,12 +69,14 @@ const processSchemaType = ({
6569
resourceId: schema.$ref,
6670
};
6771

68-
if (!plugin.getSymbol(query)) {
69-
// TODO: remove
70-
// create each schema response transformer only once
72+
let symbol = plugin.getSymbol(query);
7173

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({
7480
meta: query,
7581
name: buildName({
7682
config: {
@@ -80,35 +86,53 @@ const processSchemaType = ({
8086
name: refToName(schema.$ref),
8187
}),
8288
});
89+
plugin.setSymbolValue(symbol, null);
90+
}
8391

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,
106106
});
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);
108128
}
109129
}
110130

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)) {
112136
const ref = plugin.referenceSymbol(query);
113137
const callExpression = tsc.callExpression({
114138
functionName: ref.placeholder,

0 commit comments

Comments
 (0)