Skip to content

Commit 70ce84a

Browse files
committed
fix anyof/oneof visited
1 parent 20abe6b commit 70ce84a

2 files changed

Lines changed: 72 additions & 16 deletions

File tree

packages/core/src/rules/common/__tests__/no-required-schema-properties-undefined.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,45 @@ describe('no-required-schema-properties-undefined', () => {
670670
`);
671671
});
672672

673+
it('should NOT report when anyOf branches share a $ref that defines the required property', async () => {
674+
const document = parseYamlToDocument(
675+
outdent`
676+
openapi: 3.0.0
677+
components:
678+
schemas:
679+
Animal:
680+
type: object
681+
required:
682+
- name
683+
anyOf:
684+
- allOf:
685+
- $ref: '#/components/schemas/Base'
686+
- properties:
687+
kind:
688+
enum: [cat]
689+
- allOf:
690+
- $ref: '#/components/schemas/Base'
691+
- properties:
692+
kind:
693+
enum: [dog]
694+
Base:
695+
type: object
696+
properties:
697+
name:
698+
type: string
699+
`,
700+
'foobar.yaml'
701+
);
702+
703+
const results = await lintDocument({
704+
externalRefResolver: new BaseResolver(),
705+
document,
706+
config: await createConfig({ rules: { 'no-required-schema-properties-undefined': 'error' } }),
707+
});
708+
709+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
710+
});
711+
673712
it('should NOT report when required property is in allOf sibling reached via oneOf parent', async () => {
674713
const document = parseYamlToDocument(
675714
outdent`

packages/core/src/rules/common/no-required-schema-properties-undefined.ts

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -43,34 +43,51 @@ export const NoRequiredSchemaPropertiesUndefined:
4343
return true;
4444
}
4545

46-
const check = (s: AnySchema) => hasProperty(s, propertyName, visited, resolved.location);
46+
if (
47+
resolved.schema.allOf?.some((s) =>
48+
hasProperty(s, propertyName, visited, resolved.location)
49+
)
50+
) {
51+
return true;
52+
}
4753

48-
if (resolved.schema.allOf?.some(check)) return true;
49-
if (resolved.schema.anyOf?.every(check)) return true;
50-
if (resolved.schema.oneOf?.every(check)) return true;
54+
if (
55+
resolved.schema.anyOf?.every((s) =>
56+
hasProperty(s, propertyName, new Set(visited), resolved.location)
57+
)
58+
) {
59+
return true;
60+
}
61+
62+
if (
63+
resolved.schema.oneOf?.every((s) =>
64+
hasProperty(s, propertyName, new Set(visited), resolved.location)
65+
)
66+
) {
67+
return true;
68+
}
5169

5270
return false;
5371
};
5472

5573
const isCompositionChild = (parent: AnySchema, child: AnySchema): boolean => {
56-
const matches = (s: AnySchema) => resolveSchema(s, ctx).schema === child;
74+
const matchesChild = (s: AnySchema) => resolveSchema(s, ctx).schema === child;
5775
return !!(
58-
parent.allOf?.some(matches) ||
59-
parent.anyOf?.some(matches) ||
60-
parent.oneOf?.some(matches)
76+
parent.allOf?.some(matchesChild) ||
77+
parent.anyOf?.some(matchesChild) ||
78+
parent.oneOf?.some(matchesChild)
6179
);
6280
};
6381

64-
const findCompositionRoot = (): AnySchema | undefined => {
65-
const walk = (i: number, child: AnySchema): AnySchema | undefined => {
66-
if (i < 0) return undefined;
67-
const parent = parents[i];
68-
return isCompositionChild(parent, child) ? (walk(i - 1, parent) ?? parent) : undefined;
69-
};
70-
return walk(parents.length - 2, schema);
82+
const findCompositionRoot = (i: number, child: AnySchema): AnySchema | undefined => {
83+
if (i < 0) return undefined;
84+
const parent = parents[i];
85+
return isCompositionChild(parent, child)
86+
? (findCompositionRoot(i - 1, parent) ?? parent)
87+
: undefined;
7188
};
7289

73-
const compositionRoot = findCompositionRoot();
90+
const compositionRoot = findCompositionRoot(parents.length - 2, schema);
7491

7592
for (const [i, requiredProperty] of schema.required.entries()) {
7693
if (

0 commit comments

Comments
 (0)