Skip to content

Commit a044cd7

Browse files
committed
fix(parser-3.0.x): prevent [key: string]: never in allOf with additionalProperties: false
Applies same fix as 2.0.x parser - detects empty objects with additionalProperties: false inside allOf compositions and skips never index signature generation. Ensures consistent behavior across all OpenAPI parser versions.
1 parent 6b77896 commit a044cd7

File tree

1 file changed

+25
-38
lines changed
  • packages/openapi-ts/src/openApi/3.0.x/parser

1 file changed

+25
-38
lines changed

packages/openapi-ts/src/openApi/3.0.x/parser/schema.ts

Lines changed: 25 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -248,31 +248,24 @@ const parseObject = ({
248248
irSchema.properties = schemaProperties;
249249
}
250250

251-
// --- PATCH: Avoid [key: string]: never for empty objects in allOf ---
252-
// If this object is empty (no properties, no required, no patternProperties, no min/maxProperties)
253-
// and additionalProperties is false, and we are inside an allOf composition,
254-
// then do NOT emit additionalProperties: { type: 'never' }.
255-
// Instead, let the composition enforce the restriction.
256-
const isEmptyObject =
257-
(!schema.properties || Object.keys(schema.properties).length === 0) &&
258-
(!schema.required || schema.required.length === 0) &&
259-
schema.minProperties === undefined &&
260-
schema.maxProperties === undefined;
261-
262-
// Heuristic: if state has a marker for "inAllOf", skip [key: string]: never for empty objects
263-
const inAllOf = (state as any)?.inAllOf;
264-
265251
if (schema.additionalProperties === undefined) {
266252
if (!irSchema.properties) {
267253
irSchema.additionalProperties = {
268254
type: 'unknown',
269255
};
270256
}
271257
} else if (typeof schema.additionalProperties === 'boolean') {
272-
if (schema.additionalProperties === false && isEmptyObject && inAllOf) {
273-
// Do not emit [key: string]: never for empty object in allOf
274-
// Just skip setting additionalProperties
275-
} else {
258+
// Avoid [key: string]: never for empty objects with additionalProperties: false inside allOf
259+
// This would override inherited properties from other schemas in the composition
260+
const isEmptyObjectInAllOf =
261+
state.inAllOf &&
262+
schema.additionalProperties === false &&
263+
(!schema.properties || Object.keys(schema.properties).length === 0) &&
264+
!schema.allOf &&
265+
!schema.anyOf &&
266+
!schema.oneOf;
267+
268+
if (!isEmptyObjectInAllOf) {
276269
irSchema.additionalProperties = {
277270
type: schema.additionalProperties ? 'unknown' : 'never',
278271
};
@@ -338,26 +331,20 @@ const parseAllOf = ({
338331
const compositionSchemas = schema.allOf;
339332

340333
for (const compositionSchema of compositionSchemas) {
341-
// Mark that we are inside an allOf for parseObject
342-
// Only pass inAllOf directly to the schema in allOf if it is NOT a $ref.
343-
let irCompositionSchema;
344-
if (
345-
compositionSchema &&
346-
typeof compositionSchema === 'object' &&
347-
!('$ref' in compositionSchema)
348-
) {
349-
irCompositionSchema = schemaToIrSchema({
350-
context,
351-
schema: compositionSchema,
352-
state: { ...state, inAllOf: true },
353-
});
354-
} else {
355-
irCompositionSchema = schemaToIrSchema({
356-
context,
357-
schema: compositionSchema,
358-
state,
359-
});
360-
}
334+
// Don't propagate inAllOf flag to $ref schemas to avoid issues with reusable components
335+
const isRef = '$ref' in compositionSchema;
336+
const schemaState = isRef
337+
? state
338+
: {
339+
...state,
340+
inAllOf: true,
341+
};
342+
343+
const irCompositionSchema = schemaToIrSchema({
344+
context,
345+
schema: compositionSchema,
346+
state: schemaState,
347+
});
361348

362349
irSchema.accessScopes = mergeSchemaAccessScopes(
363350
irSchema.accessScopes,

0 commit comments

Comments
 (0)