@redocly/openapi-core 1.4.1 → 1.6.0

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/CHANGELOG.md +12 -0
  2. package/README.md +5 -5
  3. package/lib/bundle.d.ts +8 -6
  4. package/lib/bundle.js +48 -14
  5. package/lib/config/all.js +2 -0
  6. package/lib/config/config-resolvers.d.ts +9 -1
  7. package/lib/config/config-resolvers.js +22 -1
  8. package/lib/config/load.d.ts +11 -3
  9. package/lib/config/load.js +12 -6
  10. package/lib/config/minimal.js +2 -0
  11. package/lib/config/recommended-strict.js +2 -0
  12. package/lib/config/recommended.js +2 -0
  13. package/lib/lint.d.ts +6 -3
  14. package/lib/lint.js +14 -3
  15. package/lib/rules/oas3/array-parameter-serialization.d.ts +5 -0
  16. package/lib/rules/oas3/array-parameter-serialization.js +31 -0
  17. package/lib/rules/oas3/index.js +2 -0
  18. package/lib/types/portal-config-schema.d.ts +22 -2465
  19. package/lib/types/portal-config-schema.js +56 -38
  20. package/lib/types/redocly-yaml.d.ts +1 -1
  21. package/lib/types/redocly-yaml.js +5 -4
  22. package/lib/typings/openapi.d.ts +1 -0
  23. package/lib/visitors.d.ts +5 -5
  24. package/lib/visitors.js +4 -6
  25. package/lib/walk.d.ts +3 -3
  26. package/lib/walk.js +2 -2
  27. package/package.json +1 -1
  28. package/src/__tests__/lint.test.ts +2 -2
  29. package/src/bundle.ts +67 -26
  30. package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +4 -0
  31. package/src/config/__tests__/fixtures/resolve-refs-in-config/config-with-refs.yaml +8 -0
  32. package/src/config/__tests__/fixtures/resolve-refs-in-config/rules.yaml +2 -0
  33. package/src/config/__tests__/fixtures/resolve-refs-in-config/seo.yaml +1 -0
  34. package/src/config/__tests__/load.test.ts +80 -1
  35. package/src/config/all.ts +2 -0
  36. package/src/config/config-resolvers.ts +41 -11
  37. package/src/config/load.ts +36 -19
  38. package/src/config/minimal.ts +2 -0
  39. package/src/config/recommended-strict.ts +2 -0
  40. package/src/config/recommended.ts +2 -0
  41. package/src/lint.ts +32 -10
  42. package/src/rules/oas3/__tests__/array-parameter-serialization.test.ts +263 -0
  43. package/src/rules/oas3/array-parameter-serialization.ts +43 -0
  44. package/src/rules/oas3/index.ts +2 -0
  45. package/src/types/portal-config-schema.ts +65 -42
  46. package/src/types/redocly-yaml.ts +3 -4
  47. package/src/typings/openapi.ts +1 -0
  48. package/src/visitors.ts +20 -22
  49. package/src/walk.ts +8 -8
  50. package/tsconfig.tsbuildinfo +1 -1
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.rootRedoclyConfigSchema = exports.environmentSchema = exports.redoclyConfigSchema = exports.apiConfigSchema = exports.ssoConfigSchema = void 0;
3
+ exports.rootRedoclyConfigSchema = exports.environmentSchema = exports.redoclyConfigSchema = exports.PortalConfigNodeTypes = exports.apiConfigSchema = exports.ssoConfigSchema = void 0;
4
4
  const config_1 = require("../config");
5
5
  const oidcIssuerMetadataSchema = {
6
6
  type: 'object',
@@ -80,6 +80,7 @@ const authProviderConfigSchema = {
80
80
  };
81
81
  exports.ssoConfigSchema = {
82
82
  type: 'object',
83
+ properties: {},
83
84
  additionalProperties: authProviderConfigSchema,
84
85
  };
85
86
  const redirectConfigSchema = {
@@ -89,7 +90,12 @@ const redirectConfigSchema = {
89
90
  type: { type: 'number', default: 301 },
90
91
  },
91
92
  required: ['to'],
92
- additionalProperties: false,
93
+ };
94
+ const redirectsConfigSchema = {
95
+ type: 'object',
96
+ properties: {},
97
+ additionalProperties: 'redirectConfigSchema',
98
+ default: {},
93
99
  };
94
100
  exports.apiConfigSchema = {
95
101
  type: 'object',
@@ -136,13 +142,16 @@ const seoConfigSchema = {
136
142
  },
137
143
  },
138
144
  },
139
- additionalProperties: false,
140
145
  };
141
- const rbacScopeItemsSchema = { type: 'object', additionalProperties: { type: 'string' } };
146
+ const rbacScopeItemsSchema = {
147
+ type: 'object',
148
+ properties: {},
149
+ additionalProperties: { type: 'string' },
150
+ };
142
151
  const rbacConfigSchema = {
143
152
  type: 'object',
144
153
  properties: {
145
- defaults: rbacScopeItemsSchema,
154
+ defaults: 'rbacScopeItemsSchema',
146
155
  },
147
156
  additionalProperties: rbacScopeItemsSchema,
148
157
  };
@@ -207,7 +216,6 @@ const devOnboardingAdapterConfigSchema = {
207
216
  const devOnboardingConfigSchema = {
208
217
  type: 'object',
209
218
  required: ['adapters'],
210
- additionalProperties: false,
211
219
  properties: {
212
220
  adapters: {
213
221
  type: 'array',
@@ -215,6 +223,30 @@ const devOnboardingConfigSchema = {
215
223
  },
216
224
  },
217
225
  };
226
+ const i18ConfigSchema = {
227
+ type: 'object',
228
+ properties: {
229
+ defaultLocale: {
230
+ type: 'string',
231
+ },
232
+ locales: {
233
+ type: 'array',
234
+ items: {
235
+ type: 'object',
236
+ properties: {
237
+ code: {
238
+ type: 'string',
239
+ },
240
+ name: {
241
+ type: 'string',
242
+ },
243
+ },
244
+ required: ['code'],
245
+ },
246
+ },
247
+ },
248
+ required: ['defaultLocale', 'locales'],
249
+ };
218
250
  const responseHeaderSchema = {
219
251
  type: 'object',
220
252
  properties: {
@@ -224,14 +256,24 @@ const responseHeaderSchema = {
224
256
  additionalProperties: false,
225
257
  required: ['name', 'value'],
226
258
  };
259
+ exports.PortalConfigNodeTypes = {
260
+ seoConfigSchema,
261
+ rbacConfigSchema,
262
+ rbacScopeItemsSchema,
263
+ ssoConfigSchema: exports.ssoConfigSchema,
264
+ devOnboardingConfigSchema,
265
+ i18ConfigSchema,
266
+ redirectsConfigSchema,
267
+ redirectConfigSchema,
268
+ // TODO: Extract other types that need to be linted in the config
269
+ };
227
270
  exports.redoclyConfigSchema = {
228
271
  type: 'object',
229
272
  properties: {
230
273
  licenseKey: { type: 'string' },
231
- theme: { type: 'object', default: {} },
232
- redirects: { type: 'object', additionalProperties: redirectConfigSchema, default: {} },
233
- seo: seoConfigSchema,
234
- rbac: rbacConfigSchema,
274
+ redirects: 'redirectsConfigSchema',
275
+ seo: 'seoConfigSchema',
276
+ rbac: 'rbacConfigSchema',
235
277
  responseHeaders: {
236
278
  type: 'object',
237
279
  additionalProperties: {
@@ -253,37 +295,12 @@ exports.redoclyConfigSchema = {
253
295
  type: 'object',
254
296
  additionalProperties: exports.apiConfigSchema,
255
297
  },
256
- sso: exports.ssoConfigSchema,
257
- developerOnboarding: devOnboardingConfigSchema,
258
- i18n: {
259
- type: 'object',
260
- properties: {
261
- defaultLocale: {
262
- type: 'string',
263
- },
264
- locales: {
265
- type: 'array',
266
- items: {
267
- type: 'object',
268
- properties: {
269
- code: {
270
- type: 'string',
271
- },
272
- name: {
273
- type: 'string',
274
- },
275
- },
276
- required: ['code'],
277
- },
278
- },
279
- },
280
- additionalProperties: false,
281
- required: ['defaultLocale', 'locales'],
282
- },
298
+ sso: 'ssoConfigSchema',
299
+ developerOnboarding: 'devOnboardingConfigSchema',
300
+ i18n: 'i18ConfigSchema',
283
301
  metadata: metadataConfigSchema,
284
302
  },
285
303
  default: {},
286
- additionalProperties: true,
287
304
  };
288
305
  exports.environmentSchema = {
289
306
  oneOf: [
@@ -300,5 +317,6 @@ exports.environmentSchema = {
300
317
  };
301
318
  exports.rootRedoclyConfigSchema = Object.assign(Object.assign({}, exports.redoclyConfigSchema), { properties: Object.assign(Object.assign({}, exports.redoclyConfigSchema.properties), { env: {
302
319
  type: 'object',
320
+ properties: {},
303
321
  additionalProperties: exports.environmentSchema,
304
322
  } }), default: {}, required: ['redirects'] });
@@ -5,7 +5,7 @@ declare const builtInCommonOASRules: readonly ["info-license-url", "info-license
5
5
  export type BuiltInCommonOASRuleId = typeof builtInCommonOASRules[number];
6
6
  declare const builtInOAS2Rules: readonly ["boolean-parameter-prefixes", "request-mime-type", "response-contains-property", "response-mime-type"];
7
7
  export type BuiltInOAS2RuleId = typeof builtInOAS2Rules[number];
8
- declare const builtInOAS3Rules: readonly ["boolean-parameter-prefixes", "component-name-unique", "no-empty-servers", "no-example-value-and-externalValue", "no-invalid-media-type-examples", "no-server-example.com", "no-server-trailing-slash", "no-server-variables-empty-enum", "no-undefined-server-variable", "no-unused-components", "operation-4xx-problem-details-rfc7807", "request-mime-type", "response-contains-property", "response-mime-type", "spec-components-invalid-map-name"];
8
+ declare const builtInOAS3Rules: readonly ["boolean-parameter-prefixes", "component-name-unique", "no-empty-servers", "no-example-value-and-externalValue", "no-invalid-media-type-examples", "no-server-example.com", "no-server-trailing-slash", "no-server-variables-empty-enum", "no-undefined-server-variable", "no-unused-components", "operation-4xx-problem-details-rfc7807", "request-mime-type", "response-contains-property", "response-mime-type", "spec-components-invalid-map-name", "array-parameter-serialization"];
9
9
  export type BuiltInOAS3RuleId = typeof builtInOAS3Rules[number];
10
10
  declare const builtInAsync2Rules: readonly ["channels-kebab-case", "no-channel-trailing-slash"];
11
11
  export type BuiltInAsync2RuleId = typeof builtInAsync2Rules[number];
@@ -4,8 +4,8 @@ exports.ConfigTypes = void 0;
4
4
  const portal_config_schema_1 = require("./portal-config-schema");
5
5
  const theme_config_1 = require("./theme-config");
6
6
  const _1 = require(".");
7
- const oas3_1_1 = require("./oas3_1");
8
7
  const utils_1 = require("../utils");
8
+ const portal_config_schema_2 = require("./portal-config-schema");
9
9
  const builtInCommonRules = [
10
10
  'spec',
11
11
  'info-contact',
@@ -70,6 +70,7 @@ const builtInOAS3Rules = [
70
70
  'response-contains-property',
71
71
  'response-mime-type',
72
72
  'spec-components-invalid-map-name',
73
+ 'array-parameter-serialization',
73
74
  ];
74
75
  const builtInAsync2Rules = ['channels-kebab-case', 'no-channel-trailing-slash'];
75
76
  const builtInRules = [
@@ -195,7 +196,7 @@ const ConfigApisProperties = {
195
196
  items: {
196
197
  type: 'string',
197
198
  },
198
- }, lint: 'ConfigStyleguide', styleguide: 'ConfigStyleguide' }), ConfigStyleguide.properties), { 'features.openapi': 'ConfigReferenceDocs', 'features.mockServer': 'ConfigMockServer', theme: 'ConfigRootTheme', files: {
199
+ } }), ConfigStyleguide.properties), { 'features.openapi': 'ConfigReferenceDocs', 'features.mockServer': 'ConfigMockServer', theme: 'ConfigRootTheme', files: {
199
200
  type: 'array',
200
201
  items: {
201
202
  type: 'string',
@@ -837,7 +838,7 @@ const ConfigMockServer = {
837
838
  errorIfForcedExampleNotFound: { type: 'boolean' },
838
839
  },
839
840
  };
840
- exports.ConfigTypes = Object.assign(Object.assign({}, oas3_1_1.Oas3_1Types), { Assert,
841
+ exports.ConfigTypes = Object.assign({ Assert,
841
842
  ConfigRoot,
842
843
  ConfigApis,
843
844
  ConfigApisProperties,
@@ -900,4 +901,4 @@ exports.ConfigTypes = Object.assign(Object.assign({}, oas3_1_1.Oas3_1Types), { A
900
901
  Heading,
901
902
  Typography,
902
903
  AssertionDefinitionAssertions,
903
- AssertionDefinitionSubject });
904
+ AssertionDefinitionSubject }, portal_config_schema_2.PortalConfigNodeTypes);
@@ -150,6 +150,7 @@ export interface Oas3Schema {
150
150
  export type Oas3_1Schema = Oas3Schema & {
151
151
  type?: string | string[];
152
152
  examples?: any[];
153
+ prefixItems?: Oas3_1Schema[];
153
154
  };
154
155
  export interface Oas3_1Definition extends Oas3Definition {
155
156
  webhooks?: Oas3_1Webhooks;
package/lib/visitors.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import type { Oas3Definition, Oas3ExternalDocs, Oas3Info, Oas3Contact, Oas3Components, Oas3License, Oas3Schema, Oas3Header, Oas3Parameter, Oas3Operation, Oas3PathItem, Oas3ServerVariable, Oas3Server, Oas3MediaType, Oas3Response, Oas3Example, Oas3RequestBody, Oas3Tag, OasRef, Oas3SecurityScheme, Oas3SecurityRequirement, Oas3Encoding, Oas3Link, Oas3Xml, Oas3Discriminator, Oas3Callback } from './typings/openapi';
2
- import type { Oas2Definition, Oas2Tag, Oas2ExternalDocs, Oas2SecurityRequirement, Oas2Info, Oas2Contact, Oas2License, Oas2PathItem, Oas2Operation, Oas2Header, Oas2Response, Oas2Schema, Oas2Xml, Oas2Parameter, Oas2SecurityScheme } from './typings/swagger';
3
- import { NormalizedNodeType } from './types';
1
+ import type { NormalizedNodeType } from './types';
4
2
  import type { Stack } from './utils';
5
3
  import type { UserContext, ResolveResult, ProblemSeverity } from './walk';
6
4
  import type { Location } from './ref-utils';
7
- import { Async2Definition } from './typings/asyncapi';
5
+ import type { Oas3Definition, Oas3ExternalDocs, Oas3Info, Oas3Contact, Oas3Components, Oas3License, Oas3Schema, Oas3Header, Oas3Parameter, Oas3Operation, Oas3PathItem, Oas3ServerVariable, Oas3Server, Oas3MediaType, Oas3Response, Oas3Example, Oas3RequestBody, Oas3Tag, OasRef, Oas3SecurityScheme, Oas3SecurityRequirement, Oas3Encoding, Oas3Link, Oas3Xml, Oas3Discriminator, Oas3Callback } from './typings/openapi';
6
+ import type { Oas2Definition, Oas2Tag, Oas2ExternalDocs, Oas2SecurityRequirement, Oas2Info, Oas2Contact, Oas2License, Oas2PathItem, Oas2Operation, Oas2Header, Oas2Response, Oas2Schema, Oas2Xml, Oas2Parameter, Oas2SecurityScheme } from './typings/swagger';
7
+ import type { Async2Definition } from './typings/asyncapi';
8
8
  export type SkipFunctionContext = Pick<UserContext, 'location' | 'rawNode' | 'resolve' | 'rawLocation'>;
9
9
  export type VisitFunction<T> = (node: T, ctx: UserContext & {
10
10
  ignoreNextVisitorsOnNode: () => void;
@@ -184,6 +184,6 @@ export type RuleInstanceConfig = {
184
184
  severity: ProblemSeverity;
185
185
  };
186
186
  export declare function normalizeVisitors<T extends BaseVisitor>(visitorsConfig: (RuleInstanceConfig & {
187
- visitor: NestedVisitObject<any, T>;
187
+ visitor: NestedVisitObject<unknown, T>;
188
188
  })[], types: Record<keyof T, NormalizedNodeType>): NormalizedOasVisitors<T>;
189
189
  export {};
package/lib/visitors.js CHANGED
@@ -77,12 +77,10 @@ function normalizeVisitors(visitorsConfig, types) {
77
77
  }
78
78
  function addWeakFromStack(ruleConf, stack) {
79
79
  for (const interType of stack.slice(1)) {
80
- normalizedVisitors[interType.name] =
81
- normalizedVisitors[interType.name] ||
82
- {
83
- enter: [],
84
- leave: [],
85
- };
80
+ normalizedVisitors[interType.name] = normalizedVisitors[interType.name] || {
81
+ enter: [],
82
+ leave: [],
83
+ };
86
84
  normalizedVisitors[interType.name].enter.push(Object.assign(Object.assign({}, ruleConf), { visit: () => undefined, depth: 0, context: {
87
85
  isSkippedLevel: true,
88
86
  seen: new Set(),
package/lib/walk.d.ts CHANGED
@@ -1,11 +1,11 @@
1
+ import { Location } from './ref-utils';
2
+ import { ResolveError, YamlParseError, Source } from './resolve';
3
+ import { SpecVersion } from './oas-types';
1
4
  import type { Referenced } from './typings/openapi';
2
5
  import type { NormalizedOasVisitors, BaseVisitor } from './visitors';
3
6
  import type { ResolvedRefMap, Document } from './resolve';
4
7
  import type { NormalizedNodeType } from './types';
5
8
  import type { RuleSeverity } from './config';
6
- import { Location } from './ref-utils';
7
- import { ResolveError, YamlParseError, Source } from './resolve';
8
- import { SpecVersion } from './oas-types';
9
9
  export type NonUndefined = string | number | boolean | symbol | bigint | object | Record<string, any>;
10
10
  export type ResolveResult<T extends NonUndefined> = {
11
11
  node: T;
package/lib/walk.js CHANGED
@@ -86,7 +86,7 @@ function walkDocument(opts) {
86
86
  const currentEnterVisitors = anyEnterVisitors.concat(((_c = normalizedVisitors[type.name]) === null || _c === void 0 ? void 0 : _c.enter) || []);
87
87
  const activatedContexts = [];
88
88
  for (const { context, visit, skip, ruleId, severity } of currentEnterVisitors) {
89
- if (ignoredNodes.has(currentLocation.pointer))
89
+ if (ignoredNodes.has(`${currentLocation.absolutePointer}${currentLocation.pointer}`))
90
90
  break;
91
91
  if (context.isSkippedLevel) {
92
92
  if (context.parent.activatedOn &&
@@ -248,7 +248,7 @@ function walkDocument(opts) {
248
248
  parentLocations: collectParentsLocations(context),
249
249
  oasVersion: ctx.oasVersion,
250
250
  ignoreNextVisitorsOnNode: () => {
251
- ignoredNodes.add(currentLocation.pointer);
251
+ ignoredNodes.add(`${currentLocation.absolutePointer}${currentLocation.pointer}`);
252
252
  },
253
253
  getVisitorData: getVisitorDataFn.bind(undefined, ruleId),
254
254
  }, collectParents(context), context);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/openapi-core",
3
- "version": "1.4.1",
3
+ "version": "1.6.0",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "engines": {
@@ -22,7 +22,7 @@ describe('lint', () => {
22
22
  servers:
23
23
  - url: http://redocly-example.com
24
24
  paths: {}
25
- `,
25
+ `,
26
26
  config: await loadConfig(),
27
27
  });
28
28
 
@@ -139,8 +139,8 @@ describe('lint', () => {
139
139
  "ruleId": "configuration spec",
140
140
  "severity": "error",
141
141
  "suggest": [
142
- "theme",
143
142
  "env",
143
+ "theme",
144
144
  "seo",
145
145
  "sso",
146
146
  ],
package/src/bundle.ts CHANGED
@@ -1,26 +1,30 @@
1
1
  import isEqual = require('lodash.isequal');
2
+ import { BaseResolver, resolveDocument, makeRefId, makeDocumentFromString } from './resolve';
3
+ import { normalizeVisitors } from './visitors';
4
+ import { normalizeTypes } from './types';
5
+ import { walkDocument } from './walk';
2
6
  import {
3
- BaseResolver,
4
- resolveDocument,
5
- Document,
6
- ResolvedRefMap,
7
- makeRefId,
8
- makeDocumentFromString,
9
- } from './resolve';
10
- import { Oas3Rule, normalizeVisitors, Oas3Visitor, Oas2Visitor } from './visitors';
11
- import { NormalizedNodeType, normalizeTypes, NodeType } from './types';
12
- import { WalkContext, walkDocument, UserContext, ResolveResult, NormalizedProblem } from './walk';
13
- import { detectSpec, getTypes, getMajorSpecVersion, SpecMajorVersion } from './oas-types';
7
+ detectSpec,
8
+ getTypes,
9
+ getMajorSpecVersion,
10
+ SpecMajorVersion,
11
+ SpecVersion,
12
+ } from './oas-types';
14
13
  import { isAbsoluteUrl, isRef, Location, refBaseName } from './ref-utils';
15
14
  import { initRules } from './config/rules';
16
15
  import { reportUnresolvedRef } from './rules/no-unresolved-refs';
17
16
  import { isPlainObject, isTruthy } from './utils';
18
- import { OasRef } from './typings/openapi';
19
17
  import { isRedoclyRegistryURL } from './redocly';
20
18
  import { RemoveUnusedComponents as RemoveUnusedComponentsOas2 } from './decorators/oas2/remove-unused-components';
21
19
  import { RemoveUnusedComponents as RemoveUnusedComponentsOas3 } from './decorators/oas3/remove-unused-components';
20
+ import { ConfigTypes } from './types/redocly-yaml';
22
21
 
22
+ import type { Oas3Rule, Oas3Visitor, Oas2Visitor } from './visitors';
23
+ import type { NormalizedNodeType, NodeType } from './types';
24
+ import type { WalkContext, UserContext, ResolveResult, NormalizedProblem } from './walk';
23
25
  import type { Config, StyleguideConfig } from './config';
26
+ import type { OasRef } from './typings/openapi';
27
+ import type { Document, ResolvedRefMap } from './resolve';
24
28
 
25
29
  export type Oas3RuleSet = Record<string, Oas3Rule>;
26
30
 
@@ -33,12 +37,50 @@ export type BundleOptions = {
33
37
  externalRefResolver?: BaseResolver;
34
38
  config: Config;
35
39
  dereference?: boolean;
36
- base?: string;
40
+ base?: string | null;
37
41
  skipRedoclyRegistryRefs?: boolean;
38
42
  removeUnusedComponents?: boolean;
39
43
  keepUrlRefs?: boolean;
40
44
  };
41
45
 
46
+ export async function bundleConfig(document: Document, resolvedRefMap: ResolvedRefMap) {
47
+ const types = normalizeTypes(ConfigTypes);
48
+
49
+ const ctx: BundleContext = {
50
+ problems: [],
51
+ oasVersion: SpecVersion.OAS3_0,
52
+ refTypes: new Map<string, NormalizedNodeType>(),
53
+ visitorsData: {},
54
+ };
55
+
56
+ const bundleVisitor = normalizeVisitors(
57
+ [
58
+ {
59
+ severity: 'error',
60
+ ruleId: 'configBundler',
61
+ visitor: {
62
+ ref: {
63
+ leave(node: OasRef, ctx: UserContext, resolved: ResolveResult<any>) {
64
+ replaceRef(node, resolved, ctx);
65
+ },
66
+ },
67
+ },
68
+ },
69
+ ],
70
+ types
71
+ );
72
+
73
+ walkDocument({
74
+ document,
75
+ rootType: types.ConfigRoot,
76
+ normalizedVisitors: bundleVisitor,
77
+ resolvedRefMap,
78
+ ctx,
79
+ });
80
+
81
+ return document.parsed ?? {};
82
+ }
83
+
42
84
  export async function bundle(
43
85
  opts: {
44
86
  ref?: string;
@@ -254,6 +296,18 @@ export function mapTypeToComponent(typeName: string, version: SpecMajorVersion)
254
296
  }
255
297
  }
256
298
 
299
+ function replaceRef(ref: OasRef, resolved: ResolveResult<any>, ctx: UserContext) {
300
+ if (!isPlainObject(resolved.node)) {
301
+ ctx.parent[ctx.key] = resolved.node;
302
+ } else {
303
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
304
+ // @ts-ignore
305
+ delete ref.$ref;
306
+ const obj = Object.assign({}, resolved.node, ref);
307
+ Object.assign(ref, obj); // assign ref itself again so ref fields take precedence
308
+ }
309
+ }
310
+
257
311
  // function oas3Move
258
312
 
259
313
  function makeBundleVisitor(
@@ -351,19 +405,6 @@ function makeBundleVisitor(
351
405
  });
352
406
  }
353
407
 
354
- function replaceRef(ref: OasRef, resolved: ResolveResult<any>, ctx: UserContext) {
355
- if (!isPlainObject(resolved.node)) {
356
- ctx.parent[ctx.key] = resolved.node;
357
- } else {
358
- // TODO: why $ref isn't optional in OasRef?
359
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
360
- // @ts-ignore
361
- delete ref.$ref;
362
- const obj = Object.assign({}, resolved.node, ref);
363
- Object.assign(ref, obj); // assign ref itself again so ref fields take precedence
364
- }
365
- }
366
-
367
408
  function saveComponent(
368
409
  componentType: string,
369
410
  target: { node: any; location: Location },
@@ -21,6 +21,7 @@ exports[`resolveConfig should ignore minimal from the root and read local file 1
21
21
  "oas3_0Decorators": {},
22
22
  "oas3_0Preprocessors": {},
23
23
  "oas3_0Rules": {
24
+ "array-parameter-serialization": "off",
24
25
  "boolean-parameter-prefixes": "error",
25
26
  "component-name-unique": "off",
26
27
  "no-empty-servers": "error",
@@ -40,6 +41,7 @@ exports[`resolveConfig should ignore minimal from the root and read local file 1
40
41
  "oas3_1Decorators": {},
41
42
  "oas3_1Preprocessors": {},
42
43
  "oas3_1Rules": {
44
+ "array-parameter-serialization": "off",
43
45
  "boolean-parameter-prefixes": "error",
44
46
  "component-name-unique": "off",
45
47
  "no-empty-servers": "error",
@@ -125,6 +127,7 @@ exports[`resolveStyleguideConfig should resolve extends with local file config w
125
127
  "oas3_0Decorators": {},
126
128
  "oas3_0Preprocessors": {},
127
129
  "oas3_0Rules": {
130
+ "array-parameter-serialization": "off",
128
131
  "boolean-parameter-prefixes": "error",
129
132
  "component-name-unique": "off",
130
133
  "no-empty-servers": "error",
@@ -144,6 +147,7 @@ exports[`resolveStyleguideConfig should resolve extends with local file config w
144
147
  "oas3_1Decorators": {},
145
148
  "oas3_1Preprocessors": {},
146
149
  "oas3_1Rules": {
150
+ "array-parameter-serialization": "off",
147
151
  "boolean-parameter-prefixes": "error",
148
152
  "component-name-unique": "off",
149
153
  "no-empty-servers": "error",
@@ -0,0 +1,8 @@
1
+ rules:
2
+ $ref: rules.yaml
3
+
4
+ seo:
5
+ $ref: seo.yaml
6
+
7
+ theme:
8
+ $ref: wrong-ref.yaml
@@ -0,0 +1,2 @@
1
+ info-license: error
2
+ non-existing-rule: warn # fail
@@ -1,7 +1,10 @@
1
1
  import { loadConfig, findConfig, getConfig, createConfig } from '../load';
2
2
  import { RedoclyClient } from '../../redocly';
3
- import { RuleConfig, FlatRawConfig } from './../types';
4
3
  import { Config } from '../config';
4
+ import { lintConfig } from '../../lint';
5
+ import { replaceSourceWithRef } from '../../../__tests__/utils';
6
+ import type { RuleConfig, FlatRawConfig } from './../types';
7
+ import type { NormalizedProblem } from '../../walk';
5
8
 
6
9
  const fs = require('fs');
7
10
  const path = require('path');
@@ -91,6 +94,82 @@ describe('getConfig', () => {
91
94
  it('should return empty object if there is no configPath and config file is not found', () => {
92
95
  expect(getConfig()).toEqual(Promise.resolve({}));
93
96
  });
97
+
98
+ it('should resolve refs in config', async () => {
99
+ let problems: NormalizedProblem[];
100
+ const result = await getConfig({
101
+ configPath: path.join(__dirname, './fixtures/resolve-refs-in-config/config-with-refs.yaml'),
102
+ processRawConfig: async (config, resolvedRefMap) => {
103
+ problems = await lintConfig({
104
+ document: config,
105
+ severity: 'warn',
106
+ resolvedRefMap,
107
+ });
108
+ },
109
+ });
110
+ expect(result).toEqual({
111
+ seo: {
112
+ title: 1,
113
+ },
114
+ styleguide: {
115
+ rules: {
116
+ 'info-license': 'error',
117
+ 'non-existing-rule': 'warn',
118
+ },
119
+ },
120
+ });
121
+ expect(replaceSourceWithRef(problems!, __dirname)).toMatchInlineSnapshot(`
122
+ [
123
+ {
124
+ "from": {
125
+ "pointer": "#/seo",
126
+ "source": "fixtures/resolve-refs-in-config/config-with-refs.yaml",
127
+ },
128
+ "location": [
129
+ {
130
+ "pointer": "#/title",
131
+ "reportOnKey": false,
132
+ "source": "fixtures/resolve-refs-in-config/seo.yaml",
133
+ },
134
+ ],
135
+ "message": "Expected type \`string\` but got \`integer\`.",
136
+ "ruleId": "configuration spec",
137
+ "severity": "warn",
138
+ "suggest": [],
139
+ },
140
+ {
141
+ "from": {
142
+ "pointer": "#/rules",
143
+ "source": "fixtures/resolve-refs-in-config/config-with-refs.yaml",
144
+ },
145
+ "location": [
146
+ {
147
+ "pointer": "#/non-existing-rule",
148
+ "reportOnKey": true,
149
+ "source": "fixtures/resolve-refs-in-config/rules.yaml",
150
+ },
151
+ ],
152
+ "message": "Property \`non-existing-rule\` is not expected here.",
153
+ "ruleId": "configuration spec",
154
+ "severity": "warn",
155
+ "suggest": [],
156
+ },
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
+ ]
171
+ `);
172
+ });
94
173
  });
95
174
 
96
175
  describe('createConfig', () => {
package/src/config/all.ts CHANGED
@@ -78,6 +78,7 @@ const all: PluginStyleguideConfig<'built-in'> = {
78
78
  'component-name-unique': 'error',
79
79
  'response-contains-property': 'error',
80
80
  'spec-components-invalid-map-name': 'error',
81
+ 'array-parameter-serialization': 'error',
81
82
  },
82
83
  oas3_1Rules: {
83
84
  'no-invalid-media-type-examples': 'error',
@@ -101,6 +102,7 @@ const all: PluginStyleguideConfig<'built-in'> = {
101
102
  'component-name-unique': 'error',
102
103
  'response-contains-property': 'error',
103
104
  'spec-components-invalid-map-name': 'error',
105
+ 'array-parameter-serialization': 'error',
104
106
  },
105
107
  async2Rules: {
106
108
  'channels-kebab-case': 'error',