@redocly/openapi-core 1.0.0-beta.102 → 1.0.0-beta.103

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 (53) hide show
  1. package/lib/config/config.d.ts +3 -3
  2. package/lib/config/load.d.ts +1 -1
  3. package/lib/config/load.js +15 -2
  4. package/lib/config/rules.d.ts +1 -1
  5. package/lib/config/types.d.ts +4 -2
  6. package/lib/decorators/common/filters/filter-helper.d.ts +3 -0
  7. package/lib/decorators/common/filters/filter-helper.js +67 -0
  8. package/lib/decorators/common/filters/filter-in.d.ts +2 -0
  9. package/lib/decorators/common/filters/filter-in.js +17 -0
  10. package/lib/decorators/common/filters/filter-out.d.ts +2 -0
  11. package/lib/decorators/common/filters/filter-out.js +17 -0
  12. package/lib/decorators/oas2/index.d.ts +2 -0
  13. package/lib/decorators/oas2/index.js +5 -1
  14. package/lib/decorators/oas3/index.d.ts +2 -0
  15. package/lib/decorators/oas3/index.js +5 -1
  16. package/lib/index.d.ts +1 -1
  17. package/lib/lint.d.ts +2 -0
  18. package/lib/lint.js +2 -2
  19. package/lib/redocly/registry-api-types.d.ts +2 -0
  20. package/lib/redocly/registry-api.d.ts +1 -1
  21. package/lib/redocly/registry-api.js +3 -1
  22. package/lib/rules/common/assertions/asserts.d.ts +6 -1
  23. package/lib/rules/common/assertions/asserts.js +81 -51
  24. package/lib/rules/common/assertions/utils.d.ts +2 -1
  25. package/lib/rules/common/assertions/utils.js +27 -8
  26. package/lib/types/redocly-yaml.js +316 -27
  27. package/lib/utils.d.ts +2 -1
  28. package/lib/utils.js +5 -1
  29. package/lib/walk.d.ts +2 -0
  30. package/lib/walk.js +16 -7
  31. package/package.json +1 -1
  32. package/src/__tests__/lint.test.ts +17 -4
  33. package/src/config/__tests__/load.test.ts +6 -0
  34. package/src/config/load.ts +28 -5
  35. package/src/config/types.ts +6 -5
  36. package/src/decorators/__tests__/filter-in.test.ts +310 -0
  37. package/src/decorators/__tests__/filter-out.test.ts +227 -0
  38. package/src/decorators/common/filters/filter-helper.ts +72 -0
  39. package/src/decorators/common/filters/filter-in.ts +18 -0
  40. package/src/decorators/common/filters/filter-out.ts +18 -0
  41. package/src/decorators/oas2/index.ts +5 -1
  42. package/src/decorators/oas3/index.ts +5 -1
  43. package/src/index.ts +1 -0
  44. package/src/lint.ts +4 -3
  45. package/src/redocly/registry-api-types.ts +2 -0
  46. package/src/redocly/registry-api.ts +4 -0
  47. package/src/rules/common/assertions/__tests__/asserts.test.ts +149 -146
  48. package/src/rules/common/assertions/asserts.ts +97 -52
  49. package/src/rules/common/assertions/utils.ts +41 -16
  50. package/src/types/redocly-yaml.ts +321 -34
  51. package/src/utils.ts +10 -7
  52. package/src/walk.ts +39 -19
  53. package/tsconfig.tsbuildinfo +1 -1
@@ -1,6 +1,18 @@
1
- import { OrderOptions, OrderDirection, isOrdered, getIntersectionLength } from './utils';
1
+ import { Location } from '../../../ref-utils';
2
+ import { isString as runOnValue } from '../../../utils';
3
+ import {
4
+ OrderOptions,
5
+ OrderDirection,
6
+ isOrdered,
7
+ getIntersectionLength,
8
+ regexFromString,
9
+ } from './utils';
2
10
 
3
- type Asserts = Record<string, (value: any, condition: any) => boolean>;
11
+ type AssertResult = { isValid: boolean; location?: Location };
12
+ type Asserts = Record<
13
+ string,
14
+ (value: any, condition: any, baseLocation: Location, rawValue?: any) => AssertResult
15
+ >;
4
16
 
5
17
  export const runOnKeysSet = new Set([
6
18
  'mutuallyExclusive',
@@ -13,6 +25,8 @@ export const runOnKeysSet = new Set([
13
25
  'sortOrder',
14
26
  'disallowed',
15
27
  'required',
28
+ 'requireAny',
29
+ 'ref',
16
30
  ]);
17
31
  export const runOnValuesSet = new Set([
18
32
  'pattern',
@@ -24,73 +38,82 @@ export const runOnValuesSet = new Set([
24
38
  'maxLength',
25
39
  'casing',
26
40
  'sortOrder',
41
+ 'ref',
27
42
  ]);
28
43
 
29
44
  export const asserts: Asserts = {
30
- pattern: (value: string | string[], condition: string): boolean => {
31
- if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
32
- const values = typeof value === 'string' ? [value] : value;
33
- const regexOptions = condition.match(/(\b\/\b)(.+)/g) || ['/'];
34
- condition = condition.slice(1).replace(regexOptions[0], '');
35
- const regx = new RegExp(condition, regexOptions[0].slice(1));
45
+ pattern: (value: string | string[], condition: string, baseLocation: Location) => {
46
+ if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
47
+ const values = runOnValue(value) ? [value] : value;
48
+ const regx = regexFromString(condition);
36
49
  for (let _val of values) {
37
- if (!_val.match(regx)) {
38
- return false;
50
+ if (!regx?.test(_val)) {
51
+ return { isValid: false, location: runOnValue(value) ? baseLocation : baseLocation.key() };
39
52
  }
40
53
  }
41
- return true;
54
+ return { isValid: true };
42
55
  },
43
- enum: (value: string | string[], condition: string[]): boolean => {
44
- if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
45
- const values = typeof value === 'string' ? [value] : value;
56
+ enum: (value: string | string[], condition: string[], baseLocation: Location) => {
57
+ if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
58
+ const values = runOnValue(value) ? [value] : value;
46
59
  for (let _val of values) {
47
60
  if (!condition.includes(_val)) {
48
- return false;
61
+ return {
62
+ isValid: false,
63
+ location: runOnValue(value) ? baseLocation : baseLocation.child(_val).key(),
64
+ };
49
65
  }
50
66
  }
51
- return true;
67
+ return { isValid: true };
52
68
  },
53
- defined: (value: string | undefined, condition: boolean = true): boolean => {
69
+ defined: (value: string | undefined, condition: boolean = true, baseLocation: Location) => {
54
70
  const isDefined = typeof value !== 'undefined';
55
- return condition ? isDefined : !isDefined;
71
+ return { isValid: condition ? isDefined : !isDefined, location: baseLocation };
56
72
  },
57
- required: (value: string[], keys: string[]): boolean => {
73
+ required: (value: string[], keys: string[], baseLocation: Location) => {
58
74
  for (const requiredKey of keys) {
59
75
  if (!value.includes(requiredKey)) {
60
- return false;
76
+ return { isValid: false, location: baseLocation.key() };
61
77
  }
62
78
  }
63
- return true;
79
+ return { isValid: true };
64
80
  },
65
- disallowed: (value: string | string[], condition: string[]): boolean => {
66
- if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
67
- const values = typeof value === 'string' ? [value] : value;
81
+ disallowed: (value: string | string[], condition: string[], baseLocation: Location) => {
82
+ if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
83
+ const values = runOnValue(value) ? [value] : value;
68
84
  for (let _val of values) {
69
85
  if (condition.includes(_val)) {
70
- return false;
86
+ return {
87
+ isValid: false,
88
+ location: runOnValue(value) ? baseLocation : baseLocation.child(_val).key(),
89
+ };
71
90
  }
72
91
  }
73
- return true;
92
+ return { isValid: true };
74
93
  },
75
- undefined: (value: any, condition: boolean = true): boolean => {
94
+ undefined: (value: any, condition: boolean = true, baseLocation: Location) => {
76
95
  const isUndefined = typeof value === 'undefined';
77
- return condition ? isUndefined : !isUndefined;
96
+ return { isValid: condition ? isUndefined : !isUndefined, location: baseLocation };
78
97
  },
79
- nonEmpty: (value: string | undefined | null, condition: boolean = true): boolean => {
98
+ nonEmpty: (
99
+ value: string | undefined | null,
100
+ condition: boolean = true,
101
+ baseLocation: Location
102
+ ) => {
80
103
  const isEmpty = typeof value === 'undefined' || value === null || value === '';
81
- return condition ? !isEmpty : isEmpty;
104
+ return { isValid: condition ? !isEmpty : isEmpty, location: baseLocation };
82
105
  },
83
- minLength: (value: string | any[], condition: number): boolean => {
84
- if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
85
- return value.length >= condition;
106
+ minLength: (value: string | any[], condition: number, baseLocation: Location) => {
107
+ if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
108
+ return { isValid: value.length >= condition, location: baseLocation };
86
109
  },
87
- maxLength: (value: string | any[], condition: number): boolean => {
88
- if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
89
- return value.length <= condition;
110
+ maxLength: (value: string | any[], condition: number, baseLocation: Location) => {
111
+ if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
112
+ return { isValid: value.length <= condition, location: baseLocation };
90
113
  },
91
- casing: (value: string | string[], condition: string): boolean => {
92
- if (typeof value === 'undefined') return true; // property doesn't exist, no need to lint it with this assert
93
- const values = typeof value === 'string' ? [value] : value;
114
+ casing: (value: string | string[], condition: string, baseLocation: Location) => {
115
+ if (typeof value === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
116
+ const values: string[] = runOnValue(value) ? [value] : value;
94
117
  for (let _val of values) {
95
118
  let matchCase = false;
96
119
  switch (condition) {
@@ -117,24 +140,46 @@ export const asserts: Asserts = {
117
140
  break;
118
141
  }
119
142
  if (!matchCase) {
120
- return false;
143
+ return {
144
+ isValid: false,
145
+ location: runOnValue(value) ? baseLocation : baseLocation.child(_val).key(),
146
+ };
121
147
  }
122
148
  }
123
- return true;
149
+ return { isValid: true };
124
150
  },
125
- sortOrder: (value: any[], condition: OrderOptions | OrderDirection): boolean => {
126
- if (typeof value === 'undefined') return true;
127
- return isOrdered(value, condition);
151
+ sortOrder: (value: any[], condition: OrderOptions | OrderDirection, baseLocation: Location) => {
152
+ if (typeof value === 'undefined') return { isValid: true };
153
+ return { isValid: isOrdered(value, condition), location: baseLocation };
128
154
  },
129
- mutuallyExclusive: (value: string[], condition: string[]): boolean => {
130
- return getIntersectionLength(value, condition) < 2;
155
+ mutuallyExclusive: (value: string[], condition: string[], baseLocation: Location) => {
156
+ return { isValid: getIntersectionLength(value, condition) < 2, location: baseLocation.key() };
131
157
  },
132
- mutuallyRequired: (value: string[], condition: string[]): boolean => {
133
- return getIntersectionLength(value, condition) > 0
134
- ? getIntersectionLength(value, condition) === condition.length
135
- : true;
158
+ mutuallyRequired: (value: string[], condition: string[], baseLocation: Location) => {
159
+ return {
160
+ isValid:
161
+ getIntersectionLength(value, condition) > 0
162
+ ? getIntersectionLength(value, condition) === condition.length
163
+ : true,
164
+ location: baseLocation.key(),
165
+ };
136
166
  },
137
- requireAny: (value: string[], condition: string[]): boolean => {
138
- return getIntersectionLength(value, condition) >= 1;
167
+ requireAny: (value: string[], condition: string[], baseLocation: Location) => {
168
+ return { isValid: getIntersectionLength(value, condition) >= 1, location: baseLocation.key() };
169
+ },
170
+ ref: (_value: any, condition: string | boolean, baseLocation, rawValue: any) => {
171
+ if (typeof rawValue === 'undefined') return { isValid: true }; // property doesn't exist, no need to lint it with this assert
172
+ const hasRef = rawValue.hasOwnProperty('$ref');
173
+ if (typeof condition === 'boolean') {
174
+ return {
175
+ isValid: condition ? hasRef : !hasRef,
176
+ location: hasRef ? baseLocation : baseLocation.key(),
177
+ };
178
+ }
179
+ const regex = regexFromString(condition);
180
+ return {
181
+ isValid: hasRef && regex?.test(rawValue['$ref']),
182
+ location: hasRef ? baseLocation : baseLocation.key(),
183
+ };
139
184
  },
140
185
  };
@@ -1,4 +1,4 @@
1
- import { isRef } from '../../../ref-utils';
1
+ import { isRef, Location } from '../../../ref-utils';
2
2
  import { Problem, ProblemSeverity, UserContext } from '../../../walk';
3
3
  import { asserts } from './asserts';
4
4
 
@@ -23,7 +23,7 @@ export type AssertToApply = {
23
23
  export function buildVisitorObject(
24
24
  subject: string,
25
25
  context: Record<string, any>[],
26
- subjectVisitor: any,
26
+ subjectVisitor: any
27
27
  ) {
28
28
  if (!context) {
29
29
  return { [subject]: subjectVisitor };
@@ -46,7 +46,7 @@ export function buildVisitorObject(
46
46
 
47
47
  if (matchParentKeys && excludeParentKeys) {
48
48
  throw new Error(
49
- `Both 'matchParentKeys' and 'excludeParentKeys' can't be under one context item`,
49
+ `Both 'matchParentKeys' and 'excludeParentKeys' can't be under one context item`
50
50
  );
51
51
  }
52
52
 
@@ -75,9 +75,12 @@ export function buildVisitorObject(
75
75
  export function buildSubjectVisitor(
76
76
  properties: string | string[],
77
77
  asserts: AssertToApply[],
78
- context?: Record<string, any>[],
78
+ context?: Record<string, any>[]
79
79
  ) {
80
- return function (node: any, { report, location, key, type, resolve }: UserContext) {
80
+ return (
81
+ node: any,
82
+ { report, location, rawLocation, key, type, resolve, rawNode }: UserContext
83
+ ) => {
81
84
  // We need to check context's last node if it has the same type as subject node;
82
85
  // if yes - that means we didn't create context's last node visitor,
83
86
  // so we need to handle 'matchParentKeys' and 'excludeParentKeys' conditions here;
@@ -101,14 +104,28 @@ export function buildSubjectVisitor(
101
104
  }
102
105
 
103
106
  for (const assert of asserts) {
107
+ const currentLocation = assert.name === 'ref' ? rawLocation : location;
104
108
  if (properties) {
105
109
  for (const property of properties) {
106
110
  // we can have resolvable scalar so need to resolve value here.
107
111
  const value = isRef(node[property]) ? resolve(node[property])?.node : node[property];
108
- runAssertion(value, assert, location.child(property), report);
112
+ runAssertion({
113
+ values: value,
114
+ rawValues: rawNode[property],
115
+ assert,
116
+ location: currentLocation.child(property),
117
+ report,
118
+ });
109
119
  }
110
120
  } else {
111
- runAssertion(Object.keys(node), assert, location.key(), report);
121
+ const value = assert.name === 'ref' ? rawNode : Object.keys(node);
122
+ runAssertion({
123
+ values: Object.keys(node),
124
+ rawValues: value,
125
+ assert,
126
+ location: currentLocation,
127
+ report,
128
+ });
112
129
  }
113
130
  }
114
131
  };
@@ -148,20 +165,28 @@ export function isOrdered(value: any[], options: OrderOptions | OrderDirection):
148
165
  return true;
149
166
  }
150
167
 
151
- function runAssertion(
152
- values: string | string[],
153
- assert: AssertToApply,
154
- location: any,
155
- report: (problem: Problem) => void,
156
- ) {
157
- const lintResult = asserts[assert.name](values, assert.conditions);
158
- if (!lintResult) {
168
+ type RunAssertionParams = {
169
+ values: string | string[];
170
+ rawValues: any;
171
+ assert: AssertToApply;
172
+ location: Location;
173
+ report: (problem: Problem) => void;
174
+ };
175
+
176
+ function runAssertion({ values, rawValues, assert, location, report }: RunAssertionParams) {
177
+ const lintResult = asserts[assert.name](values, assert.conditions, location, rawValues);
178
+ if (!lintResult.isValid) {
159
179
  report({
160
180
  message: assert.message || `The ${assert.assertId} doesn't meet required conditions`,
161
- location,
181
+ location: lintResult.location || location,
162
182
  forceSeverity: assert.severity,
163
183
  suggest: assert.suggest,
164
184
  ruleId: assert.assertId,
165
185
  });
166
186
  }
167
187
  }
188
+
189
+ export function regexFromString(input: string): RegExp | null {
190
+ const matches = input.match(/^\/(.*)\/(.*)|(.*)/);
191
+ return matches && new RegExp(matches[1] || matches[3], matches[2]);
192
+ }