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

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 (78) hide show
  1. package/lib/config/all.js +0 -1
  2. package/lib/config/config-resolvers.js +22 -18
  3. package/lib/config/config.d.ts +4 -10
  4. package/lib/config/config.js +1 -1
  5. package/lib/config/load.d.ts +1 -1
  6. package/lib/config/load.js +10 -10
  7. package/lib/config/minimal.js +0 -1
  8. package/lib/config/recommended.js +0 -1
  9. package/lib/config/rules.d.ts +6 -3
  10. package/lib/config/rules.js +3 -2
  11. package/lib/config/types.d.ts +3 -0
  12. package/lib/ref-utils.d.ts +1 -0
  13. package/lib/ref-utils.js +5 -1
  14. package/lib/resolve.js +19 -0
  15. package/lib/rules/common/assertions/asserts.d.ts +22 -5
  16. package/lib/rules/common/assertions/asserts.js +25 -0
  17. package/lib/rules/common/assertions/index.d.ts +27 -2
  18. package/lib/rules/common/assertions/index.js +6 -29
  19. package/lib/rules/common/assertions/utils.d.ts +7 -14
  20. package/lib/rules/common/assertions/utils.js +129 -97
  21. package/lib/rules/common/spec.js +6 -0
  22. package/lib/rules/oas2/index.d.ts +0 -1
  23. package/lib/rules/oas2/index.js +0 -2
  24. package/lib/rules/oas3/index.js +0 -2
  25. package/lib/rules/utils.js +3 -0
  26. package/lib/types/oas2.js +11 -7
  27. package/lib/types/oas3.js +15 -10
  28. package/lib/types/oas3_1.js +1 -0
  29. package/lib/types/redocly-yaml.js +49 -27
  30. package/lib/utils.d.ts +2 -0
  31. package/lib/utils.js +13 -1
  32. package/lib/visitors.d.ts +2 -1
  33. package/lib/visitors.js +1 -0
  34. package/lib/walk.js +7 -1
  35. package/package.json +1 -1
  36. package/src/__tests__/bundle.test.ts +46 -0
  37. package/src/__tests__/lint.test.ts +24 -5
  38. package/src/benchmark/benches/rebilly.yaml +36 -28
  39. package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +1 -3
  40. package/src/config/__tests__/config-resolvers.test.ts +6 -7
  41. package/src/config/__tests__/fixtures/load-redocly.yaml +2 -0
  42. package/src/config/__tests__/fixtures/resolve-config/local-config-with-custom-function.yaml +6 -5
  43. package/src/config/__tests__/fixtures/resolve-config/local-config-with-wrong-custom-function.yaml +0 -1
  44. package/src/config/__tests__/load.test.ts +4 -1
  45. package/src/config/all.ts +0 -1
  46. package/src/config/config-resolvers.ts +44 -31
  47. package/src/config/config.ts +6 -5
  48. package/src/config/load.ts +19 -9
  49. package/src/config/minimal.ts +0 -1
  50. package/src/config/recommended.ts +0 -1
  51. package/src/config/rules.ts +11 -3
  52. package/src/config/types.ts +2 -0
  53. package/src/ref-utils.ts +4 -0
  54. package/src/resolve.ts +25 -3
  55. package/src/rules/common/__tests__/spec.test.ts +170 -0
  56. package/src/rules/common/assertions/__tests__/asserts.test.ts +7 -3
  57. package/src/rules/common/assertions/__tests__/index.test.ts +41 -20
  58. package/src/rules/common/assertions/__tests__/utils.test.ts +43 -17
  59. package/src/rules/common/assertions/asserts.ts +60 -8
  60. package/src/rules/common/assertions/index.ts +36 -46
  61. package/src/rules/common/assertions/utils.ts +204 -127
  62. package/src/rules/common/spec.ts +7 -0
  63. package/src/rules/oas2/index.ts +0 -2
  64. package/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts +32 -0
  65. package/src/rules/oas3/index.ts +0 -2
  66. package/src/rules/utils.ts +4 -0
  67. package/src/types/oas2.ts +11 -7
  68. package/src/types/oas3.ts +15 -10
  69. package/src/types/oas3_1.ts +1 -0
  70. package/src/types/redocly-yaml.ts +49 -29
  71. package/src/utils.ts +11 -0
  72. package/src/visitors.ts +7 -1
  73. package/src/walk.ts +8 -1
  74. package/tsconfig.tsbuildinfo +1 -1
  75. package/lib/rules/common/info-description.d.ts +0 -2
  76. package/lib/rules/common/info-description.js +0 -12
  77. package/src/rules/common/__tests__/info-description.test.ts +0 -102
  78. package/src/rules/common/info-description.ts +0 -10
package/lib/utils.js CHANGED
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.pickDefined = exports.identity = exports.isTruthy = exports.showErrorForDeprecatedField = exports.showWarningForDeprecatedField = exports.doesYamlFileExist = exports.isCustomRuleId = exports.getMatchingStatusCodeRange = exports.assignExisting = exports.isNotString = exports.isString = exports.isNotEmptyObject = exports.slash = exports.isPathParameter = exports.readFileAsStringSync = exports.isSingular = exports.validateMimeTypeOAS3 = exports.validateMimeType = exports.splitCamelCaseIntoWords = exports.omitObjectProps = exports.pickObjectProps = exports.readFileFromUrl = exports.isEmptyArray = exports.isEmptyObject = exports.isPlainObject = exports.isDefined = exports.loadYaml = exports.popStack = exports.pushStack = exports.stringifyYaml = exports.parseYaml = void 0;
12
+ exports.nextTick = exports.pickDefined = exports.keysOf = exports.identity = exports.isTruthy = exports.showErrorForDeprecatedField = exports.showWarningForDeprecatedField = exports.doesYamlFileExist = exports.isCustomRuleId = exports.getMatchingStatusCodeRange = exports.assignExisting = exports.isNotString = exports.isString = exports.isNotEmptyObject = exports.slash = exports.isPathParameter = exports.readFileAsStringSync = exports.isSingular = exports.validateMimeTypeOAS3 = exports.validateMimeType = exports.splitCamelCaseIntoWords = exports.omitObjectProps = exports.pickObjectProps = exports.readFileFromUrl = exports.isEmptyArray = exports.isEmptyObject = exports.isPlainObject = exports.isDefined = exports.loadYaml = exports.popStack = exports.pushStack = exports.stringifyYaml = exports.parseYaml = void 0;
13
13
  const fs = require("fs");
14
14
  const path_1 = require("path");
15
15
  const minimatch = require("minimatch");
@@ -205,6 +205,12 @@ function identity(value) {
205
205
  return value;
206
206
  }
207
207
  exports.identity = identity;
208
+ function keysOf(obj) {
209
+ if (!obj)
210
+ return [];
211
+ return Object.keys(obj);
212
+ }
213
+ exports.keysOf = keysOf;
208
214
  function pickDefined(obj) {
209
215
  if (!obj)
210
216
  return undefined;
@@ -217,3 +223,9 @@ function pickDefined(obj) {
217
223
  return res;
218
224
  }
219
225
  exports.pickDefined = pickDefined;
226
+ function nextTick() {
227
+ new Promise((resolve) => {
228
+ setTimeout(resolve);
229
+ });
230
+ }
231
+ exports.nextTick = nextTick;
package/lib/visitors.d.ts CHANGED
@@ -4,11 +4,12 @@ import type { NormalizedNodeType } from './types';
4
4
  import type { Stack } from './utils';
5
5
  import type { UserContext, ResolveResult, ProblemSeverity } from './walk';
6
6
  import type { Location } from './ref-utils';
7
+ export declare type SkipFunctionContext = Pick<UserContext, 'location' | 'rawNode' | 'resolve' | 'rawLocation'>;
7
8
  export declare type VisitFunction<T> = (node: T, ctx: UserContext & {
8
9
  ignoreNextVisitorsOnNode: () => void;
9
10
  }, parents?: any, context?: any) => void;
10
11
  declare type VisitRefFunction = (node: OasRef, ctx: UserContext, resolved: ResolveResult<any>) => void;
11
- declare type SkipFunction<T> = (node: T, key: string | number) => boolean;
12
+ declare type SkipFunction<T> = (node: T, key: string | number, ctx: SkipFunctionContext) => boolean;
12
13
  declare type VisitObject<T> = {
13
14
  enter?: VisitFunction<T>;
14
15
  leave?: VisitFunction<T>;
package/lib/visitors.js CHANGED
@@ -12,6 +12,7 @@ const legacyTypesMap = {
12
12
  HeadersMap: 'HeaderMap',
13
13
  LinksMap: 'LinkMap',
14
14
  OAuth2Flows: 'SecuritySchemeFlows',
15
+ Responses: 'ResponsesMap',
15
16
  };
16
17
  function normalizeVisitors(visitorsConfig, types) {
17
18
  const normalizedVisitors = {};
package/lib/walk.js CHANGED
@@ -112,7 +112,13 @@ function walkDocument(opts) {
112
112
  location: resolvedLocation,
113
113
  nextLevelTypeActivated: null,
114
114
  withParentNode: (_g = (_f = context.parent) === null || _f === void 0 ? void 0 : _f.activatedOn) === null || _g === void 0 ? void 0 : _g.value.node,
115
- skipped: (_k = (((_j = (_h = context.parent) === null || _h === void 0 ? void 0 : _h.activatedOn) === null || _j === void 0 ? void 0 : _j.value.skipped) || (skip === null || skip === void 0 ? void 0 : skip(resolvedNode, key)))) !== null && _k !== void 0 ? _k : false,
115
+ skipped: (_k = (((_j = (_h = context.parent) === null || _h === void 0 ? void 0 : _h.activatedOn) === null || _j === void 0 ? void 0 : _j.value.skipped) ||
116
+ (skip === null || skip === void 0 ? void 0 : skip(resolvedNode, key, {
117
+ location,
118
+ rawLocation,
119
+ resolve,
120
+ rawNode: node,
121
+ })))) !== null && _k !== void 0 ? _k : false,
116
122
  };
117
123
  context.activatedOn = utils_1.pushStack(context.activatedOn, activatedOn);
118
124
  let ctx = context.parent;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/openapi-core",
3
- "version": "1.0.0-beta.111",
3
+ "version": "1.0.0-beta.113",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "engines": {
@@ -187,4 +187,50 @@ describe('bundle', () => {
187
187
  expect(problems).toHaveLength(0);
188
188
  expect(parsedMeta).toMatchSnapshot();
189
189
  });
190
+
191
+ it('should bundle refs using $anchors', async () => {
192
+ const testDocument = parseYamlToDocument(
193
+ outdent`
194
+ openapi: 3.1.0
195
+ components:
196
+ schemas:
197
+ User:
198
+ type: object
199
+ properties:
200
+ profile:
201
+ $ref: '#user-profile'
202
+ UserProfile:
203
+ $anchor: user-profile
204
+ type: string
205
+ `,
206
+ ''
207
+ );
208
+
209
+ const config = await makeConfig({});
210
+
211
+ const {
212
+ bundle: { parsed },
213
+ problems,
214
+ } = await bundleDocument({
215
+ document: testDocument,
216
+ config: config,
217
+ externalRefResolver: new BaseResolver(),
218
+ });
219
+
220
+ expect(problems).toHaveLength(0);
221
+ expect(parsed).toMatchInlineSnapshot(`
222
+ openapi: 3.1.0
223
+ components:
224
+ schemas:
225
+ User:
226
+ type: object
227
+ properties:
228
+ profile:
229
+ $ref: '#user-profile'
230
+ UserProfile:
231
+ $anchor: user-profile
232
+ type: string
233
+
234
+ `);
235
+ });
190
236
  });
@@ -61,11 +61,13 @@ describe('lint', () => {
61
61
  path-http-verbs-order: error
62
62
  boolean-parameter-prefixes: off
63
63
  assert/operation-summary-length:
64
- subject: Operation
65
- property: summary
64
+ subject:
65
+ type: Operation
66
+ property: summary
66
67
  message: Operation summary should start with an active verb
67
- local/checkWordsCount:
68
- min: 3
68
+ assertions:
69
+ local/checkWordsCount:
70
+ min: 3
69
71
  features.openapi:
70
72
  showConsole: true
71
73
  layout:
@@ -247,7 +249,24 @@ describe('lint', () => {
247
249
  );
248
250
  const results = await lintConfig({ document });
249
251
 
250
- expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
252
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
253
+ Array [
254
+ Object {
255
+ "from": undefined,
256
+ "location": Array [
257
+ Object {
258
+ "pointer": "#/referenceDocs",
259
+ "reportOnKey": true,
260
+ "source": "",
261
+ },
262
+ ],
263
+ "message": "Property \`referenceDocs\` is not expected here.",
264
+ "ruleId": "configuration spec",
265
+ "severity": "error",
266
+ "suggest": Array [],
267
+ },
268
+ ]
269
+ `);
251
270
  });
252
271
 
253
272
  it("'plugins' shouldn't be allowed in 'apis'", async () => {
@@ -4238,10 +4238,11 @@ components:
4238
4238
  type: integer
4239
4239
  readOnly: true
4240
4240
  dueReminderTime:
4241
- description: Time past due reminder event will be triggered
4241
+ description: Date and time at which a past due reminder event is triggered.
4242
4242
  nullable: true
4243
- allOf:
4244
- - $ref: '#/components/schemas/ServerTimestamp'
4243
+ type: string
4244
+ format: date-time
4245
+ readOnly: true
4245
4246
  dueReminderNumber:
4246
4247
  description: Number of past due reminder events triggered
4247
4248
  type: integer
@@ -9524,10 +9525,11 @@ components:
9524
9525
  allOf:
9525
9526
  - $ref: '#/components/schemas/ServerTimestamp'
9526
9527
  expirationReminderTime:
9527
- description: Time expiration reminder event will be triggered
9528
+ description: Date and time at which an expiration reminder event is triggered.
9528
9529
  nullable: true
9529
- allOf:
9530
- - $ref: '#/components/schemas/ServerTimestamp'
9530
+ type: string
9531
+ format: date-time
9532
+ readOnly: true
9531
9533
  expirationReminderNumber:
9532
9534
  description: Number of expiration reminder events triggered
9533
9535
  type: integer
@@ -10226,9 +10228,10 @@ components:
10226
10228
  readOnly: true
10227
10229
  reviewTime:
10228
10230
  description: Date and time of manual review.
10231
+ type: string
10229
10232
  nullable: true
10230
- allOf:
10231
- - $ref: '#/components/schemas/ServerTimestamp'
10233
+ format: date-time
10234
+ readOnly: true
10232
10235
  documentMatches:
10233
10236
  $ref: '#/components/schemas/KycDocumentMatches'
10234
10237
  _links:
@@ -10587,19 +10590,22 @@ components:
10587
10590
  deliveryAddress:
10588
10591
  description: Delivery address
10589
10592
  nullable: true
10593
+ type: object
10590
10594
  allOf:
10591
10595
  - $ref: '#/components/schemas/ContactObject'
10592
10596
  billingAddress:
10593
10597
  description: Billing address
10594
10598
  nullable: true
10599
+ type: object
10595
10600
  allOf:
10596
10601
  - $ref: '#/components/schemas/ContactObject'
10597
10602
  riskMetadata:
10598
10603
  nullable: true
10604
+ type: object
10599
10605
  example: null
10600
- description: >-
10601
- Risk metadata. If null, the value would coalesce to the risk
10602
- metadata captured when creating the payment token.
10606
+ description: |-
10607
+ Risk metadata.
10608
+ If null, the value would coalesce to the risk metadata captured when creating the payment token.
10603
10609
  allOf:
10604
10610
  - $ref: '#/components/schemas/RiskMetadata'
10605
10611
  activationTime:
@@ -10842,18 +10848,16 @@ components:
10842
10848
  type: string
10843
10849
  format: date-time
10844
10850
  invoiceTimeShift:
10845
- description: >
10846
- You can shift issue time and due time of invoices for this
10847
- subscription.
10851
+ description: |-
10852
+ You can shift issue time and due time of invoices for this subscription.
10848
10853
 
10849
- This setting overrides plan settings. To use plan settings, set
10850
- `null`.
10851
-
10852
- To use multiple plans in one subscription they all must have the
10853
- same billing period,
10854
+ This setting overrides plan settings.
10855
+ To use plan settings, set `null`.
10854
10856
 
10857
+ To use multiple plans in one subscription they all must have the same billing period,
10855
10858
  this property allows to subscribe to different plans.
10856
10859
  nullable: true
10860
+ type: object
10857
10861
  example: null
10858
10862
  allOf:
10859
10863
  - $ref: '#/components/schemas/InvoiceTimeShift'
@@ -10898,19 +10902,21 @@ components:
10898
10902
  type: integer
10899
10903
  readOnly: true
10900
10904
  renewalReminderTime:
10901
- description: Time renewal reminder event will be triggered
10905
+ description: Time renewal reminder event will be triggered.
10902
10906
  nullable: true
10903
- allOf:
10904
- - $ref: '#/components/schemas/ServerTimestamp'
10907
+ type: string
10908
+ format: date-time
10909
+ readOnly: true
10905
10910
  renewalReminderNumber:
10906
10911
  description: Number of renewal reminder events triggered
10907
10912
  type: integer
10908
10913
  readOnly: true
10909
10914
  trialReminderTime:
10910
- description: Time renewal reminder event will be triggered
10915
+ description: Time renewal reminder event will be triggered.
10911
10916
  nullable: true
10912
- allOf:
10913
- - $ref: '#/components/schemas/ServerTimestamp'
10917
+ type: string
10918
+ format: date-time
10919
+ readOnly: true
10914
10920
  trialReminderNumber:
10915
10921
  description: Number of renewal reminder events triggered
10916
10922
  type: integer
@@ -11807,10 +11813,12 @@ components:
11807
11813
  paymentInstrument:
11808
11814
  $ref: '#/components/schemas/PaymentInstrument'
11809
11815
  billingAddress:
11810
- description: >-
11811
- Billing Address. If not supplied, we use the billing address
11812
- associated with the payment instrument, and then customer.
11816
+ description: |-
11817
+ Billing address.
11818
+ If not supplied, the billing address associated
11819
+ with the payment instrument is used, and then customer.
11813
11820
  nullable: true
11821
+ type: object
11814
11822
  allOf:
11815
11823
  - $ref: '#/components/schemas/ContactObject'
11816
11824
  requestId:
@@ -39,7 +39,6 @@ Object {
39
39
  "assertions": "warn",
40
40
  "boolean-parameter-prefixes": "error",
41
41
  "info-contact": "off",
42
- "info-description": "warn",
43
42
  "info-license": "warn",
44
43
  "info-license-url": "warn",
45
44
  "local/operation-id-not-test": "error",
@@ -73,7 +72,7 @@ Object {
73
72
  }
74
73
  `;
75
74
 
76
- exports[`resolveStyleguideConfig should resolve extends with local file config witch contains path to nested config 1`] = `
75
+ exports[`resolveStyleguideConfig should resolve extends with local file config which contains path to nested config 1`] = `
77
76
  Object {
78
77
  "decorators": Object {},
79
78
  "doNotResolveExamples": undefined,
@@ -129,7 +128,6 @@ Object {
129
128
  ],
130
129
  "boolean-parameter-prefixes": "error",
131
130
  "info-contact": "off",
132
- "info-description": "warn",
133
131
  "info-license": "warn",
134
132
  "info-license-url": "warn",
135
133
  "local/operation-id-not-test": "error",
@@ -1,5 +1,5 @@
1
1
  import { colorize } from '../../logger';
2
- import { asserts } from '../../rules/common/assertions/asserts';
2
+ import { Asserts, asserts } from '../../rules/common/assertions/asserts';
3
3
  import { resolveStyleguideConfig, resolveApis, resolveConfig } from '../config-resolvers';
4
4
  const path = require('path');
5
5
 
@@ -100,7 +100,7 @@ describe('resolveStyleguideConfig', () => {
100
100
  }).toThrow('Circular dependency in config file');
101
101
  });
102
102
 
103
- it('should resolve extends with local file config witch contains path to nested config', async () => {
103
+ it('should resolve extends with local file config which contains path to nested config', async () => {
104
104
  const styleguideConfig = {
105
105
  extends: ['local-config-with-file.yaml'],
106
106
  };
@@ -143,7 +143,7 @@ describe('resolveStyleguideConfig', () => {
143
143
 
144
144
  expect(plugins).toBeDefined();
145
145
  expect(plugins?.length).toBe(2);
146
- expect(asserts['test-plugin/checkWordsCount']).toBeDefined();
146
+ expect(asserts['test-plugin/checkWordsCount' as keyof Asserts]).toBeDefined();
147
147
  });
148
148
 
149
149
  it('should throw error when custom assertion load not exist plugin', async () => {
@@ -163,7 +163,7 @@ describe('resolveStyleguideConfig', () => {
163
163
  );
164
164
  }
165
165
 
166
- expect(asserts['test-plugin/checkWordsCount']).toBeDefined();
166
+ expect(asserts['test-plugin/checkWordsCount' as keyof Asserts]).toBeDefined();
167
167
  });
168
168
 
169
169
  it('should correctly merge assertions from nested config', async () => {
@@ -197,7 +197,7 @@ describe('resolveStyleguideConfig', () => {
197
197
  ]);
198
198
  });
199
199
 
200
- it('should resolve extends with url file config witch contains path to nested config', async () => {
200
+ it('should resolve extends with url file config which contains path to nested config', async () => {
201
201
  const styleguideConfig = {
202
202
  // This points to ./fixtures/resolve-remote-configs/remote-config.yaml
203
203
  extends: [
@@ -342,7 +342,7 @@ describe('resolveApis', () => {
342
342
  });
343
343
 
344
344
  describe('resolveConfig', () => {
345
- it('should add recommended to top level by default', async () => {
345
+ it('should NOT add recommended to top level by default IF there is a config file', async () => {
346
346
  const rawConfig: RawConfig = {
347
347
  apis: {
348
348
  petstore: {
@@ -373,7 +373,6 @@ describe('resolveConfig', () => {
373
373
  expect(apis['petstore'].styleguide.pluginPaths!.map(removeAbsolutePath)).toEqual([]);
374
374
 
375
375
  expect(apis['petstore'].styleguide.rules).toEqual({
376
- ...(await recommendedStyleguidePreset).rules,
377
376
  'operation-2xx-response': 'warn',
378
377
  'operation-4xx-response': 'error',
379
378
  });
@@ -0,0 +1,2 @@
1
+ rules:
2
+ info-contact: warn
@@ -1,14 +1,15 @@
1
- lint:
2
1
  rules:
3
2
  no-invalid-media-type-examples: warn
4
3
  operation-4xx-response: off
5
4
  assert/tag-description:
6
- subject: Tag
7
- property: description
5
+ subject:
6
+ type: Tag
7
+ property: description
8
8
  message: Tag description must have at least 3 words.
9
9
  severity: error
10
- test-plugin/checkWordsCount:
11
- min: 3
10
+ assertions:
11
+ test-plugin/checkWordsCount:
12
+ min: 3
12
13
  plugins:
13
14
  - plugin.js
14
15
  extends:
@@ -1,4 +1,3 @@
1
- lint:
2
1
  rules:
3
2
  no-invalid-media-type-examples: warn
4
3
  operation-4xx-response: off
@@ -49,7 +49,10 @@ describe('loadConfig', () => {
49
49
 
50
50
  it('should call callback if such passed', async () => {
51
51
  const mockFn = jest.fn();
52
- await loadConfig({ processRawConfig: mockFn });
52
+ await loadConfig({
53
+ configPath: path.join(__dirname, './fixtures/load-redocly.yaml'),
54
+ processRawConfig: mockFn,
55
+ });
53
56
  expect(mockFn).toHaveBeenCalled();
54
57
  });
55
58
  });
package/src/config/all.ts CHANGED
@@ -2,7 +2,6 @@ import type { PluginStyleguideConfig } from './types';
2
2
 
3
3
  export default {
4
4
  rules: {
5
- 'info-description': 'error',
6
5
  'info-contact': 'error',
7
6
  'info-license': 'error',
8
7
  'info-license-url': 'error',
@@ -22,10 +22,16 @@ import type {
22
22
  DeprecatedInRawConfig,
23
23
  } from './types';
24
24
  import { isBrowser } from '../env';
25
- import { isNotString, isString, isDefined, parseYaml } from '../utils';
25
+ import { isNotString, isString, isDefined, parseYaml, keysOf } from '../utils';
26
26
  import { Config } from './config';
27
27
  import { colorize, logger } from '../logger';
28
- import { asserts, buildAssertCustomFunction } from '../rules/common/assertions/asserts';
28
+ import {
29
+ Asserts,
30
+ AssertionFn,
31
+ asserts,
32
+ buildAssertCustomFunction,
33
+ } from '../rules/common/assertions/asserts';
34
+ import type { Assertion, AssertionDefinition, RawAssertion } from '../rules/common/assertions';
29
35
 
30
36
  export async function resolveConfig(rawConfig: RawConfig, configPath?: string): Promise<Config> {
31
37
  if (rawConfig.styleguide?.extends?.some(isNotString)) {
@@ -35,25 +41,15 @@ export async function resolveConfig(rawConfig: RawConfig, configPath?: string):
35
41
  }
36
42
 
37
43
  const resolver = new BaseResolver(getResolveConfig(rawConfig.resolve));
38
- const configExtends = rawConfig?.styleguide?.extends ?? ['recommended'];
39
- const recommendedFallback = !rawConfig?.styleguide?.extends;
40
- const styleguideConfig = {
41
- ...rawConfig?.styleguide,
42
- extends: configExtends,
43
- recommendedFallback,
44
- };
45
44
 
46
45
  const apis = await resolveApis({
47
- rawConfig: {
48
- ...rawConfig,
49
- styleguide: styleguideConfig,
50
- },
46
+ rawConfig,
51
47
  configPath,
52
48
  resolver,
53
49
  });
54
50
 
55
51
  const styleguide = await resolveStyleguideConfig({
56
- styleguideConfig,
52
+ styleguideConfig: rawConfig.styleguide,
57
53
  configPath,
58
54
  resolver,
59
55
  });
@@ -409,26 +405,17 @@ function groupStyleguideAssertionRules({
409
405
  const transformedRules: Record<string, RuleConfig> = {};
410
406
 
411
407
  // Collect assertion rules
412
- const assertions = [];
408
+ const assertions: Assertion[] = [];
413
409
  for (const [ruleKey, rule] of Object.entries(rules)) {
414
410
  if (ruleKey.startsWith('assert/') && typeof rule === 'object' && rule !== null) {
415
- const assertion = rule;
411
+ const assertion = rule as RawAssertion;
412
+
416
413
  if (plugins) {
417
- for (const field of Object.keys(assertion)) {
418
- const [pluginId, fn] = field.split('/');
419
- if (!pluginId || !fn) continue;
420
- const plugin = plugins.find((plugin) => plugin.id === pluginId);
421
- if (!plugin) {
422
- throw Error(colorize.red(`Plugin ${colorize.blue(pluginId)} isn't found.`));
423
- }
424
- if (!plugin.assertions || !plugin.assertions[fn]) {
425
- throw Error(
426
- `Plugin ${colorize.red(
427
- pluginId
428
- )} doesn't export assertions function with name ${colorize.red(fn)}.`
429
- );
430
- }
431
- asserts[field] = buildAssertCustomFunction(plugin.assertions[fn]);
414
+ registerCustomAssertions(plugins, assertion);
415
+
416
+ // We may have custom assertion inside where block
417
+ for (const context of assertion.where || []) {
418
+ registerCustomAssertions(plugins, context);
432
419
  }
433
420
  }
434
421
  assertions.push({
@@ -446,3 +433,29 @@ function groupStyleguideAssertionRules({
446
433
 
447
434
  return transformedRules;
448
435
  }
436
+
437
+ function registerCustomAssertions(plugins: Plugin[], assertion: AssertionDefinition) {
438
+ for (const field of keysOf(assertion.assertions)) {
439
+ const [pluginId, fn] = field.split('/');
440
+
441
+ if (!pluginId || !fn) continue;
442
+
443
+ const plugin = plugins.find((plugin) => plugin.id === pluginId);
444
+
445
+ if (!plugin) {
446
+ throw Error(colorize.red(`Plugin ${colorize.blue(pluginId)} isn't found.`));
447
+ }
448
+
449
+ if (!plugin.assertions || !plugin.assertions[fn]) {
450
+ throw Error(
451
+ `Plugin ${colorize.red(
452
+ pluginId
453
+ )} doesn't export assertions function with name ${colorize.red(fn)}.`
454
+ );
455
+ }
456
+
457
+ (asserts as Asserts & { [name: string]: AssertionFn })[field] = buildAssertCustomFunction(
458
+ plugin.assertions[fn]
459
+ );
460
+ }
461
+ }
@@ -4,7 +4,7 @@ import { parseYaml, stringifyYaml } from '../js-yaml';
4
4
  import { slash, doesYamlFileExist } from '../utils';
5
5
  import { NormalizedProblem } from '../walk';
6
6
  import { OasVersion, OasMajorVersion, Oas2RuleSet, Oas3RuleSet } from '../oas-types';
7
- import { env } from '../env';
7
+ import { isBrowser, env } from '../env';
8
8
 
9
9
  import type { NodeType } from '../types';
10
10
  import type {
@@ -17,6 +17,7 @@ import type {
17
17
  ResolvedConfig,
18
18
  ResolvedStyleguideConfig,
19
19
  RuleConfig,
20
+ RuleSettings,
20
21
  } from './types';
21
22
  import { getResolveConfig } from './utils';
22
23
 
@@ -50,7 +51,7 @@ function getIgnoreFilePath(configFile?: string): string | undefined {
50
51
  ? path.join(path.dirname(configFile), IGNORE_FILE)
51
52
  : path.join(configFile, IGNORE_FILE);
52
53
  } else {
53
- return typeof process === 'undefined' ? undefined : path.join(process.cwd(), IGNORE_FILE);
54
+ return isBrowser ? undefined : path.join(process.cwd(), IGNORE_FILE);
54
55
  }
55
56
  }
56
57
 
@@ -183,7 +184,7 @@ export class StyleguideConfig {
183
184
  return extendedTypes;
184
185
  }
185
186
 
186
- getRuleSettings(ruleId: string, oasVersion: OasVersion) {
187
+ getRuleSettings(ruleId: string, oasVersion: OasVersion): RuleSettings {
187
188
  this._usedRules.add(ruleId);
188
189
  this._usedVersions.add(oasVersion);
189
190
  const settings = this.rules[oasVersion][ruleId] || 'off';
@@ -196,7 +197,7 @@ export class StyleguideConfig {
196
197
  }
197
198
  }
198
199
 
199
- getPreprocessorSettings(ruleId: string, oasVersion: OasVersion) {
200
+ getPreprocessorSettings(ruleId: string, oasVersion: OasVersion): RuleSettings {
200
201
  this._usedRules.add(ruleId);
201
202
  this._usedVersions.add(oasVersion);
202
203
 
@@ -210,7 +211,7 @@ export class StyleguideConfig {
210
211
  }
211
212
  }
212
213
 
213
- getDecoratorSettings(ruleId: string, oasVersion: OasVersion) {
214
+ getDecoratorSettings(ruleId: string, oasVersion: OasVersion): RuleSettings {
214
215
  this._usedRules.add(ruleId);
215
216
  this._usedVersions.add(oasVersion);
216
217
  const settings = this.decorators[oasVersion][ruleId] || 'off';