@redocly/openapi-core 1.0.0-beta.111 → 1.0.0-beta.112

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/lib/config/all.js +0 -1
  2. package/lib/config/config-resolvers.js +19 -12
  3. package/lib/config/load.d.ts +1 -1
  4. package/lib/config/load.js +5 -5
  5. package/lib/config/minimal.js +0 -1
  6. package/lib/config/recommended.js +0 -1
  7. package/lib/rules/common/assertions/asserts.d.ts +22 -5
  8. package/lib/rules/common/assertions/asserts.js +25 -0
  9. package/lib/rules/common/assertions/index.d.ts +27 -2
  10. package/lib/rules/common/assertions/index.js +6 -29
  11. package/lib/rules/common/assertions/utils.d.ts +7 -14
  12. package/lib/rules/common/assertions/utils.js +129 -97
  13. package/lib/rules/oas2/index.d.ts +0 -1
  14. package/lib/rules/oas2/index.js +0 -2
  15. package/lib/rules/oas3/index.js +0 -2
  16. package/lib/types/redocly-yaml.js +44 -27
  17. package/lib/utils.d.ts +1 -0
  18. package/lib/utils.js +7 -1
  19. package/lib/visitors.d.ts +2 -1
  20. package/lib/visitors.js +1 -0
  21. package/lib/walk.js +7 -1
  22. package/package.json +1 -1
  23. package/src/__tests__/lint.test.ts +24 -5
  24. package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +1 -3
  25. package/src/config/__tests__/config-resolvers.test.ts +5 -5
  26. package/src/config/__tests__/fixtures/load-redocly.yaml +4 -0
  27. package/src/config/__tests__/fixtures/resolve-config/local-config-with-custom-function.yaml +6 -4
  28. package/src/config/__tests__/load.test.ts +4 -1
  29. package/src/config/all.ts +0 -1
  30. package/src/config/config-resolvers.ts +42 -19
  31. package/src/config/load.ts +8 -5
  32. package/src/config/minimal.ts +0 -1
  33. package/src/config/recommended.ts +0 -1
  34. package/src/rules/common/assertions/__tests__/asserts.test.ts +7 -3
  35. package/src/rules/common/assertions/__tests__/index.test.ts +41 -20
  36. package/src/rules/common/assertions/__tests__/utils.test.ts +43 -17
  37. package/src/rules/common/assertions/asserts.ts +60 -8
  38. package/src/rules/common/assertions/index.ts +36 -46
  39. package/src/rules/common/assertions/utils.ts +204 -127
  40. package/src/rules/oas2/index.ts +0 -2
  41. package/src/rules/oas3/index.ts +0 -2
  42. package/src/types/redocly-yaml.ts +44 -29
  43. package/src/utils.ts +5 -0
  44. package/src/visitors.ts +7 -1
  45. package/src/walk.ts +8 -1
  46. package/tsconfig.tsbuildinfo +1 -1
  47. package/lib/rules/common/info-description.d.ts +0 -2
  48. package/lib/rules/common/info-description.js +0 -12
  49. package/src/rules/common/__tests__/info-description.test.ts +0 -102
  50. package/src/rules/common/info-description.ts +0 -10
@@ -1,8 +1,15 @@
1
- import type { AssertResult, RuleSeverity } from '../../../config';
1
+ import { asserts, runOnKeysSet, runOnValuesSet, Asserts } from './asserts';
2
2
  import { colorize } from '../../../logger';
3
3
  import { isRef, Location } from '../../../ref-utils';
4
- import { UserContext } from '../../../walk';
5
- import { asserts } from './asserts';
4
+ import { isTruthy, keysOf, isString } from '../../../utils';
5
+ import type { AssertResult } from '../../../config';
6
+ import type { Assertion, AssertionDefinition, AssertionLocators } from '.';
7
+ import type {
8
+ Oas2Visitor,
9
+ Oas3Visitor,
10
+ SkipFunctionContext,
11
+ VisitFunction,
12
+ } from '../../../visitors';
6
13
 
7
14
  export type OrderDirection = 'asc' | 'desc';
8
15
 
@@ -11,166 +18,236 @@ export type OrderOptions = {
11
18
  property: string;
12
19
  };
13
20
 
14
- type Assertion = {
15
- property: string | string[];
16
- context?: Record<string, any>[];
17
- severity?: RuleSeverity;
18
- suggest?: any[];
19
- message?: string;
20
- subject: string;
21
- };
22
-
23
21
  export type AssertToApply = {
24
- name: string;
22
+ name: keyof Asserts;
25
23
  conditions: any;
26
24
  runsOnKeys: boolean;
27
25
  runsOnValues: boolean;
28
26
  };
29
27
 
28
+ type AssertionContext = SkipFunctionContext & {
29
+ node: any;
30
+ };
31
+
32
+ const assertionMessageTemplates = {
33
+ problems: '{{problems}}',
34
+ };
35
+
36
+ function getPredicatesFromLocators(
37
+ locators: AssertionLocators
38
+ ): ((key: string | number) => boolean)[] {
39
+ const { filterInParentKeys, filterOutParentKeys, matchParentKeys } = locators;
40
+
41
+ const keyMatcher = matchParentKeys && regexFromString(matchParentKeys);
42
+ const matchKeysPredicate =
43
+ keyMatcher && ((key: string | number) => keyMatcher.test(key.toString()));
44
+
45
+ const filterInPredicate =
46
+ Array.isArray(filterInParentKeys) &&
47
+ ((key: string | number) => filterInParentKeys.includes(key.toString()));
48
+
49
+ const filterOutPredicate =
50
+ Array.isArray(filterOutParentKeys) &&
51
+ ((key: string | number) => !filterOutParentKeys.includes(key.toString()));
52
+
53
+ return [matchKeysPredicate, filterInPredicate, filterOutPredicate].filter(isTruthy);
54
+ }
55
+
56
+ export function getAssertsToApply(assertion: AssertionDefinition): AssertToApply[] {
57
+ const assertsToApply = keysOf(asserts)
58
+ .filter((assertName) => assertion.assertions[assertName] !== undefined)
59
+ .map((assertName) => {
60
+ return {
61
+ name: assertName,
62
+ conditions: assertion.assertions[assertName],
63
+ runsOnKeys: runOnKeysSet.has(assertName),
64
+ runsOnValues: runOnValuesSet.has(assertName),
65
+ };
66
+ });
67
+
68
+ const shouldRunOnKeys: AssertToApply | undefined = assertsToApply.find(
69
+ (assert: AssertToApply) => assert.runsOnKeys && !assert.runsOnValues
70
+ );
71
+ const shouldRunOnValues: AssertToApply | undefined = assertsToApply.find(
72
+ (assert: AssertToApply) => assert.runsOnValues && !assert.runsOnKeys
73
+ );
74
+
75
+ if (shouldRunOnValues && !assertion.subject.property) {
76
+ throw new Error(
77
+ `${shouldRunOnValues.name} can't be used on all keys. Please provide a single property`
78
+ );
79
+ }
80
+
81
+ if (shouldRunOnKeys && assertion.subject.property) {
82
+ throw new Error(
83
+ `${shouldRunOnKeys.name} can't be used on a single property. Please use 'property'.`
84
+ );
85
+ }
86
+
87
+ return assertsToApply;
88
+ }
89
+
90
+ function getAssertionProperties({ subject }: AssertionDefinition): string[] {
91
+ return (Array.isArray(subject.property) ? subject.property : [subject?.property]).filter(
92
+ Boolean
93
+ ) as string[];
94
+ }
95
+
96
+ function applyAssertions(
97
+ assertionDefinition: AssertionDefinition,
98
+ asserts: AssertToApply[],
99
+ { rawLocation, rawNode, resolve, location, node }: AssertionContext
100
+ ): AssertResult[] {
101
+ const properties = getAssertionProperties(assertionDefinition);
102
+ const assertResults: Array<AssertResult[]> = [];
103
+
104
+ for (const assert of asserts) {
105
+ const currentLocation = assert.name === 'ref' ? rawLocation : location;
106
+
107
+ if (properties.length) {
108
+ for (const property of properties) {
109
+ // we can have resolvable scalar so need to resolve value here.
110
+ const value = isRef(node[property]) ? resolve(node[property])?.node : node[property];
111
+ assertResults.push(
112
+ runAssertion({
113
+ values: value,
114
+ rawValues: rawNode[property],
115
+ assert,
116
+ location: currentLocation.child(property),
117
+ })
118
+ );
119
+ }
120
+ } else {
121
+ const value = assert.name === 'ref' ? rawNode : Object.keys(node);
122
+ assertResults.push(
123
+ runAssertion({
124
+ values: Object.keys(node),
125
+ rawValues: value,
126
+ assert,
127
+ location: currentLocation,
128
+ })
129
+ );
130
+ }
131
+ }
132
+
133
+ return assertResults.flat();
134
+ }
135
+
30
136
  export function buildVisitorObject(
31
- subject: string,
32
- context: Record<string, any>[],
33
- subjectVisitor: any
34
- ) {
35
- if (!context) {
36
- return { [subject]: subjectVisitor };
137
+ assertion: Assertion,
138
+ subjectVisitor: VisitFunction<any>
139
+ ): Oas2Visitor | Oas3Visitor {
140
+ const targetVisitorLocatorPredicates = getPredicatesFromLocators(assertion.subject);
141
+ const targetVisitorSkipFunction = targetVisitorLocatorPredicates.length
142
+ ? (node: any, key: string | number) =>
143
+ !targetVisitorLocatorPredicates.every((predicate) => predicate(key))
144
+ : undefined;
145
+ const targetVisitor: Oas2Visitor | Oas3Visitor = {
146
+ [assertion.subject.type]: {
147
+ enter: subjectVisitor,
148
+ ...(targetVisitorSkipFunction && { skip: targetVisitorSkipFunction }),
149
+ },
150
+ };
151
+
152
+ if (!Array.isArray(assertion.where)) {
153
+ return targetVisitor;
37
154
  }
38
155
 
39
156
  let currentVisitorLevel: Record<string, any> = {};
40
157
  const visitor: Record<string, any> = currentVisitorLevel;
158
+ const context = assertion.where;
41
159
 
42
160
  for (let index = 0; index < context.length; index++) {
43
- const node = context[index];
44
- if (context.length === index + 1 && node.type === subject) {
45
- // Visitors don't work properly for the same type nested nodes, so
46
- // as a workaround for that we don't create separate visitor for the last element
47
- // which is the same as subject;
48
- // we will check includes/excludes it in the last visitor.
49
- continue;
50
- }
51
- const matchParentKeys = node.matchParentKeys;
52
- const excludeParentKeys = node.excludeParentKeys;
161
+ const assertionDefinitionNode = context[index];
53
162
 
54
- if (matchParentKeys && excludeParentKeys) {
163
+ if (!isString(assertionDefinitionNode.subject?.type)) {
55
164
  throw new Error(
56
- `Both 'matchParentKeys' and 'excludeParentKeys' can't be under one context item`
165
+ `${assertion.assertionId} -> where -> [${index}]: 'type' (String) is required`
57
166
  );
58
167
  }
59
168
 
60
- if (matchParentKeys || excludeParentKeys) {
61
- currentVisitorLevel[node.type] = {
62
- skip: (_value: any, key: string) => {
63
- if (matchParentKeys) {
64
- return !matchParentKeys.includes(key);
65
- }
66
- if (excludeParentKeys) {
67
- return excludeParentKeys.includes(key);
68
- }
69
- },
169
+ const locatorPredicates = getPredicatesFromLocators(assertionDefinitionNode.subject);
170
+ const assertsToApply = getAssertsToApply(assertionDefinitionNode);
171
+
172
+ const skipFunction = (
173
+ node: unknown,
174
+ key: string | number,
175
+ { location, rawLocation, resolve, rawNode }: SkipFunctionContext
176
+ ): boolean =>
177
+ !locatorPredicates.every((predicate) => predicate(key)) ||
178
+ !!applyAssertions(assertionDefinitionNode, assertsToApply, {
179
+ location,
180
+ node,
181
+ rawLocation,
182
+ rawNode,
183
+ resolve,
184
+ }).length;
185
+
186
+ const nodeVisitor = {
187
+ ...((locatorPredicates.length || assertsToApply.length) && { skip: skipFunction }),
188
+ };
189
+
190
+ if (
191
+ assertionDefinitionNode.subject.type === assertion.subject.type &&
192
+ index === context.length - 1
193
+ ) {
194
+ // We have to merge the visitors if the last node inside the `where` is the same as the subject.
195
+ targetVisitor[assertion.subject.type] = {
196
+ enter: subjectVisitor,
197
+ ...((nodeVisitor.skip && { skip: nodeVisitor.skip }) ||
198
+ (targetVisitorSkipFunction && {
199
+ skip: (
200
+ node,
201
+ key,
202
+ ctx // We may have locators defined on assertion level and on where level for the same node type
203
+ ) => !!(nodeVisitor.skip?.(node, key, ctx) || targetVisitorSkipFunction?.(node, key)),
204
+ })),
70
205
  };
71
206
  } else {
72
- currentVisitorLevel[node.type] = {};
207
+ currentVisitorLevel = currentVisitorLevel[assertionDefinitionNode.subject?.type] =
208
+ nodeVisitor;
73
209
  }
74
- currentVisitorLevel = currentVisitorLevel[node.type];
75
210
  }
76
211
 
77
- currentVisitorLevel[subject] = subjectVisitor;
212
+ currentVisitorLevel[assertion.subject.type] = targetVisitor[assertion.subject.type];
78
213
 
79
214
  return visitor;
80
215
  }
81
216
 
82
- export function buildSubjectVisitor(
83
- assertId: string,
84
- assertion: Assertion,
85
- asserts: AssertToApply[]
86
- ) {
87
- return (
88
- node: any,
89
- { report, location, rawLocation, key, type, resolve, rawNode }: UserContext
90
- ) => {
91
- let properties = assertion.property;
92
- // We need to check context's last node if it has the same type as subject node;
93
- // if yes - that means we didn't create context's last node visitor,
94
- // so we need to handle 'matchParentKeys' and 'excludeParentKeys' conditions here;
95
- if (assertion.context) {
96
- const lastContextNode = assertion.context[assertion.context.length - 1];
97
- if (lastContextNode.type === type.name) {
98
- const matchParentKeys = lastContextNode.matchParentKeys;
99
- const excludeParentKeys = lastContextNode.excludeParentKeys;
100
-
101
- if (matchParentKeys && !matchParentKeys.includes(key)) {
102
- return;
103
- }
104
- if (excludeParentKeys && excludeParentKeys.includes(key)) {
105
- return;
106
- }
107
- }
108
- }
109
-
110
- if (properties) {
111
- properties = Array.isArray(properties) ? properties : [properties];
112
- }
217
+ export function buildSubjectVisitor(assertId: string, assertion: Assertion): VisitFunction<any> {
218
+ return (node: any, { report, location, rawLocation, resolve, rawNode }) => {
219
+ const properties = getAssertionProperties(assertion);
113
220
 
114
221
  const defaultMessage = `${colorize.blue(assertId)} failed because the ${colorize.blue(
115
- assertion.subject
116
- )}${colorize.blue(
117
- properties ? ` ${(properties as string[]).join(', ')}` : ''
118
- )} didn't meet the assertions: {{problems}}`;
119
-
120
- const assertResults: Array<AssertResult[]> = [];
121
- for (const assert of asserts) {
122
- const currentLocation = assert.name === 'ref' ? rawLocation : location;
123
- if (properties) {
124
- for (const property of properties) {
125
- // we can have resolvable scalar so need to resolve value here.
126
- const value = isRef(node[property]) ? resolve(node[property])?.node : node[property];
127
- assertResults.push(
128
- runAssertion({
129
- values: value,
130
- rawValues: rawNode[property],
131
- assert,
132
- location: currentLocation.child(property),
133
- })
134
- );
135
- }
136
- } else {
137
- const value = assert.name === 'ref' ? rawNode : Object.keys(node);
138
- assertResults.push(
139
- runAssertion({
140
- values: Object.keys(node),
141
- rawValues: value,
142
- assert,
143
- location: currentLocation,
144
- })
145
- );
146
- }
147
- }
222
+ assertion.subject.type
223
+ )} ${colorize.blue(properties.join(', '))} didn't meet the assertions: ${
224
+ assertionMessageTemplates.problems
225
+ }`.replace(/ +/g, ' ');
226
+
227
+ const problems = applyAssertions(assertion, getAssertsToApply(assertion), {
228
+ rawLocation,
229
+ rawNode,
230
+ resolve,
231
+ location,
232
+ node,
233
+ });
148
234
 
149
- const problems = assertResults.flat();
150
235
  if (problems.length) {
151
236
  const message = assertion.message || defaultMessage;
152
-
153
- report({
154
- message: message.replace('{{problems}}', getProblemsMessage(problems)),
155
- location: getProblemsLocation(problems) || location,
156
- forceSeverity: assertion.severity || 'error',
157
- suggest: assertion.suggest || [],
158
- ruleId: assertId,
159
- });
237
+ for (const problem of problems) {
238
+ const problemMessage = problem.message ? problem.message : defaultMessage;
239
+ report({
240
+ message: message.replace(assertionMessageTemplates.problems, problemMessage),
241
+ location: problem.location || location,
242
+ forceSeverity: assertion.severity || 'error',
243
+ suggest: assertion.suggest || [],
244
+ ruleId: assertId,
245
+ });
246
+ }
160
247
  }
161
248
  };
162
249
  }
163
250
 
164
- function getProblemsLocation(problems: AssertResult[]) {
165
- return problems.length ? problems[0].location : undefined;
166
- }
167
-
168
- function getProblemsMessage(problems: AssertResult[]) {
169
- return problems.length === 1
170
- ? problems[0].message ?? ''
171
- : problems.map((problem) => `\n- ${problem.message ?? ''}`).join('');
172
- }
173
-
174
251
  export function getIntersectionLength(keys: string[], properties: string[]): number {
175
252
  const props = new Set(properties);
176
253
  let count = 0;
@@ -2,7 +2,6 @@ import { Oas2Rule } from '../../visitors';
2
2
  import { OasSpec } from '../common/spec';
3
3
  import { NoInvalidSchemaExamples } from '../common/no-invalid-schema-examples';
4
4
  import { NoInvalidParameterExamples } from '../common/no-invalid-parameter-examples';
5
- import { InfoDescription } from '../common/info-description';
6
5
  import { InfoContact } from '../common/info-contact';
7
6
  import { InfoLicense } from '../common/info-license';
8
7
  import { InfoLicenseUrl } from '../common/info-license-url';
@@ -45,7 +44,6 @@ export const rules = {
45
44
  spec: OasSpec as Oas2Rule,
46
45
  'no-invalid-schema-examples': NoInvalidSchemaExamples,
47
46
  'no-invalid-parameter-examples': NoInvalidParameterExamples,
48
- 'info-description': InfoDescription as Oas2Rule,
49
47
  'info-contact': InfoContact as Oas2Rule,
50
48
  'info-license': InfoLicense as Oas2Rule,
51
49
  'info-license-url': InfoLicenseUrl as Oas2Rule,
@@ -15,7 +15,6 @@ import { OperationIdUrlSafe } from '../common/operation-operationId-url-safe';
15
15
  import { TagsAlphabetical } from '../common/tags-alphabetical';
16
16
  import { NoServerExample } from './no-server-example.com';
17
17
  import { NoServerTrailingSlash } from './no-server-trailing-slash';
18
- import { InfoDescription } from '../common/info-description';
19
18
  import { TagDescription } from '../common/tag-description';
20
19
  import { InfoContact } from '../common/info-contact';
21
20
  import { InfoLicense } from '../common/info-license';
@@ -53,7 +52,6 @@ import { Operation4xxProblemDetailsRfc7807 } from './operation-4xx-problem-detai
53
52
 
54
53
  export const rules = {
55
54
  spec: OasSpec,
56
- 'info-description': InfoDescription,
57
55
  'info-contact': InfoContact,
58
56
  'info-license': InfoLicense,
59
57
  'info-license-url': InfoLicenseUrl,
@@ -3,7 +3,6 @@ import { omitObjectProps, pickObjectProps, isCustomRuleId } from '../utils';
3
3
 
4
4
  const builtInRulesList = [
5
5
  'spec',
6
- 'info-description',
7
6
  'info-contact',
8
7
  'info-license',
9
8
  'info-license-url',
@@ -147,16 +146,8 @@ const ConfigRoot: NodeType = {
147
146
  properties: {
148
147
  organization: { type: 'string' },
149
148
  apis: 'ConfigApis',
150
- apiDefinitions: {
151
- type: 'object',
152
- properties: {},
153
- additionalProperties: { properties: { type: 'string' } },
154
- }, // deprecated
155
149
  ...RootConfigStyleguide.properties,
156
- styleguide: 'RootConfigStyleguide', // deprecated
157
- lint: 'RootConfigStyleguide', // deprecated
158
150
  'features.openapi': 'ConfigReferenceDocs',
159
- referenceDocs: 'ConfigReferenceDocs', // deprecated
160
151
  'features.mockServer': 'ConfigMockServer',
161
152
  region: { enum: ['us', 'eu'] },
162
153
  resolve: {
@@ -239,15 +230,9 @@ const ObjectRule: NodeType = {
239
230
  required: ['severity'],
240
231
  };
241
232
 
242
- const Assert: NodeType = {
233
+ const AssertionDefinitionSubject: NodeType = {
243
234
  properties: {
244
- subject: (value: unknown) => {
245
- if (Array.isArray(value)) {
246
- return { type: 'array', items: { enum: nodeTypesList } };
247
- } else {
248
- return { enum: nodeTypesList };
249
- }
250
- },
235
+ type: { enum: nodeTypesList },
251
236
  property: (value: unknown) => {
252
237
  if (Array.isArray(value)) {
253
238
  return { type: 'array', items: { type: 'string' } };
@@ -257,10 +242,15 @@ const Assert: NodeType = {
257
242
  return { type: 'string' };
258
243
  }
259
244
  },
260
- context: listOf('Context'),
261
- message: { type: 'string' },
262
- suggest: { type: 'array', items: { type: 'string' } },
263
- severity: { enum: ['error', 'warn', 'off'] },
245
+ filterInParentKeys: { type: 'array', items: { type: 'string' } },
246
+ filterOutParentKeys: { type: 'array', items: { type: 'string' } },
247
+ matchParentKeys: { type: 'string' },
248
+ },
249
+ required: ['type'],
250
+ };
251
+
252
+ const AssertionDefinitionAssertions: NodeType = {
253
+ properties: {
264
254
  enum: { type: 'array', items: { type: 'string' } },
265
255
  pattern: { type: 'string' },
266
256
  casing: {
@@ -280,27 +270,50 @@ const Assert: NodeType = {
280
270
  requireAny: { type: 'array', items: { type: 'string' } },
281
271
  disallowed: { type: 'array', items: { type: 'string' } },
282
272
  defined: { type: 'boolean' },
283
- undefined: { type: 'boolean' },
273
+ // undefined: { type: 'boolean' }, // TODO: Remove `undefined` assertion from codebase overall
284
274
  nonEmpty: { type: 'boolean' },
285
275
  minLength: { type: 'integer' },
286
276
  maxLength: { type: 'integer' },
287
277
  ref: (value: string | boolean) =>
288
278
  typeof value === 'string' ? { type: 'string' } : { type: 'boolean' },
279
+ const: (value: string | boolean | number) => {
280
+ if (typeof value === 'string') {
281
+ return { type: 'string' };
282
+ }
283
+ if (typeof value === 'number') {
284
+ return { type: 'number' };
285
+ }
286
+ if (typeof value === 'boolean') {
287
+ return { type: 'boolean' };
288
+ } else {
289
+ return;
290
+ }
291
+ },
289
292
  },
290
293
  additionalProperties: (_value: unknown, key: string) => {
291
294
  if (/^\w+\/\w+$/.test(key)) return { type: 'object' };
292
295
  return;
293
296
  },
294
- required: ['subject'],
295
297
  };
296
298
 
297
- const Context: NodeType = {
299
+ const AssertDefinition: NodeType = {
298
300
  properties: {
299
- type: { enum: nodeTypesList },
300
- matchParentKeys: { type: 'array', items: { type: 'string' } },
301
- excludeParentKeys: { type: 'array', items: { type: 'string' } },
301
+ subject: 'AssertionDefinitionSubject',
302
+ assertions: 'AssertionDefinitionAssertions',
302
303
  },
303
- required: ['type'],
304
+ required: ['subject', 'assertions'],
305
+ };
306
+
307
+ const Assert: NodeType = {
308
+ properties: {
309
+ subject: 'AssertionDefinitionSubject',
310
+ assertions: 'AssertionDefinitionAssertions',
311
+ where: listOf('AssertDefinition'),
312
+ message: { type: 'string' },
313
+ suggest: { type: 'array', items: { type: 'string' } },
314
+ severity: { enum: ['error', 'warn', 'off'] },
315
+ },
316
+ required: ['subject', 'assertions'],
304
317
  };
305
318
 
306
319
  const ConfigLanguage: NodeType = {
@@ -926,7 +939,7 @@ export const ConfigTypes: Record<string, NodeType> = {
926
939
  ConfigSidebarLinks,
927
940
  CommonConfigSidebarLinks,
928
941
  ConfigTheme,
929
- Context,
942
+ AssertDefinition,
930
943
  ThemeColors,
931
944
  CommonThemeColors,
932
945
  BorderThemeColors,
@@ -973,4 +986,6 @@ export const ConfigTypes: Record<string, NodeType> = {
973
986
  Sidebar,
974
987
  Heading,
975
988
  Typography,
989
+ AssertionDefinitionAssertions,
990
+ AssertionDefinitionSubject,
976
991
  };
package/src/utils.ts CHANGED
@@ -232,6 +232,11 @@ export function identity<T>(value: T): T {
232
232
  return value;
233
233
  }
234
234
 
235
+ export function keysOf<T>(obj: T) {
236
+ if (!obj) return [];
237
+ return Object.keys(obj) as (keyof T)[];
238
+ }
239
+
235
240
  export function pickDefined<T extends Record<string, unknown>>(
236
241
  obj?: T
237
242
  ): Record<string, unknown> | undefined {
package/src/visitors.ts CHANGED
@@ -50,6 +50,11 @@ import type { Stack } from './utils';
50
50
  import type { UserContext, ResolveResult, ProblemSeverity } from './walk';
51
51
  import type { Location } from './ref-utils';
52
52
 
53
+ export type SkipFunctionContext = Pick<
54
+ UserContext,
55
+ 'location' | 'rawNode' | 'resolve' | 'rawLocation'
56
+ >;
57
+
53
58
  export type VisitFunction<T> = (
54
59
  node: T,
55
60
  ctx: UserContext & { ignoreNextVisitorsOnNode: () => void },
@@ -59,7 +64,7 @@ export type VisitFunction<T> = (
59
64
 
60
65
  type VisitRefFunction = (node: OasRef, ctx: UserContext, resolved: ResolveResult<any>) => void;
61
66
 
62
- type SkipFunction<T> = (node: T, key: string | number) => boolean;
67
+ type SkipFunction<T> = (node: T, key: string | number, ctx: SkipFunctionContext) => boolean;
63
68
 
64
69
  type VisitObject<T> = {
65
70
  enter?: VisitFunction<T>;
@@ -210,6 +215,7 @@ const legacyTypesMap = {
210
215
  HeadersMap: 'HeaderMap',
211
216
  LinksMap: 'LinkMap',
212
217
  OAuth2Flows: 'SecuritySchemeFlows',
218
+ Responses: 'ResponsesMap',
213
219
  };
214
220
 
215
221
  type Oas3NestedVisitor = {
package/src/walk.ts CHANGED
@@ -226,7 +226,14 @@ export function walkDocument<T>(opts: {
226
226
  nextLevelTypeActivated: null,
227
227
  withParentNode: context.parent?.activatedOn?.value.node,
228
228
  skipped:
229
- (context.parent?.activatedOn?.value.skipped || skip?.(resolvedNode, key)) ?? false,
229
+ (context.parent?.activatedOn?.value.skipped ||
230
+ skip?.(resolvedNode, key, {
231
+ location,
232
+ rawLocation,
233
+ resolve,
234
+ rawNode: node,
235
+ })) ??
236
+ false,
230
237
  };
231
238
 
232
239
  context.activatedOn = pushStack<any>(context.activatedOn, activatedOn);