@redocly/openapi-core 1.8.2 → 1.9.1

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 (60) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/lib/bundle.d.ts +1 -1
  3. package/lib/config/all.js +1 -0
  4. package/lib/config/minimal.js +1 -0
  5. package/lib/config/recommended-strict.js +1 -0
  6. package/lib/config/recommended.js +1 -0
  7. package/lib/lint.d.ts +1 -0
  8. package/lib/lint.js +1 -1
  9. package/lib/oas-types.d.ts +1 -1
  10. package/lib/ref-utils.js +4 -4
  11. package/lib/resolve.js +9 -1
  12. package/lib/rules/common/no-required-schema-properties-undefined.d.ts +2 -0
  13. package/lib/rules/common/no-required-schema-properties-undefined.js +37 -0
  14. package/lib/rules/oas2/index.js +2 -0
  15. package/lib/rules/oas3/index.js +2 -0
  16. package/lib/types/index.d.ts +7 -7
  17. package/lib/types/json-schema-adapter.d.ts +3 -0
  18. package/lib/types/json-schema-adapter.js +173 -0
  19. package/lib/types/oas2.d.ts +3 -2
  20. package/lib/types/oas3.d.ts +3 -2
  21. package/lib/types/oas3_1.d.ts +3 -2
  22. package/lib/types/portal-config-schema.d.ts +5261 -52
  23. package/lib/types/portal-config-schema.js +71 -55
  24. package/lib/types/redocly-yaml.d.ts +14 -2
  25. package/lib/types/redocly-yaml.js +102 -39
  26. package/lib/types/theme-config.d.ts +819 -36
  27. package/lib/types/theme-config.js +67 -29
  28. package/lib/utils.d.ts +2 -2
  29. package/lib/visitors.js +1 -1
  30. package/lib/walk.js +7 -1
  31. package/package.json +1 -1
  32. package/src/__tests__/lint.test.ts +1218 -36
  33. package/src/__tests__/ref-utils.test.ts +22 -0
  34. package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +2 -0
  35. package/src/config/__tests__/load.test.ts +13 -13
  36. package/src/config/all.ts +1 -0
  37. package/src/config/minimal.ts +1 -0
  38. package/src/config/recommended-strict.ts +1 -0
  39. package/src/config/recommended.ts +1 -0
  40. package/src/decorators/oas2/remove-unused-components.ts +3 -2
  41. package/src/decorators/oas3/remove-unused-components.ts +3 -2
  42. package/src/lint.ts +2 -1
  43. package/src/ref-utils.ts +4 -4
  44. package/src/resolve.ts +13 -1
  45. package/src/rules/common/__tests__/no-required-schema-properties-undefined.test.ts +550 -0
  46. package/src/rules/common/no-required-schema-properties-undefined.ts +53 -0
  47. package/src/rules/oas2/index.ts +2 -0
  48. package/src/rules/oas3/index.ts +2 -0
  49. package/src/types/index.ts +7 -12
  50. package/src/types/json-schema-adapter.ts +217 -0
  51. package/src/types/oas2.ts +5 -2
  52. package/src/types/oas3.ts +6 -2
  53. package/src/types/oas3_1.ts +5 -2
  54. package/src/types/portal-config-schema.ts +111 -61
  55. package/src/types/redocly-yaml.ts +119 -43
  56. package/src/types/theme-config.ts +125 -27
  57. package/src/utils.ts +2 -2
  58. package/src/visitors.ts +1 -1
  59. package/src/walk.ts +7 -1
  60. package/tsconfig.tsbuildinfo +1 -1
@@ -96,6 +96,28 @@ describe('ref-utils', () => {
96
96
  expect(result).toMatchInlineSnapshot(`[]`);
97
97
  });
98
98
 
99
+ it('should parse a ref correctly', () => {
100
+ expect(parseRef('./info.yaml#/description')).toEqual({
101
+ uri: './info.yaml',
102
+ pointer: ['description'],
103
+ });
104
+ });
105
+
106
+ it('should parse a ref which contain a hash in the middle', () => {
107
+ // Here `info#description.md` is a file name
108
+ expect(parseRef('./info#description.md')).toEqual({
109
+ uri: './info#description.md',
110
+ pointer: [],
111
+ });
112
+ });
113
+
114
+ it('should parse a ref which ends with a hash', () => {
115
+ expect(parseRef('./info.yaml#')).toEqual({
116
+ uri: './info.yaml',
117
+ pointer: [],
118
+ });
119
+ });
120
+
99
121
  describe('refBaseName', () => {
100
122
  it('returns base name for file reference', () => {
101
123
  expect(refBaseName('../testcase/Pet.yaml')).toStrictEqual('Pet');
@@ -74,6 +74,7 @@ exports[`resolveConfig should ignore minimal from the root and read local file 1
74
74
  "no-invalid-parameter-examples": "off",
75
75
  "no-invalid-schema-examples": "off",
76
76
  "no-path-trailing-slash": "error",
77
+ "no-required-schema-properties-undefined": "off",
77
78
  "no-unresolved-refs": "error",
78
79
  "operation-2xx-response": "warn",
79
80
  "operation-4xx-response": "error",
@@ -198,6 +199,7 @@ exports[`resolveStyleguideConfig should resolve extends with local file config w
198
199
  "no-invalid-parameter-examples": "off",
199
200
  "no-invalid-schema-examples": "off",
200
201
  "no-path-trailing-slash": "error",
202
+ "no-required-schema-properties-undefined": "off",
201
203
  "no-unresolved-refs": "error",
202
204
  "operation-2xx-response": "error",
203
205
  "operation-4xx-response": "off",
@@ -137,6 +137,19 @@ describe('getConfig', () => {
137
137
  "severity": "warn",
138
138
  "suggest": [],
139
139
  },
140
+ {
141
+ "location": [
142
+ {
143
+ "pointer": "#/theme",
144
+ "reportOnKey": false,
145
+ "source": "fixtures/resolve-refs-in-config/config-with-refs.yaml",
146
+ },
147
+ ],
148
+ "message": "Can't resolve $ref: ENOENT: no such file or directory 'fixtures/resolve-refs-in-config/wrong-ref.yaml'",
149
+ "ruleId": "configuration no-unresolved-refs",
150
+ "severity": "warn",
151
+ "suggest": [],
152
+ },
140
153
  {
141
154
  "from": {
142
155
  "pointer": "#/rules",
@@ -154,19 +167,6 @@ describe('getConfig', () => {
154
167
  "severity": "warn",
155
168
  "suggest": [],
156
169
  },
157
- {
158
- "location": [
159
- {
160
- "pointer": "#/theme",
161
- "reportOnKey": false,
162
- "source": "fixtures/resolve-refs-in-config/config-with-refs.yaml",
163
- },
164
- ],
165
- "message": "Can't resolve $ref: ENOENT: no such file or directory 'fixtures/resolve-refs-in-config/wrong-ref.yaml'",
166
- "ruleId": "configuration no-unresolved-refs",
167
- "severity": "warn",
168
- "suggest": [],
169
- },
170
170
  ]
171
171
  `);
172
172
  });
package/src/config/all.ts CHANGED
@@ -43,6 +43,7 @@ const all: PluginStyleguideConfig<'built-in'> = {
43
43
  'path-params-defined': 'error',
44
44
  'required-string-property-missing-min-length': 'error',
45
45
  'response-contains-header': 'error',
46
+ 'no-required-schema-properties-undefined': 'error',
46
47
  },
47
48
  oas2Rules: {
48
49
  'boolean-parameter-prefixes': 'error',
@@ -40,6 +40,7 @@ const minimal: PluginStyleguideConfig<'built-in'> = {
40
40
  'response-contains-header': 'off',
41
41
  'path-segment-plural': 'off',
42
42
  'scalar-property-missing-example': 'off',
43
+ 'no-required-schema-properties-undefined': 'off',
43
44
  },
44
45
  oas2Rules: {
45
46
  'boolean-parameter-prefixes': 'off',
@@ -40,6 +40,7 @@ const recommendedStrict: PluginStyleguideConfig<'built-in'> = {
40
40
  'required-string-property-missing-min-length': 'off',
41
41
  'response-contains-header': 'off',
42
42
  'scalar-property-missing-example': 'off',
43
+ 'no-required-schema-properties-undefined': 'off',
43
44
  },
44
45
  oas2Rules: {
45
46
  'boolean-parameter-prefixes': 'off',
@@ -40,6 +40,7 @@ const recommended: PluginStyleguideConfig<'built-in'> = {
40
40
  'required-string-property-missing-min-length': 'off',
41
41
  'response-contains-header': 'off',
42
42
  'scalar-property-missing-example': 'off',
43
+ 'no-required-schema-properties-undefined': 'off',
43
44
  },
44
45
  oas2Rules: {
45
46
  'boolean-parameter-prefixes': 'off',
@@ -1,8 +1,9 @@
1
- import type { Oas2Decorator } from '../../visitors';
2
1
  import { Location } from '../../ref-utils';
3
- import type { Oas2Components } from '../../typings/swagger';
4
2
  import { isEmptyObject } from '../../utils';
5
3
 
4
+ import type { Oas2Decorator } from '../../visitors';
5
+ import type { Oas2Components } from '../../typings/swagger';
6
+
6
7
  export const RemoveUnusedComponents: Oas2Decorator = () => {
7
8
  const components = new Map<
8
9
  string,
@@ -1,8 +1,9 @@
1
- import type { Oas3Decorator } from '../../visitors';
2
1
  import { Location } from '../../ref-utils';
3
- import type { Oas3Components } from '../../typings/openapi';
4
2
  import { isEmptyObject } from '../../utils';
5
3
 
4
+ import type { Oas3Decorator } from '../../visitors';
5
+ import type { Oas3Components } from '../../typings/openapi';
6
+
6
7
  export const RemoveUnusedComponents: Oas3Decorator = () => {
7
8
  const components = new Map<
8
9
  string,
package/src/lint.ts CHANGED
@@ -112,6 +112,7 @@ export async function lintConfig(opts: {
112
112
  resolvedRefMap?: ResolvedRefMap;
113
113
  severity?: ProblemSeverity;
114
114
  externalRefResolver?: BaseResolver;
115
+ externalConfigTypes?: Record<string, NodeType>;
115
116
  }) {
116
117
  const { document, severity, externalRefResolver = new BaseResolver() } = opts;
117
118
 
@@ -126,7 +127,7 @@ export async function lintConfig(opts: {
126
127
  rules: { spec: 'error' },
127
128
  });
128
129
 
129
- const types = normalizeTypes(ConfigTypes, config);
130
+ const types = normalizeTypes(opts.externalConfigTypes || ConfigTypes, config);
130
131
  const rules: (RuleInstanceConfig & {
131
132
  visitor: NestedVisitObject<unknown, Oas3Visitor | Oas3Visitor[]>;
132
133
  })[] = [
package/src/ref-utils.ts CHANGED
@@ -43,15 +43,15 @@ export function escapePointer<T extends string | number>(fragment: T): T {
43
43
  }
44
44
 
45
45
  export function parseRef(ref: string): { uri: string | null; pointer: string[] } {
46
- const [uri, pointer] = ref.split('#/');
46
+ const [uri, pointer = ''] = ref.split('#/');
47
47
  return {
48
- uri: uri || null,
49
- pointer: pointer ? pointer.split('/').map(unescapePointer).filter(isTruthy) : [],
48
+ uri: (uri.endsWith('#') ? uri.slice(0, -1) : uri) || null,
49
+ pointer: parsePointer(pointer),
50
50
  };
51
51
  }
52
52
 
53
53
  export function parsePointer(pointer: string) {
54
- return pointer.substr(2).split('/').map(unescapePointer);
54
+ return pointer.split('/').map(unescapePointer).filter(isTruthy);
55
55
  }
56
56
 
57
57
  export function pointerBaseName(pointer: string) {
package/src/resolve.ts CHANGED
@@ -269,8 +269,20 @@ export async function resolveDocument(opts: {
269
269
  if (itemsType === undefined && type !== unknownType && type !== SpecExtension) {
270
270
  return;
271
271
  }
272
+ const isTypeAFunction = typeof itemsType === 'function';
272
273
  for (let i = 0; i < node.length; i++) {
273
- walk(node[i], itemsType || unknownType, joinPointer(nodeAbsoluteRef, i));
274
+ const itemType = isTypeAFunction
275
+ ? itemsType(node[i], joinPointer(nodeAbsoluteRef, i))
276
+ : itemsType;
277
+ // we continue resolving unknown types, but stop early on known scalars
278
+ if (itemType === undefined && type !== unknownType && type !== SpecExtension) {
279
+ continue;
280
+ }
281
+ walk(
282
+ node[i],
283
+ isNamedType(itemType) ? itemType : unknownType,
284
+ joinPointer(nodeAbsoluteRef, i)
285
+ );
274
286
  }
275
287
  return;
276
288
  }