@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
@@ -5,7 +5,6 @@ const _1 = require(".");
5
5
  const utils_1 = require("../utils");
6
6
  const builtInRulesList = [
7
7
  'spec',
8
- 'info-description',
9
8
  'info-contact',
10
9
  'info-license',
11
10
  'info-license-url',
@@ -140,11 +139,7 @@ const RootConfigStyleguide = {
140
139
  } }, ConfigStyleguide.properties),
141
140
  };
142
141
  const ConfigRoot = {
143
- properties: Object.assign(Object.assign({ organization: { type: 'string' }, apis: 'ConfigApis', apiDefinitions: {
144
- type: 'object',
145
- properties: {},
146
- additionalProperties: { properties: { type: 'string' } },
147
- } }, RootConfigStyleguide.properties), { styleguide: 'RootConfigStyleguide', lint: 'RootConfigStyleguide', 'features.openapi': 'ConfigReferenceDocs', referenceDocs: 'ConfigReferenceDocs', 'features.mockServer': 'ConfigMockServer', region: { enum: ['us', 'eu'] }, resolve: {
142
+ properties: Object.assign(Object.assign({ organization: { type: 'string' }, apis: 'ConfigApis' }, RootConfigStyleguide.properties), { 'features.openapi': 'ConfigReferenceDocs', 'features.mockServer': 'ConfigMockServer', region: { enum: ['us', 'eu'] }, resolve: {
148
143
  properties: {
149
144
  http: 'ConfigHTTP',
150
145
  doNotResolveExamples: { type: 'boolean' },
@@ -209,16 +204,9 @@ const ObjectRule = {
209
204
  additionalProperties: {},
210
205
  required: ['severity'],
211
206
  };
212
- const Assert = {
207
+ const AssertionDefinitionSubject = {
213
208
  properties: {
214
- subject: (value) => {
215
- if (Array.isArray(value)) {
216
- return { type: 'array', items: { enum: nodeTypesList } };
217
- }
218
- else {
219
- return { enum: nodeTypesList };
220
- }
221
- },
209
+ type: { enum: nodeTypesList },
222
210
  property: (value) => {
223
211
  if (Array.isArray(value)) {
224
212
  return { type: 'array', items: { type: 'string' } };
@@ -230,10 +218,14 @@ const Assert = {
230
218
  return { type: 'string' };
231
219
  }
232
220
  },
233
- context: _1.listOf('Context'),
234
- message: { type: 'string' },
235
- suggest: { type: 'array', items: { type: 'string' } },
236
- severity: { enum: ['error', 'warn', 'off'] },
221
+ filterInParentKeys: { type: 'array', items: { type: 'string' } },
222
+ filterOutParentKeys: { type: 'array', items: { type: 'string' } },
223
+ matchParentKeys: { type: 'string' },
224
+ },
225
+ required: ['type'],
226
+ };
227
+ const AssertionDefinitionAssertions = {
228
+ properties: {
237
229
  enum: { type: 'array', items: { type: 'string' } },
238
230
  pattern: { type: 'string' },
239
231
  casing: {
@@ -253,26 +245,49 @@ const Assert = {
253
245
  requireAny: { type: 'array', items: { type: 'string' } },
254
246
  disallowed: { type: 'array', items: { type: 'string' } },
255
247
  defined: { type: 'boolean' },
256
- undefined: { type: 'boolean' },
248
+ // undefined: { type: 'boolean' }, // TODO: Remove `undefined` assertion from codebase overall
257
249
  nonEmpty: { type: 'boolean' },
258
250
  minLength: { type: 'integer' },
259
251
  maxLength: { type: 'integer' },
260
252
  ref: (value) => typeof value === 'string' ? { type: 'string' } : { type: 'boolean' },
253
+ const: (value) => {
254
+ if (typeof value === 'string') {
255
+ return { type: 'string' };
256
+ }
257
+ if (typeof value === 'number') {
258
+ return { type: 'number' };
259
+ }
260
+ if (typeof value === 'boolean') {
261
+ return { type: 'boolean' };
262
+ }
263
+ else {
264
+ return;
265
+ }
266
+ },
261
267
  },
262
268
  additionalProperties: (_value, key) => {
263
269
  if (/^\w+\/\w+$/.test(key))
264
270
  return { type: 'object' };
265
271
  return;
266
272
  },
267
- required: ['subject'],
268
273
  };
269
- const Context = {
274
+ const AssertDefinition = {
270
275
  properties: {
271
- type: { enum: nodeTypesList },
272
- matchParentKeys: { type: 'array', items: { type: 'string' } },
273
- excludeParentKeys: { type: 'array', items: { type: 'string' } },
276
+ subject: 'AssertionDefinitionSubject',
277
+ assertions: 'AssertionDefinitionAssertions',
274
278
  },
275
- required: ['type'],
279
+ required: ['subject', 'assertions'],
280
+ };
281
+ const Assert = {
282
+ properties: {
283
+ subject: 'AssertionDefinitionSubject',
284
+ assertions: 'AssertionDefinitionAssertions',
285
+ where: _1.listOf('AssertDefinition'),
286
+ message: { type: 'string' },
287
+ suggest: { type: 'array', items: { type: 'string' } },
288
+ severity: { enum: ['error', 'warn', 'off'] },
289
+ },
290
+ required: ['subject', 'assertions'],
276
291
  };
277
292
  const ConfigLanguage = {
278
293
  properties: {
@@ -792,7 +807,7 @@ exports.ConfigTypes = {
792
807
  ConfigSidebarLinks,
793
808
  CommonConfigSidebarLinks,
794
809
  ConfigTheme,
795
- Context,
810
+ AssertDefinition,
796
811
  ThemeColors,
797
812
  CommonThemeColors,
798
813
  BorderThemeColors,
@@ -839,4 +854,6 @@ exports.ConfigTypes = {
839
854
  Sidebar,
840
855
  Heading,
841
856
  Typography,
857
+ AssertionDefinitionAssertions,
858
+ AssertionDefinitionSubject,
842
859
  };
package/lib/utils.d.ts CHANGED
@@ -46,4 +46,5 @@ export declare function showErrorForDeprecatedField(deprecatedField: string, upd
46
46
  export declare type Falsy = undefined | null | false | '' | 0;
47
47
  export declare function isTruthy<Truthy>(value: Truthy | Falsy): value is Truthy;
48
48
  export declare function identity<T>(value: T): T;
49
+ export declare function keysOf<T>(obj: T): (keyof T)[];
49
50
  export declare function pickDefined<T extends Record<string, unknown>>(obj?: T): Record<string, unknown> | undefined;
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.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;
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.112",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "engines": {
@@ -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 () => {
@@ -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: [
@@ -0,0 +1,4 @@
1
+ lint:
2
+ rules:
3
+ info-contact: warn
4
+ extends: []
@@ -3,12 +3,14 @@ lint:
3
3
  no-invalid-media-type-examples: warn
4
4
  operation-4xx-response: off
5
5
  assert/tag-description:
6
- subject: Tag
7
- property: description
6
+ subject:
7
+ type: Tag
8
+ property: description
8
9
  message: Tag description must have at least 3 words.
9
10
  severity: error
10
- test-plugin/checkWordsCount:
11
- min: 3
11
+ assertions:
12
+ test-plugin/checkWordsCount:
13
+ min: 3
12
14
  plugins:
13
15
  - plugin.js
14
16
  extends:
@@ -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)) {
@@ -409,26 +415,17 @@ function groupStyleguideAssertionRules({
409
415
  const transformedRules: Record<string, RuleConfig> = {};
410
416
 
411
417
  // Collect assertion rules
412
- const assertions = [];
418
+ const assertions: Assertion[] = [];
413
419
  for (const [ruleKey, rule] of Object.entries(rules)) {
414
420
  if (ruleKey.startsWith('assert/') && typeof rule === 'object' && rule !== null) {
415
- const assertion = rule;
421
+ const assertion = rule as RawAssertion;
422
+
416
423
  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]);
424
+ registerCustomAssertions(plugins, assertion);
425
+
426
+ // We may have custom assertion inside where block
427
+ for (const context of assertion.where || []) {
428
+ registerCustomAssertions(plugins, context);
432
429
  }
433
430
  }
434
431
  assertions.push({
@@ -446,3 +443,29 @@ function groupStyleguideAssertionRules({
446
443
 
447
444
  return transformedRules;
448
445
  }
446
+
447
+ function registerCustomAssertions(plugins: Plugin[], assertion: AssertionDefinition) {
448
+ for (const field of keysOf(assertion.assertions)) {
449
+ const [pluginId, fn] = field.split('/');
450
+
451
+ if (!pluginId || !fn) continue;
452
+
453
+ const plugin = plugins.find((plugin) => plugin.id === pluginId);
454
+
455
+ if (!plugin) {
456
+ throw Error(colorize.red(`Plugin ${colorize.blue(pluginId)} isn't found.`));
457
+ }
458
+
459
+ if (!plugin.assertions || !plugin.assertions[fn]) {
460
+ throw Error(
461
+ `Plugin ${colorize.red(
462
+ pluginId
463
+ )} doesn't export assertions function with name ${colorize.red(fn)}.`
464
+ );
465
+ }
466
+
467
+ (asserts as Asserts & { [name: string]: AssertionFn })[field] = buildAssertCustomFunction(
468
+ plugin.assertions[fn]
469
+ );
470
+ }
471
+ }
@@ -71,11 +71,8 @@ export async function loadConfig(
71
71
  } = {}
72
72
  ): Promise<Config> {
73
73
  const { configPath = findConfig(), customExtends, processRawConfig, files, region } = options;
74
- const config = await getConfig(configPath);
74
+ const config = await getConfig(configPath, processRawConfig);
75
75
  const rawConfig = { ...config, files: files ?? config.files, region: region ?? config.region };
76
- if (typeof processRawConfig === 'function') {
77
- await processRawConfig(rawConfig);
78
- }
79
76
 
80
77
  const redoclyClient = new RedoclyClient();
81
78
  const tokens = await redoclyClient.getTokens();
@@ -105,11 +102,17 @@ export function findConfig(dir?: string): string | undefined {
105
102
  return existingConfigFiles[0];
106
103
  }
107
104
 
108
- export async function getConfig(configPath: string | undefined = findConfig()): Promise<RawConfig> {
105
+ export async function getConfig(
106
+ configPath: string | undefined = findConfig(),
107
+ processRawConfig?: (rawConfig: RawConfig) => void | Promise<void>
108
+ ): Promise<RawConfig> {
109
109
  if (!configPath || !doesYamlFileExist(configPath)) return {};
110
110
  try {
111
111
  const rawConfig =
112
112
  (await loadYaml<RawConfig & DeprecatedInRawConfig & FlatRawConfig>(configPath)) || {};
113
+ if (typeof processRawConfig === 'function') {
114
+ await processRawConfig(rawConfig);
115
+ }
113
116
  return transformConfig(rawConfig);
114
117
  } catch (e) {
115
118
  throw new Error(`Error parsing config file at '${configPath}': ${e.message}`);
@@ -2,7 +2,6 @@ import type { PluginStyleguideConfig } from './types';
2
2
 
3
3
  export default {
4
4
  rules: {
5
- 'info-description': 'warn',
6
5
  'info-contact': 'off',
7
6
  'info-license': 'off',
8
7
  'info-license-url': 'off',
@@ -2,7 +2,6 @@ import type { PluginStyleguideConfig } from './types';
2
2
 
3
3
  export default {
4
4
  rules: {
5
- 'info-description': 'warn',
6
5
  'info-contact': 'off',
7
6
  'info-license': 'warn',
8
7
  'info-license-url': 'warn',
@@ -1,6 +1,6 @@
1
1
  import { Location } from '../../../../ref-utils';
2
2
  import { Source } from '../../../../resolve';
3
- import { asserts, buildAssertCustomFunction } from '../asserts';
3
+ import { Asserts, asserts, buildAssertCustomFunction } from '../asserts';
4
4
 
5
5
  let baseLocation = new Location(jest.fn() as any as Source, 'pointer');
6
6
 
@@ -674,9 +674,13 @@ describe('oas3 assertions', () => {
674
674
  }
675
675
  return [];
676
676
  });
677
- asserts['local/customFn'] = buildAssertCustomFunction(customFn);
677
+ asserts['local/customFn' as keyof Asserts] = buildAssertCustomFunction(customFn);
678
678
  expect(
679
- asserts['local/customFn'](Object.keys(fakeNode), { word: 'foo' }, baseLocation)
679
+ asserts['local/customFn' as keyof Asserts](
680
+ Object.keys(fakeNode),
681
+ { word: 'foo' },
682
+ baseLocation
683
+ )
680
684
  ).toEqual([
681
685
  {
682
686
  message: 'First value should be foo',
@@ -1,61 +1,82 @@
1
- import { Assertions } from '../.';
1
+ import { Assertion, Assertions } from '../.';
2
2
 
3
3
  const opts = {
4
4
  '0': {
5
- subject: 'Operation',
6
- property: 'summary',
5
+ subject: {
6
+ type: 'Operation',
7
+ property: 'summary',
8
+ },
7
9
  description: 'example warn text',
8
10
  severity: 'warn',
9
- pattern: '/example/',
11
+ assertions: { pattern: '/example/' },
10
12
  },
11
13
  '1': {
12
- subject: 'PathItem',
13
- context: [{ type: 'Operation', matchParentKeys: ['post'] }],
14
+ subject: {
15
+ type: 'PathItem',
16
+ },
17
+ where: [
18
+ {
19
+ subject: { type: 'Operation', filterInParentKeys: ['post'], property: 'responses' },
20
+ assertions: { defined: true },
21
+ },
22
+ ],
14
23
  description: 'example warn text',
15
24
  severity: 'warn',
16
- mutuallyExclusive: ['summary', 'security'],
25
+ assertions: { mutuallyExclusive: ['summary', 'security'] },
17
26
  },
18
27
  '2': {
19
- subject: ['PathItem'],
20
- context: [{ type: 'Operation' }],
21
- property: 'tags',
28
+ subject: { type: 'PathItem', property: 'tags' },
29
+ where: [
30
+ { subject: { type: 'Operation', property: 'responses' }, assertions: { defined: true } },
31
+ ],
22
32
  description: 'example warn text',
23
33
  severity: 'warn',
24
- sortOrder: 'desc',
34
+ assertions: { sortOrder: 'desc' },
25
35
  },
26
36
  '3': {
27
- subject: ['Foo'],
28
- context: [{ type: 'Bar' }, { type: 'Baz' }],
29
- property: 'test',
37
+ subject: { type: 'Foo', property: 'test' },
38
+ where: [
39
+ { subject: { type: 'Bar' }, assertions: {} },
40
+ { subject: { type: 'Baz' }, assertions: {} },
41
+ ],
30
42
  description: 'example warn text',
31
43
  severity: 'warn',
32
- sortOrder: 'desc',
44
+ assertions: { sortOrder: 'desc' },
33
45
  },
34
46
  };
35
47
 
36
48
  describe('Oas3 assertions', () => {
37
49
  it('should return the right visitor structure', () => {
38
- const visitors = Assertions(opts) as any;
50
+ const visitors = Assertions(opts as any);
39
51
  expect(visitors).toMatchInlineSnapshot(`
40
52
  Array [
41
53
  Object {
42
- "Operation": [Function],
54
+ "Operation": Object {
55
+ "enter": [Function],
56
+ },
43
57
  },
44
58
  Object {
45
59
  "Operation": Object {
46
- "PathItem": [Function],
60
+ "PathItem": Object {
61
+ "enter": [Function],
62
+ },
47
63
  "skip": [Function],
48
64
  },
49
65
  },
50
66
  Object {
51
67
  "Operation": Object {
52
- "PathItem": [Function],
68
+ "PathItem": Object {
69
+ "enter": [Function],
70
+ },
71
+ "skip": [Function],
53
72
  },
54
73
  },
55
74
  Object {
56
75
  "Bar": Object {
57
76
  "Baz": Object {
58
- "Foo": [Function],
77
+ "Foo": Object {
78
+ "enter": [Function],
79
+ },
59
80
  },
60
81
  },
61
82
  },