@redocly/openapi-core 1.0.0-beta.108 → 1.0.0-beta.110

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 (159) hide show
  1. package/README.md +2 -2
  2. package/lib/benchmark/benches/resolve-with-no-external.bench.js +1 -1
  3. package/lib/bundle.d.ts +1 -1
  4. package/lib/bundle.js +4 -4
  5. package/lib/config/all.js +3 -1
  6. package/lib/config/config-resolvers.js +22 -4
  7. package/lib/config/config.d.ts +1 -0
  8. package/lib/config/config.js +1 -0
  9. package/lib/config/load.d.ts +8 -2
  10. package/lib/config/load.js +4 -2
  11. package/lib/config/minimal.js +3 -1
  12. package/lib/config/recommended.js +3 -1
  13. package/lib/config/rules.js +1 -1
  14. package/lib/config/types.d.ts +17 -0
  15. package/lib/config/utils.d.ts +2 -2
  16. package/lib/config/utils.js +44 -6
  17. package/lib/decorators/common/registry-dependencies.js +1 -1
  18. package/lib/format/format.d.ts +1 -1
  19. package/lib/format/format.js +22 -1
  20. package/lib/lint.js +2 -2
  21. package/lib/redocly/registry-api.d.ts +0 -1
  22. package/lib/redocly/registry-api.js +5 -4
  23. package/lib/resolve.js +3 -1
  24. package/lib/rules/ajv.d.ts +1 -1
  25. package/lib/rules/ajv.js +5 -5
  26. package/lib/rules/common/assertions/asserts.d.ts +3 -5
  27. package/lib/rules/common/assertions/asserts.js +137 -97
  28. package/lib/rules/common/assertions/index.js +2 -6
  29. package/lib/rules/common/assertions/utils.d.ts +12 -6
  30. package/lib/rules/common/assertions/utils.js +33 -20
  31. package/lib/rules/common/no-ambiguous-paths.js +1 -1
  32. package/lib/rules/common/no-identical-paths.js +1 -1
  33. package/lib/rules/common/operation-2xx-response.js +1 -1
  34. package/lib/rules/common/operation-4xx-response.js +1 -1
  35. package/lib/rules/common/operation-operationId.js +1 -1
  36. package/lib/rules/common/operation-tag-defined.js +1 -1
  37. package/lib/rules/common/path-not-include-query.js +1 -1
  38. package/lib/rules/common/security-defined.d.ts +2 -0
  39. package/lib/rules/common/{operation-security-defined.js → security-defined.js} +18 -4
  40. package/lib/rules/common/spec.js +12 -1
  41. package/lib/rules/common/tags-alphabetical.js +1 -1
  42. package/lib/rules/oas2/index.d.ts +1 -1
  43. package/lib/rules/oas2/index.js +2 -2
  44. package/lib/rules/oas2/remove-unused-components.js +1 -1
  45. package/lib/rules/oas2/request-mime-type.js +1 -1
  46. package/lib/rules/oas2/response-mime-type.js +1 -1
  47. package/lib/rules/oas3/index.js +6 -2
  48. package/lib/rules/oas3/no-empty-servers.js +1 -1
  49. package/lib/rules/oas3/no-server-variables-empty-enum.js +1 -1
  50. package/lib/rules/oas3/no-unused-components.js +1 -1
  51. package/lib/rules/oas3/operation-4xx-problem-details-rfc7807.d.ts +5 -0
  52. package/lib/rules/oas3/operation-4xx-problem-details-rfc7807.js +36 -0
  53. package/lib/rules/oas3/remove-unused-components.js +1 -1
  54. package/lib/rules/oas3/request-mime-type.js +1 -1
  55. package/lib/rules/oas3/response-mime-type.js +1 -1
  56. package/lib/rules/oas3/spec-components-invalid-map-name.d.ts +2 -0
  57. package/lib/rules/oas3/spec-components-invalid-map-name.js +46 -0
  58. package/lib/rules/other/stats.d.ts +2 -2
  59. package/lib/rules/other/stats.js +2 -2
  60. package/lib/rules/utils.js +1 -1
  61. package/lib/types/oas2.js +5 -5
  62. package/lib/types/oas3.js +27 -20
  63. package/lib/types/oas3_1.js +3 -3
  64. package/lib/types/redocly-yaml.js +60 -54
  65. package/lib/utils.d.ts +3 -3
  66. package/lib/utils.js +5 -5
  67. package/lib/visitors.d.ts +11 -11
  68. package/lib/visitors.js +13 -1
  69. package/package.json +3 -5
  70. package/src/__tests__/__snapshots__/bundle.test.ts.snap +3 -3
  71. package/src/__tests__/fixtures/extension.js +3 -3
  72. package/src/__tests__/format.test.ts +76 -0
  73. package/src/__tests__/lint.test.ts +184 -121
  74. package/src/__tests__/resolve-http.test.ts +1 -1
  75. package/src/__tests__/resolve.test.ts +9 -9
  76. package/src/__tests__/walk.test.ts +78 -10
  77. package/src/benchmark/benches/resolve-with-no-external.bench.ts +1 -1
  78. package/src/bundle.ts +4 -4
  79. package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +6 -2
  80. package/src/config/__tests__/config-resolvers.test.ts +37 -1
  81. package/src/config/__tests__/config.test.ts +5 -0
  82. package/src/config/__tests__/fixtures/plugin-config.yaml +2 -3
  83. package/src/config/__tests__/fixtures/resolve-config/api/nested-config.yaml +11 -12
  84. package/src/config/__tests__/fixtures/resolve-config/local-config-with-circular.yaml +7 -8
  85. package/src/config/__tests__/fixtures/resolve-config/local-config-with-custom-function.yaml +16 -0
  86. package/src/config/__tests__/fixtures/resolve-config/local-config-with-file.yaml +18 -19
  87. package/src/config/__tests__/fixtures/resolve-config/local-config-with-wrong-custom-function.yaml +16 -0
  88. package/src/config/__tests__/fixtures/resolve-config/local-config.yaml +9 -10
  89. package/src/config/__tests__/fixtures/resolve-config/plugin.js +11 -0
  90. package/src/config/__tests__/fixtures/resolve-remote-configs/nested-remote-config.yaml +3 -4
  91. package/src/config/__tests__/fixtures/resolve-remote-configs/remote-config.yaml +4 -5
  92. package/src/config/__tests__/load.test.ts +13 -16
  93. package/src/config/__tests__/resolve-plugins.test.ts +3 -3
  94. package/src/config/__tests__/utils.test.ts +64 -4
  95. package/src/config/all.ts +3 -1
  96. package/src/config/config-resolvers.ts +30 -7
  97. package/src/config/config.ts +2 -0
  98. package/src/config/load.ts +13 -6
  99. package/src/config/minimal.ts +3 -1
  100. package/src/config/recommended.ts +3 -1
  101. package/src/config/rules.ts +2 -2
  102. package/src/config/types.ts +24 -0
  103. package/src/config/utils.ts +103 -13
  104. package/src/decorators/common/registry-dependencies.ts +1 -1
  105. package/src/format/format.ts +32 -2
  106. package/src/lint.ts +2 -2
  107. package/src/redocly/registry-api.ts +5 -4
  108. package/src/resolve.ts +3 -1
  109. package/src/rules/__tests__/utils.test.ts +1 -1
  110. package/src/rules/ajv.ts +4 -4
  111. package/src/rules/common/__tests__/no-enum-type-mismatch.test.ts +1 -0
  112. package/src/rules/common/__tests__/operation-2xx-response.test.ts +1 -1
  113. package/src/rules/common/__tests__/operation-4xx-response.test.ts +26 -3
  114. package/src/rules/common/__tests__/security-defined.test.ts +175 -0
  115. package/src/rules/common/__tests__/spec.test.ts +79 -0
  116. package/src/rules/common/assertions/__tests__/asserts.test.ts +491 -428
  117. package/src/rules/common/assertions/__tests__/utils.test.ts +2 -2
  118. package/src/rules/common/assertions/asserts.ts +155 -97
  119. package/src/rules/common/assertions/index.ts +2 -11
  120. package/src/rules/common/assertions/utils.ts +66 -36
  121. package/src/rules/common/no-ambiguous-paths.ts +1 -1
  122. package/src/rules/common/no-identical-paths.ts +1 -1
  123. package/src/rules/common/operation-2xx-response.ts +1 -1
  124. package/src/rules/common/operation-4xx-response.ts +1 -1
  125. package/src/rules/common/operation-operationId.ts +1 -1
  126. package/src/rules/common/operation-tag-defined.ts +1 -1
  127. package/src/rules/common/path-not-include-query.ts +1 -1
  128. package/src/rules/common/{operation-security-defined.ts → security-defined.ts} +19 -4
  129. package/src/rules/common/spec.ts +15 -1
  130. package/src/rules/common/tags-alphabetical.ts +1 -1
  131. package/src/rules/oas2/index.ts +2 -2
  132. package/src/rules/oas2/remove-unused-components.ts +1 -1
  133. package/src/rules/oas2/request-mime-type.ts +1 -1
  134. package/src/rules/oas2/response-mime-type.ts +1 -1
  135. package/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts +51 -2
  136. package/src/rules/oas3/__tests__/operation-4xx-problem-details-rfc7807.test.ts +145 -0
  137. package/src/rules/oas3/__tests__/spec/spec.test.ts +10 -0
  138. package/src/rules/oas3/__tests__/spec-components-invalid-map-name.test.ts +217 -0
  139. package/src/rules/oas3/index.ts +6 -2
  140. package/src/rules/oas3/no-empty-servers.ts +1 -1
  141. package/src/rules/oas3/no-server-variables-empty-enum.ts +1 -1
  142. package/src/rules/oas3/no-unused-components.ts +1 -1
  143. package/src/rules/oas3/operation-4xx-problem-details-rfc7807.ts +36 -0
  144. package/src/rules/oas3/remove-unused-components.ts +1 -1
  145. package/src/rules/oas3/request-mime-type.ts +1 -1
  146. package/src/rules/oas3/response-mime-type.ts +1 -1
  147. package/src/rules/oas3/spec-components-invalid-map-name.ts +53 -0
  148. package/src/rules/other/stats.ts +2 -2
  149. package/src/rules/utils.ts +2 -1
  150. package/src/types/index.ts +2 -2
  151. package/src/types/oas2.ts +5 -5
  152. package/src/types/oas3.ts +27 -20
  153. package/src/types/oas3_1.ts +3 -3
  154. package/src/types/redocly-yaml.ts +66 -38
  155. package/src/utils.ts +11 -7
  156. package/src/visitors.ts +29 -13
  157. package/tsconfig.tsbuildinfo +1 -1
  158. package/lib/rules/common/operation-security-defined.d.ts +0 -2
  159. package/src/rules/common/__tests__/operation-security-defined.test.ts +0 -69
@@ -306,6 +306,7 @@ export class Config {
306
306
  'features.openapi': Record<string, any>;
307
307
  'features.mockServer'?: Record<string, any>;
308
308
  organization?: string;
309
+ files: string[];
309
310
  constructor(public rawConfig: ResolvedConfig, public configFile?: string) {
310
311
  this.apis = rawConfig.apis || {};
311
312
  this.styleguide = new StyleguideConfig(rawConfig.styleguide || {}, configFile);
@@ -314,5 +315,6 @@ export class Config {
314
315
  this.resolve = getResolveConfig(rawConfig?.resolve);
315
316
  this.region = rawConfig.region;
316
317
  this.organization = rawConfig.organization;
318
+ this.files = rawConfig.files || [];
317
319
  }
318
320
  }
@@ -7,7 +7,7 @@ import { Config, DOMAINS } from './config';
7
7
  import { transformConfig } from './utils';
8
8
  import { resolveConfig } from './config-resolvers';
9
9
 
10
- import type { DeprecatedInRawConfig, RawConfig, Region } from './types';
10
+ import type { DeprecatedInRawConfig, FlatRawConfig, RawConfig, Region } from './types';
11
11
  import { RegionalTokenWithValidity } from '../redocly/redocly-client-types';
12
12
 
13
13
  async function addConfigMetadata({
@@ -62,11 +62,17 @@ async function addConfigMetadata({
62
62
  }
63
63
 
64
64
  export async function loadConfig(
65
- configPath: string | undefined = findConfig(),
66
- customExtends?: string[],
67
- processRawConfig?: (rawConfig: RawConfig) => void | Promise<void>
65
+ options: {
66
+ configPath?: string;
67
+ customExtends?: string[];
68
+ processRawConfig?: (rawConfig: RawConfig) => void | Promise<void>;
69
+ files?: string[];
70
+ region?: Region;
71
+ } = {}
68
72
  ): Promise<Config> {
69
- const rawConfig = await getConfig(configPath);
73
+ const { configPath = findConfig(), customExtends, processRawConfig, files, region } = options;
74
+ const config = await getConfig(configPath);
75
+ const rawConfig = { ...config, files: files ?? config.files, region: region ?? config.region };
70
76
  if (typeof processRawConfig === 'function') {
71
77
  await processRawConfig(rawConfig);
72
78
  }
@@ -102,7 +108,8 @@ export function findConfig(dir?: string): string | undefined {
102
108
  export async function getConfig(configPath: string | undefined = findConfig()): Promise<RawConfig> {
103
109
  if (!configPath || !doesYamlFileExist(configPath)) return {};
104
110
  try {
105
- const rawConfig = (await loadYaml<RawConfig & DeprecatedInRawConfig>(configPath)) || {};
111
+ const rawConfig =
112
+ (await loadYaml<RawConfig & DeprecatedInRawConfig & FlatRawConfig>(configPath)) || {};
106
113
  return transformConfig(rawConfig);
107
114
  } catch (e) {
108
115
  throw new Error(`Error parsing config file at '${configPath}': ${e.message}`);
@@ -24,7 +24,7 @@ export default {
24
24
  'operation-operationId-unique': 'warn',
25
25
  'operation-parameters-unique': 'warn',
26
26
  'operation-tag-defined': 'off',
27
- 'operation-security-defined': 'warn',
27
+ 'security-defined': 'warn',
28
28
  'operation-operationId-url-safe': 'warn',
29
29
  'operation-singular-tag': 'off',
30
30
  'no-unresolved-refs': 'error',
@@ -45,6 +45,7 @@ export default {
45
45
  'no-unused-components': 'warn',
46
46
  'no-undefined-server-variable': 'warn',
47
47
  'no-server-variables-empty-enum': 'error',
48
+ 'spec-components-invalid-map-name': 'warn',
48
49
  },
49
50
  oas3_1Rules: {
50
51
  'no-invalid-media-type-examples': 'warn',
@@ -55,5 +56,6 @@ export default {
55
56
  'no-unused-components': 'warn',
56
57
  'no-undefined-server-variable': 'warn',
57
58
  'no-server-variables-empty-enum': 'error',
59
+ 'spec-components-invalid-map-name': 'warn',
58
60
  },
59
61
  } as PluginStyleguideConfig;
@@ -25,7 +25,7 @@ export default {
25
25
  'operation-operationId-url-safe': 'error',
26
26
  'operation-parameters-unique': 'error',
27
27
  'operation-tag-defined': 'off',
28
- 'operation-security-defined': 'error',
28
+ 'security-defined': 'error',
29
29
  'operation-singular-tag': 'off',
30
30
  'no-unresolved-refs': 'error',
31
31
  'no-enum-type-mismatch': 'error',
@@ -45,6 +45,7 @@ export default {
45
45
  'no-unused-components': 'warn',
46
46
  'no-undefined-server-variable': 'error',
47
47
  'no-server-variables-empty-enum': 'error',
48
+ 'spec-components-invalid-map-name': 'error',
48
49
  },
49
50
  oas3_1Rules: {
50
51
  'no-invalid-media-type-examples': 'warn',
@@ -55,5 +56,6 @@ export default {
55
56
  'no-unused-components': 'warn',
56
57
  'no-undefined-server-variable': 'error',
57
58
  'no-server-variables-empty-enum': 'error',
59
+ 'spec-components-invalid-map-name': 'error',
58
60
  },
59
61
  } as PluginStyleguideConfig;
@@ -1,6 +1,6 @@
1
1
  import { RuleSet, OasVersion } from '../oas-types';
2
2
  import { StyleguideConfig } from './config';
3
- import { notUndefined } from '../utils';
3
+ import { isDefined } from '../utils';
4
4
 
5
5
  export function initRules<T extends Function, P extends RuleSet<T>>(
6
6
  rules: P[],
@@ -42,5 +42,5 @@ export function initRules<T extends Function, P extends RuleSet<T>>(
42
42
  })
43
43
  )
44
44
  .flatMap((visitor) => visitor)
45
- .filter(notUndefined);
45
+ .filter(isDefined);
46
46
  }
@@ -10,6 +10,7 @@ import type {
10
10
  OasVersion,
11
11
  } from '../oas-types';
12
12
  import type { NodeType } from '../types';
13
+ import { Location } from '../ref-utils';
13
14
 
14
15
  export type RuleSeverity = ProblemSeverity | 'off';
15
16
 
@@ -83,6 +84,15 @@ export type CustomRulesConfig = {
83
84
  oas2?: Oas2RuleSet;
84
85
  };
85
86
 
87
+ export type AssertResult = { message?: string; location?: Location };
88
+ export type CustomFunction = (
89
+ value: any,
90
+ options: unknown,
91
+ baseLocation: Location
92
+ ) => AssertResult[];
93
+
94
+ export type AssertionsConfig = Record<string, CustomFunction>;
95
+
86
96
  export type Plugin = {
87
97
  id: string;
88
98
  configs?: Record<string, PluginStyleguideConfig>;
@@ -90,6 +100,7 @@ export type Plugin = {
90
100
  preprocessors?: PreprocessorsConfig;
91
101
  decorators?: DecoratorsConfig;
92
102
  typeExtension?: TypeExtensionsConfig;
103
+ assertions?: AssertionsConfig;
93
104
  };
94
105
 
95
106
  export type PluginStyleguideConfig = Omit<StyleguideRawConfig, 'plugins' | 'extends'>;
@@ -110,6 +121,7 @@ export type ResolveHeader =
110
121
 
111
122
  export type RawResolveConfig = {
112
123
  http?: Partial<HttpResolveConfig>;
124
+ doNotResolveExamples?: boolean;
113
125
  };
114
126
 
115
127
  export type HttpResolveConfig = {
@@ -128,6 +140,7 @@ export type AccessTokens = { [region in Region]?: string };
128
140
  export type DeprecatedInRawConfig = {
129
141
  apiDefinitions?: Record<string, string>;
130
142
  lint?: StyleguideRawConfig;
143
+ styleguide?: StyleguideRawConfig;
131
144
  referenceDocs?: Record<string, any>;
132
145
  apis?: Record<string, Api & DeprecatedInApi>;
133
146
  };
@@ -143,6 +156,7 @@ export type DeprecatedInApi = {
143
156
 
144
157
  export type ResolvedApi = Omit<Api, 'styleguide'> & {
145
158
  styleguide: ResolvedStyleguideConfig;
159
+ files?: string[];
146
160
  };
147
161
 
148
162
  export type RawConfig = {
@@ -151,8 +165,18 @@ export type RawConfig = {
151
165
  resolve?: RawResolveConfig;
152
166
  region?: Region;
153
167
  organization?: string;
168
+ files?: string[];
154
169
  } & FeaturesConfig;
155
170
 
171
+ export type FlatApi = Omit<Api, 'styleguide'> &
172
+ Omit<ApiStyleguideRawConfig, 'doNotResolveExamples'>;
173
+
174
+ export type FlatRawConfig = Omit<RawConfig, 'styleguide' | 'resolve' | 'apis'> &
175
+ Omit<StyleguideRawConfig, 'doNotResolveExamples'> & {
176
+ resolve?: RawResolveConfig;
177
+ apis?: Record<string, FlatApi>;
178
+ };
179
+
156
180
  export type ResolvedConfig = Omit<RawConfig, 'apis' | 'styleguide'> & {
157
181
  apis: Record<string, ResolvedApi>;
158
182
  styleguide: ResolvedStyleguideConfig;
@@ -1,21 +1,24 @@
1
1
  import {
2
2
  assignExisting,
3
+ isDefined,
3
4
  isTruthy,
4
5
  showErrorForDeprecatedField,
5
6
  showWarningForDeprecatedField,
6
7
  } from '../utils';
7
8
  import { Config } from './config';
8
-
9
9
  import type {
10
10
  Api,
11
11
  DeprecatedInApi,
12
12
  DeprecatedInRawConfig,
13
+ FlatApi,
14
+ FlatRawConfig,
13
15
  Plugin,
14
16
  RawConfig,
15
17
  RawResolveConfig,
16
18
  ResolveConfig,
17
19
  ResolvedStyleguideConfig,
18
20
  RulesFields,
21
+ StyleguideRawConfig,
19
22
  } from './types';
20
23
  import { logger, colorize } from '../logger';
21
24
 
@@ -39,13 +42,86 @@ export function transformApiDefinitionsToApis(
39
42
  return apis;
40
43
  }
41
44
 
45
+ function extractFlatConfig<
46
+ T extends Partial<
47
+ (Api & DeprecatedInApi & FlatApi) & (DeprecatedInRawConfig & RawConfig & FlatRawConfig)
48
+ >
49
+ >({
50
+ plugins,
51
+ extends: _extends,
52
+
53
+ rules,
54
+ oas2Rules,
55
+ oas3_0Rules,
56
+ oas3_1Rules,
57
+
58
+ preprocessors,
59
+ oas2Preprocessors,
60
+ oas3_0Preprocessors,
61
+ oas3_1Preprocessors,
62
+
63
+ decorators,
64
+ oas2Decorators,
65
+ oas3_0Decorators,
66
+ oas3_1Decorators,
67
+
68
+ ...rawConfigRest
69
+ }: T): {
70
+ styleguideConfig?: StyleguideRawConfig;
71
+ rawConfigRest: Omit<T, 'plugins' | 'extends' | RulesFields>;
72
+ } {
73
+ const styleguideConfig = {
74
+ plugins,
75
+ extends: _extends,
76
+
77
+ rules,
78
+ oas2Rules,
79
+ oas3_0Rules,
80
+ oas3_1Rules,
81
+
82
+ preprocessors,
83
+ oas2Preprocessors,
84
+ oas3_0Preprocessors,
85
+ oas3_1Preprocessors,
86
+
87
+ decorators,
88
+ oas2Decorators,
89
+ oas3_0Decorators,
90
+ oas3_1Decorators,
91
+
92
+ doNotResolveExamples: (rawConfigRest as FlatRawConfig).resolve?.doNotResolveExamples,
93
+ };
94
+
95
+ if (
96
+ (rawConfigRest.lint && rawConfigRest.styleguide) ||
97
+ (Object.values(styleguideConfig).some(isDefined) &&
98
+ (rawConfigRest.lint || rawConfigRest.styleguide))
99
+ ) {
100
+ throw new Error(
101
+ `Do not use 'lint', 'styleguide' and flat syntax together. \nSee more about the configuration in the docs: https://redocly.com/docs/cli/configuration/ \n`
102
+ );
103
+ }
104
+
105
+ return {
106
+ styleguideConfig: Object.values(styleguideConfig).some(isDefined)
107
+ ? styleguideConfig
108
+ : undefined,
109
+
110
+ rawConfigRest,
111
+ };
112
+ }
113
+
42
114
  function transformApis(
43
- legacyApis?: Record<string, Api & DeprecatedInApi>
115
+ legacyApis?: Record<string, Api & DeprecatedInApi & FlatApi>
44
116
  ): Record<string, Api> | undefined {
45
117
  if (!legacyApis) return undefined;
46
118
  const apis: Record<string, Api> = {};
47
119
  for (const [apiName, { lint, ...apiContent }] of Object.entries(legacyApis)) {
48
- apis[apiName] = { styleguide: lint, ...apiContent };
120
+ const { styleguideConfig, rawConfigRest } = extractFlatConfig(apiContent);
121
+ apis[apiName] = {
122
+ styleguide: styleguideConfig || lint,
123
+ ...rawConfigRest,
124
+ };
49
125
  }
50
126
  return apis;
51
127
  }
@@ -157,6 +233,7 @@ export function getMergedConfig(config: Config, apiName?: string): Config {
157
233
  ...config['features.mockServer'],
158
234
  ...config.apis[apiName]?.['features.mockServer'],
159
235
  },
236
+ files: [...config.files, ...(config.apis?.[apiName]?.files ?? [])],
160
237
  // TODO: merge everything else here
161
238
  },
162
239
  config.configFile
@@ -165,17 +242,22 @@ export function getMergedConfig(config: Config, apiName?: string): Config {
165
242
  }
166
243
 
167
244
  function checkForDeprecatedFields(
168
- deprecatedField: keyof DeprecatedInRawConfig,
169
- updatedField: keyof RawConfig,
170
- rawConfig: DeprecatedInRawConfig & RawConfig
245
+ deprecatedField: keyof (DeprecatedInRawConfig & RawConfig),
246
+ updatedField: keyof FlatRawConfig | undefined,
247
+ rawConfig: DeprecatedInRawConfig & RawConfig & FlatRawConfig
171
248
  ): void {
172
249
  const isDeprecatedFieldInApis =
173
250
  rawConfig.apis &&
174
251
  Object.values(rawConfig.apis).some(
175
- (api: Api & DeprecatedInApi & DeprecatedInRawConfig) => api[deprecatedField]
252
+ (api: Api & FlatApi & DeprecatedInApi & DeprecatedInRawConfig & RawConfig & FlatRawConfig) =>
253
+ api[deprecatedField]
176
254
  );
177
255
 
178
- if (rawConfig[deprecatedField] && rawConfig[updatedField]) {
256
+ if (rawConfig[deprecatedField] && updatedField === null) {
257
+ showWarningForDeprecatedField(deprecatedField);
258
+ }
259
+
260
+ if (rawConfig[deprecatedField] && updatedField && rawConfig[updatedField]) {
179
261
  showErrorForDeprecatedField(deprecatedField, updatedField);
180
262
  }
181
263
 
@@ -184,11 +266,17 @@ function checkForDeprecatedFields(
184
266
  }
185
267
  }
186
268
 
187
- export function transformConfig(rawConfig: DeprecatedInRawConfig & RawConfig): RawConfig {
188
- const migratedFields: [keyof DeprecatedInRawConfig, keyof RawConfig][] = [
269
+ export function transformConfig(
270
+ rawConfig: DeprecatedInRawConfig & RawConfig & FlatRawConfig
271
+ ): RawConfig {
272
+ const migratedFields: [
273
+ keyof (DeprecatedInRawConfig & RawConfig),
274
+ keyof FlatRawConfig | undefined
275
+ ][] = [
189
276
  ['apiDefinitions', 'apis'],
190
277
  ['referenceDocs', 'features.openapi'],
191
- ['lint', 'styleguide'], // TODO: update docs
278
+ ['lint', undefined],
279
+ ['styleguide', undefined],
192
280
  ];
193
281
 
194
282
  for (const [deprecatedField, updatedField] of migratedFields) {
@@ -197,11 +285,13 @@ export function transformConfig(rawConfig: DeprecatedInRawConfig & RawConfig): R
197
285
 
198
286
  const { apis, apiDefinitions, referenceDocs, lint, ...rest } = rawConfig;
199
287
 
288
+ const { styleguideConfig, rawConfigRest } = extractFlatConfig(rest);
289
+
200
290
  return {
201
291
  'features.openapi': referenceDocs,
202
292
  apis: transformApis(apis) || transformApiDefinitionsToApis(apiDefinitions),
203
- styleguide: lint,
204
- ...rest,
293
+ styleguide: styleguideConfig || lint,
294
+ ...rawConfigRest,
205
295
  };
206
296
  }
207
297
 
@@ -7,7 +7,7 @@ export const RegistryDependencies: Oas3Decorator | Oas2Decorator = () => {
7
7
  const registryDependencies = new Set<string>();
8
8
 
9
9
  return {
10
- DefinitionRoot: {
10
+ Root: {
11
11
  leave(_: any, ctx: UserContext) {
12
12
  const data = ctx.getVisitorData();
13
13
  data.links = Array.from(registryDependencies);
@@ -44,7 +44,13 @@ function severityToNumber(severity: ProblemSeverity) {
44
44
  return severity === 'error' ? 1 : 2;
45
45
  }
46
46
 
47
- export type OutputFormat = 'codeframe' | 'stylish' | 'json' | 'checkstyle' | 'codeclimate';
47
+ export type OutputFormat =
48
+ | 'codeframe'
49
+ | 'stylish'
50
+ | 'json'
51
+ | 'checkstyle'
52
+ | 'codeclimate'
53
+ | 'summary';
48
54
 
49
55
  export function getTotals(problems: (NormalizedProblem & { ignored?: boolean })[]): Totals {
50
56
  let errors = 0;
@@ -143,6 +149,9 @@ export function formatProblems(
143
149
  case 'codeclimate':
144
150
  outputForCodeClimate();
145
151
  break;
152
+ case 'summary':
153
+ formatSummary(problems);
154
+ break;
146
155
  }
147
156
 
148
157
  if (totalProblems - ignoredProblems > maxProblems) {
@@ -257,13 +266,34 @@ export function formatProblems(
257
266
  }
258
267
  }
259
268
 
269
+ function formatSummary(problems: NormalizedProblem[]): void {
270
+ const counts: Record<string, { count: number; severity: ProblemSeverity }> = {};
271
+ for (const problem of problems) {
272
+ counts[problem.ruleId] = counts[problem.ruleId] || { count: 0, severity: problem.severity };
273
+ counts[problem.ruleId].count++;
274
+ }
275
+ const sorted = Object.entries(counts).sort(([, a], [, b]) => {
276
+ const severityDiff = severityToNumber(a.severity) - severityToNumber(b.severity);
277
+ return severityDiff || b.count - a.count;
278
+ });
279
+
280
+ for (const [ruleId, info] of sorted) {
281
+ const color = COLORS[info.severity];
282
+ const severityName = color(SEVERITY_NAMES[info.severity].toLowerCase().padEnd(7));
283
+ logger.info(`${severityName} ${ruleId}: ${info.count}\n`);
284
+ }
285
+
286
+ logger.info('\n');
287
+ }
288
+
260
289
  function formatFrom(cwd: string, location?: LocationObject) {
261
290
  if (!location) return '';
262
291
  const relativePath = path.relative(cwd, location.source.absoluteRef);
263
292
  const loc = getLineColLocation(location);
264
293
  const fileWithLoc = `${relativePath}:${loc.start.line}:${loc.start.col}`;
294
+ const atPointer = location.pointer ? colorize.gray(`at ${location.pointer}`) : '';
265
295
 
266
- return `referenced from ${colorize.blue(fileWithLoc)}\n\n`;
296
+ return `referenced from ${colorize.blue(fileWithLoc)} ${atPointer} \n\n`;
267
297
  }
268
298
 
269
299
  function formatDidYouMean(problem: NormalizedProblem) {
package/src/lint.ts CHANGED
@@ -80,13 +80,13 @@ export async function lintDocument(opts: {
80
80
  const normalizedVisitors = normalizeVisitors([...preprocessors, ...regularRules] as any, types);
81
81
  const resolvedRefMap = await resolveDocument({
82
82
  rootDocument: document,
83
- rootType: types.DefinitionRoot,
83
+ rootType: types.Root,
84
84
  externalRefResolver,
85
85
  });
86
86
 
87
87
  walkDocument({
88
88
  document,
89
- rootType: types.DefinitionRoot,
89
+ rootType: types.Root,
90
90
  normalizedVisitors,
91
91
  resolvedRefMap,
92
92
  ctx,
@@ -10,9 +10,6 @@ import { DEFAULT_REGION, DOMAINS } from '../config/config';
10
10
  import { isNotEmptyObject } from '../utils';
11
11
  const version = require('../../package.json').version;
12
12
 
13
- export const currentCommand =
14
- typeof process !== 'undefined' ? process.env?.REDOCLY_CLI_COMMAND || '' : '';
15
-
16
13
  export class RegistryApi {
17
14
  constructor(private accessTokens: AccessTokens, private region: Region) {}
18
15
 
@@ -30,9 +27,13 @@ export class RegistryApi {
30
27
  }
31
28
 
32
29
  private async request(path = '', options: RequestInit = {}, region?: Region) {
30
+ const currentCommand =
31
+ typeof process !== 'undefined' ? process.env?.REDOCLY_CLI_COMMAND || '' : '';
32
+ const redoclyEnv = typeof process !== 'undefined' ? process.env?.REDOCLY_ENVIRONMENT || '' : '';
33
+
33
34
  const headers = Object.assign({}, options.headers || {}, {
34
35
  'x-redocly-cli-version': version,
35
- 'user-agent': `redocly-cli / ${version} ${currentCommand}`,
36
+ 'user-agent': `redocly-cli / ${version} ${currentCommand} ${redoclyEnv}`,
36
37
  });
37
38
 
38
39
  if (!headers.hasOwnProperty('authorization')) {
package/src/resolve.ts CHANGED
@@ -115,7 +115,9 @@ export class BaseResolver {
115
115
  const { body, mimeType } = await readFileFromUrl(absoluteRef, this.config.http);
116
116
  return new Source(absoluteRef, body, mimeType);
117
117
  } else {
118
- return new Source(absoluteRef, await fs.promises.readFile(absoluteRef, 'utf-8'));
118
+ const content = await fs.promises.readFile(absoluteRef, 'utf-8');
119
+ // In some cases file have \r\n line delimeters like on windows, we should skip it.
120
+ return new Source(absoluteRef, content.replace(/\r\n/g, '\n'));
119
121
  }
120
122
  } catch (error) {
121
123
  throw new ResolveError(error);
@@ -153,7 +153,7 @@ describe('get-additional-properties-option', () => {
153
153
  } catch (error) {
154
154
  expect(error).toBeInstanceOf(Error);
155
155
  expect(error.message).toEqual(
156
- "Do not use 'disallowAdditionalProperties' field. Use 'allowAdditionalProperties' instead.\n"
156
+ "Do not use 'disallowAdditionalProperties' field. Use 'allowAdditionalProperties' instead. \n"
157
157
  );
158
158
  }
159
159
  });
package/src/rules/ajv.ts CHANGED
@@ -1,4 +1,4 @@
1
- import Ajv, { ValidateFunction, ErrorObject } from '@redocly/ajv';
1
+ import Ajv, { ValidateFunction, ErrorObject } from '@redocly/ajv/dist/2020';
2
2
  import { Location, escapePointer } from '../ref-utils';
3
3
  import { ResolveFn } from '../walk';
4
4
 
@@ -20,7 +20,7 @@ function getAjv(resolve: ResolveFn, allowAdditionalProperties: boolean) {
20
20
  discriminator: true,
21
21
  allowUnionTypes: true,
22
22
  validateFormats: false, // TODO: fix it
23
- defaultAdditionalProperties: allowAdditionalProperties,
23
+ defaultUnevaluatedProperties: allowAdditionalProperties,
24
24
  loadSchemaSync(base: string, $ref: string) {
25
25
  const resolvedRef = resolve({ $ref }, base.split('#')[0]);
26
26
  if (!resolvedRef || !resolvedRef.location) return false;
@@ -87,8 +87,8 @@ export function validateJsonSchema(
87
87
  if (propName) {
88
88
  message = `\`${propName}\` property ${message}`;
89
89
  }
90
- if (error.keyword === 'additionalProperties') {
91
- const property = error.params.additionalProperty;
90
+ if (error.keyword === 'additionalProperties' || error.keyword === 'unevaluatedProperties') {
91
+ const property = error.params.additionalProperty || error.params.unevaluatedProperty;
92
92
  message = `${message} \`${property}\``;
93
93
  error.instancePath += '/' + escapePointer(property);
94
94
  }
@@ -191,6 +191,7 @@ describe('Oas3 typed enum', () => {
191
191
  expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
192
192
  Array [
193
193
  Object {
194
+ "from": undefined,
194
195
  "location": Array [
195
196
  Object {
196
197
  "pointer": "#/paths/~1some/get/responses/200/content/application~1json/schema",
@@ -34,7 +34,7 @@ describe('Oas3 operation-2xx-response', () => {
34
34
  "source": "foobar.yaml",
35
35
  },
36
36
  ],
37
- "message": "Operation must have at least one \`2xx\` response.",
37
+ "message": "Operation must have at least one \`2XX\` response.",
38
38
  "ruleId": "operation-2xx-response",
39
39
  "severity": "error",
40
40
  "suggest": Array [],
@@ -34,7 +34,7 @@ describe('Oas3 operation-4xx-response', () => {
34
34
  "source": "foobar.yaml",
35
35
  },
36
36
  ],
37
- "message": "Operation must have at least one \`4xx\` response.",
37
+ "message": "Operation must have at least one \`4XX\` response.",
38
38
  "ruleId": "operation-4xx-response",
39
39
  "severity": "error",
40
40
  "suggest": Array [],
@@ -43,7 +43,7 @@ describe('Oas3 operation-4xx-response', () => {
43
43
  `);
44
44
  });
45
45
 
46
- it('should not report for present 4xx response', async () => {
46
+ it('should not report for present 400 response', async () => {
47
47
  const document = parseYamlToDocument(
48
48
  outdent`
49
49
  openapi: 3.0.0
@@ -66,6 +66,29 @@ describe('Oas3 operation-4xx-response', () => {
66
66
  expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
67
67
  });
68
68
 
69
+ it('should not report for present 4XX response', async () => {
70
+ const document = parseYamlToDocument(
71
+ outdent`
72
+ openapi: 3.0.0
73
+ paths:
74
+ '/test/':
75
+ put:
76
+ responses:
77
+ 4XX:
78
+ description: error response
79
+ `,
80
+ 'foobar.yaml'
81
+ );
82
+
83
+ const results = await lintDocument({
84
+ externalRefResolver: new BaseResolver(),
85
+ document,
86
+ config: await makeConfig({ 'operation-4xx-response': 'error' }),
87
+ });
88
+
89
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
90
+ });
91
+
69
92
  it('should report if default is present but missing 4xx response', async () => {
70
93
  const document = parseYamlToDocument(
71
94
  outdent`
@@ -96,7 +119,7 @@ describe('Oas3 operation-4xx-response', () => {
96
119
  "source": "foobar.yaml",
97
120
  },
98
121
  ],
99
- "message": "Operation must have at least one \`4xx\` response.",
122
+ "message": "Operation must have at least one \`4XX\` response.",
100
123
  "ruleId": "operation-4xx-response",
101
124
  "severity": "error",
102
125
  "suggest": Array [],