@redocly/openapi-core 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (275) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/__tests__/utils.ts +88 -0
  3. package/lib/config/all.js +0 -1
  4. package/lib/config/minimal.js +0 -1
  5. package/lib/config/recommended.js +0 -1
  6. package/package.json +1 -1
  7. package/src/__tests__/__snapshots__/bundle.test.ts.snap +437 -0
  8. package/src/__tests__/bundle.test.ts +236 -0
  9. package/src/__tests__/codeframes.test.ts +530 -0
  10. package/src/__tests__/fixtures/.redocly.lint-ignore.yaml +5 -0
  11. package/src/__tests__/fixtures/extension.js +24 -0
  12. package/src/__tests__/fixtures/refs/definitions.yaml +3 -0
  13. package/src/__tests__/fixtures/refs/examples.yaml +8 -0
  14. package/src/__tests__/fixtures/refs/external-request-body.yaml +13 -0
  15. package/src/__tests__/fixtures/refs/externalref.yaml +35 -0
  16. package/src/__tests__/fixtures/refs/hosted.yaml +35 -0
  17. package/src/__tests__/fixtures/refs/openapi-with-external-refs-conflicting-names.yaml +21 -0
  18. package/src/__tests__/fixtures/refs/openapi-with-external-refs.yaml +33 -0
  19. package/src/__tests__/fixtures/refs/openapi-with-url-refs.yaml +18 -0
  20. package/src/__tests__/fixtures/refs/param-b.yaml +1 -0
  21. package/src/__tests__/fixtures/refs/param-c.yaml +1 -0
  22. package/src/__tests__/fixtures/refs/rename.yaml +1 -0
  23. package/src/__tests__/fixtures/refs/requestBody.yaml +9 -0
  24. package/src/__tests__/fixtures/refs/schema-a.yaml +1 -0
  25. package/src/__tests__/fixtures/refs/simple.yaml +1 -0
  26. package/src/__tests__/fixtures/refs/vendor.schema.yaml +20 -0
  27. package/src/__tests__/fixtures/resolve/External.yaml +10 -0
  28. package/src/__tests__/fixtures/resolve/External2.yaml +4 -0
  29. package/src/__tests__/fixtures/resolve/description.md +3 -0
  30. package/src/__tests__/fixtures/resolve/externalInfo.yaml +4 -0
  31. package/src/__tests__/fixtures/resolve/externalLicense.yaml +1 -0
  32. package/src/__tests__/fixtures/resolve/openapi-with-back.yaml +13 -0
  33. package/src/__tests__/fixtures/resolve/openapi-with-md-description.yaml +5 -0
  34. package/src/__tests__/fixtures/resolve/openapi.yaml +28 -0
  35. package/src/__tests__/fixtures/resolve/schemas/type-a.yaml +10 -0
  36. package/src/__tests__/fixtures/resolve/schemas/type-b.yaml +6 -0
  37. package/src/__tests__/fixtures/resolve/transitive/a.yaml +1 -0
  38. package/src/__tests__/fixtures/resolve/transitive/components.yaml +5 -0
  39. package/src/__tests__/fixtures/resolve/transitive/schemas.yaml +3 -0
  40. package/src/__tests__/format.test.ts +76 -0
  41. package/src/__tests__/js-yaml.test.ts +73 -0
  42. package/src/__tests__/lint.test.ts +392 -0
  43. package/src/__tests__/logger-browser.test.ts +53 -0
  44. package/src/__tests__/logger.test.ts +47 -0
  45. package/src/__tests__/login.test.ts +17 -0
  46. package/src/__tests__/normalizeVisitors.test.ts +151 -0
  47. package/src/__tests__/output-browser.test.ts +18 -0
  48. package/src/__tests__/output.test.ts +15 -0
  49. package/src/__tests__/ref-utils.test.ts +120 -0
  50. package/src/__tests__/resolve-http.test.ts +77 -0
  51. package/src/__tests__/resolve.test.ts +431 -0
  52. package/src/__tests__/utils-browser.test.ts +11 -0
  53. package/src/__tests__/utils.test.ts +144 -0
  54. package/src/__tests__/walk.test.ts +1545 -0
  55. package/src/benchmark/benches/lint-with-many-rules.bench.ts +35 -0
  56. package/src/benchmark/benches/lint-with-nested-rule.bench.ts +39 -0
  57. package/src/benchmark/benches/lint-with-no-rules.bench.ts +20 -0
  58. package/src/benchmark/benches/lint-with-top-level-rule-report.bench.ts +35 -0
  59. package/src/benchmark/benches/lint-with-top-level-rule.bench.ts +32 -0
  60. package/src/benchmark/benches/rebilly.yaml +32275 -0
  61. package/src/benchmark/benches/recommended-oas3.bench.ts +22 -0
  62. package/src/benchmark/benches/resolve-with-no-external.bench.ts +23 -0
  63. package/src/benchmark/benchmark.js +311 -0
  64. package/src/benchmark/colors.js +29 -0
  65. package/src/benchmark/fork.js +83 -0
  66. package/src/benchmark/utils.ts +36 -0
  67. package/src/bundle.ts +417 -0
  68. package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +164 -0
  69. package/src/config/__tests__/__snapshots__/config.test.ts.snap +144 -0
  70. package/src/config/__tests__/config-resolvers.test.ts +491 -0
  71. package/src/config/__tests__/config.test.ts +312 -0
  72. package/src/config/__tests__/fixtures/ingore-file.ts +8 -0
  73. package/src/config/__tests__/fixtures/load-redocly.yaml +2 -0
  74. package/src/config/__tests__/fixtures/plugin-config.yaml +2 -0
  75. package/src/config/__tests__/fixtures/plugin.js +56 -0
  76. package/src/config/__tests__/fixtures/resolve-config/api/nested-config.yaml +11 -0
  77. package/src/config/__tests__/fixtures/resolve-config/api/plugin.js +69 -0
  78. package/src/config/__tests__/fixtures/resolve-config/local-config-with-circular.yaml +7 -0
  79. package/src/config/__tests__/fixtures/resolve-config/local-config-with-custom-function.yaml +17 -0
  80. package/src/config/__tests__/fixtures/resolve-config/local-config-with-file.yaml +18 -0
  81. package/src/config/__tests__/fixtures/resolve-config/local-config-with-wrong-custom-function.yaml +15 -0
  82. package/src/config/__tests__/fixtures/resolve-config/local-config.yaml +9 -0
  83. package/src/config/__tests__/fixtures/resolve-config/plugin.js +80 -0
  84. package/src/config/__tests__/fixtures/resolve-remote-configs/nested-remote-config.yaml +3 -0
  85. package/src/config/__tests__/fixtures/resolve-remote-configs/remote-config.yaml +4 -0
  86. package/src/config/__tests__/load.test.ts +167 -0
  87. package/src/config/__tests__/resolve-plugins.test.ts +27 -0
  88. package/src/config/__tests__/utils.test.ts +204 -0
  89. package/src/config/all.ts +74 -0
  90. package/src/config/builtIn.ts +37 -0
  91. package/src/config/config-resolvers.ts +474 -0
  92. package/src/config/config.ts +332 -0
  93. package/src/config/index.ts +7 -0
  94. package/src/config/load.ts +144 -0
  95. package/src/config/minimal.ts +61 -0
  96. package/src/config/recommended.ts +61 -0
  97. package/src/config/rules.ts +54 -0
  98. package/src/config/types.ts +231 -0
  99. package/src/config/utils.ts +349 -0
  100. package/src/decorators/__tests__/filter-in.test.ts +310 -0
  101. package/src/decorators/__tests__/filter-out.test.ts +335 -0
  102. package/src/decorators/__tests__/media-type-examples-override.test.ts +665 -0
  103. package/src/decorators/__tests__/remove-x-internal.test.ts +316 -0
  104. package/src/decorators/__tests__/resources/request.yaml +3 -0
  105. package/src/decorators/__tests__/resources/response.yaml +3 -0
  106. package/src/decorators/common/filters/filter-helper.ts +72 -0
  107. package/src/decorators/common/filters/filter-in.ts +18 -0
  108. package/src/decorators/common/filters/filter-out.ts +18 -0
  109. package/src/decorators/common/info-description-override.ts +24 -0
  110. package/src/decorators/common/info-override.ts +15 -0
  111. package/src/decorators/common/media-type-examples-override.ts +79 -0
  112. package/src/decorators/common/operation-description-override.ts +30 -0
  113. package/src/decorators/common/registry-dependencies.ts +25 -0
  114. package/src/decorators/common/remove-x-internal.ts +59 -0
  115. package/src/decorators/common/tag-description-override.ts +25 -0
  116. package/src/decorators/oas2/index.ts +20 -0
  117. package/src/decorators/oas3/index.ts +22 -0
  118. package/src/env.ts +5 -0
  119. package/src/format/codeframes.ts +216 -0
  120. package/src/format/format.ts +375 -0
  121. package/src/index.ts +71 -0
  122. package/src/js-yaml/index.ts +14 -0
  123. package/src/lint.ts +148 -0
  124. package/src/logger.ts +34 -0
  125. package/src/oas-types.ts +57 -0
  126. package/src/output.ts +7 -0
  127. package/src/redocly/__tests__/redocly-client.test.ts +146 -0
  128. package/src/redocly/index.ts +187 -0
  129. package/src/redocly/redocly-client-types.ts +10 -0
  130. package/src/redocly/registry-api-types.ts +32 -0
  131. package/src/redocly/registry-api.ts +150 -0
  132. package/src/ref-utils.ts +85 -0
  133. package/src/resolve.ts +417 -0
  134. package/src/rules/__tests__/fixtures/code-sample.php +9 -0
  135. package/src/rules/__tests__/fixtures/invalid-yaml.yaml +1 -0
  136. package/src/rules/__tests__/fixtures/ref.yaml +1 -0
  137. package/src/rules/__tests__/no-unresolved-refs.test.ts +257 -0
  138. package/src/rules/__tests__/utils.test.ts +160 -0
  139. package/src/rules/ajv.ts +102 -0
  140. package/src/rules/common/__tests__/info-license.test.ts +62 -0
  141. package/src/rules/common/__tests__/license-url.test.ts +63 -0
  142. package/src/rules/common/__tests__/no-ambiguous-paths.test.ts +96 -0
  143. package/src/rules/common/__tests__/no-enum-type-mismatch.test.ts +210 -0
  144. package/src/rules/common/__tests__/no-identical-paths.test.ts +58 -0
  145. package/src/rules/common/__tests__/no-path-trailing-slash.test.ts +85 -0
  146. package/src/rules/common/__tests__/operation-2xx-response.test.ts +192 -0
  147. package/src/rules/common/__tests__/operation-4xx-response.test.ts +231 -0
  148. package/src/rules/common/__tests__/operation-operationId-unique.test.ts +76 -0
  149. package/src/rules/common/__tests__/operation-operationId-url-safe.test.ts +45 -0
  150. package/src/rules/common/__tests__/operation-parameters-unique.test.ts +167 -0
  151. package/src/rules/common/__tests__/operation-singular-tag.test.ts +72 -0
  152. package/src/rules/common/__tests__/path-http-verbs-order.test.ts +95 -0
  153. package/src/rules/common/__tests__/path-not-include-query.test.ts +64 -0
  154. package/src/rules/common/__tests__/path-params-defined.test.ts +202 -0
  155. package/src/rules/common/__tests__/paths-kebab-case.test.ts +108 -0
  156. package/src/rules/common/__tests__/scalar-property-missing-example.test.ts +264 -0
  157. package/src/rules/common/__tests__/security-defined.test.ts +175 -0
  158. package/src/rules/common/__tests__/spec-strict-refs.test.ts +69 -0
  159. package/src/rules/common/__tests__/spec.test.ts +610 -0
  160. package/src/rules/common/__tests__/tag-description.test.ts +65 -0
  161. package/src/rules/common/__tests__/tags-alphabetical.test.ts +64 -0
  162. package/src/rules/common/assertions/__tests__/asserts.test.ts +869 -0
  163. package/src/rules/common/assertions/__tests__/index.test.ts +100 -0
  164. package/src/rules/common/assertions/__tests__/utils.test.ts +236 -0
  165. package/src/rules/common/assertions/asserts.ts +357 -0
  166. package/src/rules/common/assertions/index.ts +53 -0
  167. package/src/rules/common/assertions/utils.ts +331 -0
  168. package/src/rules/common/info-contact.ts +15 -0
  169. package/src/rules/common/info-license-url.ts +10 -0
  170. package/src/rules/common/info-license.ts +15 -0
  171. package/src/rules/common/no-ambiguous-paths.ts +50 -0
  172. package/src/rules/common/no-enum-type-mismatch.ts +52 -0
  173. package/src/rules/common/no-http-verbs-in-paths.ts +36 -0
  174. package/src/rules/common/no-identical-paths.ts +24 -0
  175. package/src/rules/common/no-invalid-parameter-examples.ts +36 -0
  176. package/src/rules/common/no-invalid-schema-examples.ts +27 -0
  177. package/src/rules/common/no-path-trailing-slash.ts +15 -0
  178. package/src/rules/common/operation-2xx-response.ts +24 -0
  179. package/src/rules/common/operation-4xx-response.ts +24 -0
  180. package/src/rules/common/operation-description.ts +13 -0
  181. package/src/rules/common/operation-operationId-unique.ts +21 -0
  182. package/src/rules/common/operation-operationId-url-safe.ts +19 -0
  183. package/src/rules/common/operation-operationId.ts +17 -0
  184. package/src/rules/common/operation-parameters-unique.ts +48 -0
  185. package/src/rules/common/operation-singular-tag.ts +17 -0
  186. package/src/rules/common/operation-summary.ts +13 -0
  187. package/src/rules/common/operation-tag-defined.ts +26 -0
  188. package/src/rules/common/parameter-description.ts +22 -0
  189. package/src/rules/common/path-declaration-must-exist.ts +15 -0
  190. package/src/rules/common/path-excludes-patterns.ts +23 -0
  191. package/src/rules/common/path-http-verbs-order.ts +30 -0
  192. package/src/rules/common/path-not-include-query.ts +17 -0
  193. package/src/rules/common/path-params-defined.ts +65 -0
  194. package/src/rules/common/path-segment-plural.ts +31 -0
  195. package/src/rules/common/paths-kebab-case.ts +19 -0
  196. package/src/rules/common/required-string-property-missing-min-length.ts +44 -0
  197. package/src/rules/common/response-contains-header.ts +35 -0
  198. package/src/rules/common/scalar-property-missing-example.ts +58 -0
  199. package/src/rules/common/security-defined.ts +65 -0
  200. package/src/rules/common/spec-strict-refs.ts +30 -0
  201. package/src/rules/common/spec.ts +175 -0
  202. package/src/rules/common/tag-description.ts +10 -0
  203. package/src/rules/common/tags-alphabetical.ts +20 -0
  204. package/src/rules/no-unresolved-refs.ts +51 -0
  205. package/src/rules/oas2/__tests__/boolean-parameter-prefixes.test.ts +110 -0
  206. package/src/rules/oas2/__tests__/response-contains-header.test.ts +174 -0
  207. package/src/rules/oas2/__tests__/response-contains-property.test.ts +155 -0
  208. package/src/rules/oas2/__tests__/spec/fixtures/description.md +1 -0
  209. package/src/rules/oas2/__tests__/spec/info.test.ts +355 -0
  210. package/src/rules/oas2/__tests__/spec/operation.test.ts +123 -0
  211. package/src/rules/oas2/__tests__/spec/paths.test.ts +245 -0
  212. package/src/rules/oas2/__tests__/spec/referenceableScalars.test.ts +35 -0
  213. package/src/rules/oas2/__tests__/spec/utils.ts +32 -0
  214. package/src/rules/oas2/boolean-parameter-prefixes.ts +26 -0
  215. package/src/rules/oas2/index.ts +91 -0
  216. package/src/rules/oas2/remove-unused-components.ts +81 -0
  217. package/src/rules/oas2/request-mime-type.ts +16 -0
  218. package/src/rules/oas2/response-contains-property.ts +36 -0
  219. package/src/rules/oas2/response-mime-type.ts +16 -0
  220. package/src/rules/oas3/__tests__/boolean-parameter-prefixes.test.ts +111 -0
  221. package/src/rules/oas3/__tests__/component-name-unique.test.ts +823 -0
  222. package/src/rules/oas3/__tests__/fixtures/common.yaml +11 -0
  223. package/src/rules/oas3/__tests__/no-empty-enum-servers.com.test.ts +205 -0
  224. package/src/rules/oas3/__tests__/no-example-value-and-externalValue.test.ts +65 -0
  225. package/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts +473 -0
  226. package/src/rules/oas3/__tests__/no-server-example.com.test.ts +60 -0
  227. package/src/rules/oas3/__tests__/no-server-trailing-slash.test.ts +79 -0
  228. package/src/rules/oas3/__tests__/no-unused-components.test.ts +131 -0
  229. package/src/rules/oas3/__tests__/operation-4xx-problem-details-rfc7807.test.ts +145 -0
  230. package/src/rules/oas3/__tests__/response-contains-header.test.ts +389 -0
  231. package/src/rules/oas3/__tests__/response-contains-property.test.ts +403 -0
  232. package/src/rules/oas3/__tests__/spec/callbacks.test.ts +41 -0
  233. package/src/rules/oas3/__tests__/spec/fixtures/description.md +1 -0
  234. package/src/rules/oas3/__tests__/spec/info.test.ts +391 -0
  235. package/src/rules/oas3/__tests__/spec/operation.test.ts +253 -0
  236. package/src/rules/oas3/__tests__/spec/paths.test.ts +284 -0
  237. package/src/rules/oas3/__tests__/spec/referenceableScalars.test.ts +77 -0
  238. package/src/rules/oas3/__tests__/spec/servers.test.ts +505 -0
  239. package/src/rules/oas3/__tests__/spec/spec.test.ts +298 -0
  240. package/src/rules/oas3/__tests__/spec/utils.ts +32 -0
  241. package/src/rules/oas3/__tests__/spec-components-invalid-map-name.test.ts +276 -0
  242. package/src/rules/oas3/__tests__/utils/lint-document-for-test.ts +23 -0
  243. package/src/rules/oas3/boolean-parameter-prefixes.ts +28 -0
  244. package/src/rules/oas3/component-name-unique.ts +158 -0
  245. package/src/rules/oas3/index.ts +113 -0
  246. package/src/rules/oas3/no-empty-servers.ts +22 -0
  247. package/src/rules/oas3/no-example-value-and-externalValue.ts +14 -0
  248. package/src/rules/oas3/no-invalid-media-type-examples.ts +49 -0
  249. package/src/rules/oas3/no-server-example.com.ts +14 -0
  250. package/src/rules/oas3/no-server-trailing-slash.ts +15 -0
  251. package/src/rules/oas3/no-server-variables-empty-enum.ts +66 -0
  252. package/src/rules/oas3/no-undefined-server-variable.ts +30 -0
  253. package/src/rules/oas3/no-unused-components.ts +75 -0
  254. package/src/rules/oas3/operation-4xx-problem-details-rfc7807.ts +35 -0
  255. package/src/rules/oas3/remove-unused-components.ts +95 -0
  256. package/src/rules/oas3/request-mime-type.ts +30 -0
  257. package/src/rules/oas3/response-contains-property.ts +38 -0
  258. package/src/rules/oas3/response-mime-type.ts +30 -0
  259. package/src/rules/oas3/spec-components-invalid-map-name.ts +69 -0
  260. package/src/rules/other/stats.ts +73 -0
  261. package/src/rules/utils.ts +193 -0
  262. package/src/types/config-external-schemas.ts +917 -0
  263. package/src/types/index.ts +149 -0
  264. package/src/types/oas2.ts +478 -0
  265. package/src/types/oas3.ts +597 -0
  266. package/src/types/oas3_1.ts +258 -0
  267. package/src/types/redocly-yaml.ts +1040 -0
  268. package/src/typings/common.ts +17 -0
  269. package/src/typings/openapi.ts +298 -0
  270. package/src/typings/swagger.ts +236 -0
  271. package/src/utils.ts +276 -0
  272. package/src/visitors.ts +491 -0
  273. package/src/walk.ts +439 -0
  274. package/tsconfig.json +8 -0
  275. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,474 @@
1
+ import * as path from 'path';
2
+ import { isAbsoluteUrl } from '../ref-utils';
3
+ import { pickDefined } from '../utils';
4
+ import { BaseResolver } from '../resolve';
5
+ import { defaultPlugin } from './builtIn';
6
+ import {
7
+ getResolveConfig,
8
+ getUniquePlugins,
9
+ mergeExtends,
10
+ parsePresetName,
11
+ prefixRules,
12
+ transformConfig,
13
+ } from './utils';
14
+ import type {
15
+ StyleguideRawConfig,
16
+ ApiStyleguideRawConfig,
17
+ Plugin,
18
+ RawConfig,
19
+ ResolvedApi,
20
+ ResolvedStyleguideConfig,
21
+ RuleConfig,
22
+ DeprecatedInRawConfig,
23
+ } from './types';
24
+ import { isBrowser } from '../env';
25
+ import { isNotString, isString, isDefined, parseYaml, keysOf } from '../utils';
26
+ import { Config } from './config';
27
+ import { colorize, logger } from '../logger';
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';
35
+
36
+ export async function resolveConfig(rawConfig: RawConfig, configPath?: string): Promise<Config> {
37
+ if (rawConfig.styleguide?.extends?.some(isNotString)) {
38
+ throw new Error(
39
+ `Error configuration format not detected in extends value must contain strings`
40
+ );
41
+ }
42
+
43
+ const resolver = new BaseResolver(getResolveConfig(rawConfig.resolve));
44
+
45
+ const apis = await resolveApis({
46
+ rawConfig,
47
+ configPath,
48
+ resolver,
49
+ });
50
+
51
+ const styleguide = await resolveStyleguideConfig({
52
+ styleguideConfig: rawConfig.styleguide,
53
+ configPath,
54
+ resolver,
55
+ });
56
+
57
+ return new Config(
58
+ {
59
+ ...rawConfig,
60
+ apis,
61
+ styleguide,
62
+ },
63
+ configPath
64
+ );
65
+ }
66
+
67
+ export function resolvePlugins(
68
+ plugins: (string | Plugin)[] | null,
69
+ configPath: string = ''
70
+ ): Plugin[] {
71
+ if (!plugins) return [];
72
+
73
+ // TODO: implement or reuse Resolver approach so it will work in node and browser envs
74
+ const requireFunc = (plugin: string | Plugin): Plugin | undefined => {
75
+ if (isBrowser && isString(plugin)) {
76
+ logger.error(`Cannot load ${plugin}. Plugins aren't supported in browser yet.`);
77
+
78
+ return undefined;
79
+ }
80
+
81
+ if (isString(plugin)) {
82
+ try {
83
+ const absoltePluginPath = path.resolve(path.dirname(configPath), plugin);
84
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
85
+ // @ts-ignore
86
+ return typeof __webpack_require__ === 'function'
87
+ ? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
88
+ // @ts-ignore
89
+ __non_webpack_require__(absoltePluginPath)
90
+ : require(absoltePluginPath);
91
+ } catch (e) {
92
+ if (e instanceof SyntaxError) {
93
+ throw e;
94
+ }
95
+ throw new Error(`Failed to load plugin "${plugin}". Please provide a valid path`);
96
+ }
97
+ }
98
+
99
+ return plugin;
100
+ };
101
+
102
+ const seenPluginIds = new Map<string, string>();
103
+
104
+ return plugins
105
+ .map((p) => {
106
+ if (isString(p) && isAbsoluteUrl(p)) {
107
+ throw new Error(colorize.red(`We don't support remote plugins yet.`));
108
+ }
109
+
110
+ // TODO: resolve npm packages similar to eslint
111
+ const pluginModule = requireFunc(p);
112
+
113
+ if (!pluginModule) {
114
+ return;
115
+ }
116
+
117
+ const id = pluginModule.id;
118
+ if (typeof id !== 'string') {
119
+ throw new Error(
120
+ colorize.red(`Plugin must define \`id\` property in ${colorize.blue(p.toString())}.`)
121
+ );
122
+ }
123
+
124
+ if (seenPluginIds.has(id)) {
125
+ const pluginPath = seenPluginIds.get(id)!;
126
+ throw new Error(
127
+ colorize.red(
128
+ `Plugin "id" must be unique. Plugin ${colorize.blue(
129
+ p.toString()
130
+ )} uses id "${colorize.blue(id)}" already seen in ${colorize.blue(pluginPath)}`
131
+ )
132
+ );
133
+ }
134
+
135
+ seenPluginIds.set(id, p.toString());
136
+
137
+ const plugin: Plugin = {
138
+ id,
139
+ ...(pluginModule.configs ? { configs: pluginModule.configs } : {}),
140
+ ...(pluginModule.typeExtension ? { typeExtension: pluginModule.typeExtension } : {}),
141
+ };
142
+
143
+ if (pluginModule.rules) {
144
+ if (!pluginModule.rules.oas3 && !pluginModule.rules.oas2) {
145
+ throw new Error(`Plugin rules must have \`oas3\` or \`oas2\` rules "${p}.`);
146
+ }
147
+ plugin.rules = {};
148
+ if (pluginModule.rules.oas3) {
149
+ plugin.rules.oas3 = prefixRules(pluginModule.rules.oas3, id);
150
+ }
151
+ if (pluginModule.rules.oas2) {
152
+ plugin.rules.oas2 = prefixRules(pluginModule.rules.oas2, id);
153
+ }
154
+ }
155
+ if (pluginModule.preprocessors) {
156
+ if (!pluginModule.preprocessors.oas3 && !pluginModule.preprocessors.oas2) {
157
+ throw new Error(
158
+ `Plugin \`preprocessors\` must have \`oas3\` or \`oas2\` preprocessors "${p}.`
159
+ );
160
+ }
161
+ plugin.preprocessors = {};
162
+ if (pluginModule.preprocessors.oas3) {
163
+ plugin.preprocessors.oas3 = prefixRules(pluginModule.preprocessors.oas3, id);
164
+ }
165
+ if (pluginModule.preprocessors.oas2) {
166
+ plugin.preprocessors.oas2 = prefixRules(pluginModule.preprocessors.oas2, id);
167
+ }
168
+ }
169
+
170
+ if (pluginModule.decorators) {
171
+ if (!pluginModule.decorators.oas3 && !pluginModule.decorators.oas2) {
172
+ throw new Error(`Plugin \`decorators\` must have \`oas3\` or \`oas2\` decorators "${p}.`);
173
+ }
174
+ plugin.decorators = {};
175
+ if (pluginModule.decorators.oas3) {
176
+ plugin.decorators.oas3 = prefixRules(pluginModule.decorators.oas3, id);
177
+ }
178
+ if (pluginModule.decorators.oas2) {
179
+ plugin.decorators.oas2 = prefixRules(pluginModule.decorators.oas2, id);
180
+ }
181
+ }
182
+
183
+ if (pluginModule.assertions) {
184
+ plugin.assertions = pluginModule.assertions;
185
+ }
186
+
187
+ return plugin;
188
+ })
189
+ .filter(isDefined);
190
+ }
191
+
192
+ export async function resolveApis({
193
+ rawConfig,
194
+ configPath = '',
195
+ resolver,
196
+ }: {
197
+ rawConfig: RawConfig;
198
+ configPath?: string;
199
+ resolver?: BaseResolver;
200
+ }): Promise<Record<string, ResolvedApi>> {
201
+ const { apis = {}, styleguide: styleguideConfig = {} } = rawConfig;
202
+ const resolvedApis: Record<string, ResolvedApi> = {};
203
+ for (const [apiName, apiContent] of Object.entries(apis || {})) {
204
+ if (apiContent.styleguide?.extends?.some(isNotString)) {
205
+ throw new Error(
206
+ `Error configuration format not detected in extends value must contain strings`
207
+ );
208
+ }
209
+ const rawStyleguideConfig = getMergedRawStyleguideConfig(
210
+ styleguideConfig,
211
+ apiContent.styleguide
212
+ );
213
+ const resolvedApiConfig = await resolveStyleguideConfig({
214
+ styleguideConfig: rawStyleguideConfig,
215
+ configPath,
216
+ resolver,
217
+ });
218
+ resolvedApis[apiName] = { ...apiContent, styleguide: resolvedApiConfig };
219
+ }
220
+ return resolvedApis;
221
+ }
222
+
223
+ async function resolveAndMergeNestedStyleguideConfig(
224
+ {
225
+ styleguideConfig,
226
+ configPath = '',
227
+ resolver = new BaseResolver(),
228
+ }: {
229
+ styleguideConfig?: StyleguideRawConfig;
230
+ configPath?: string;
231
+ resolver?: BaseResolver;
232
+ },
233
+ parentConfigPaths: string[] = [],
234
+ extendPaths: string[] = []
235
+ ): Promise<ResolvedStyleguideConfig> {
236
+ if (parentConfigPaths.includes(configPath)) {
237
+ throw new Error(`Circular dependency in config file: "${configPath}"`);
238
+ }
239
+ const plugins = getUniquePlugins(
240
+ resolvePlugins([...(styleguideConfig?.plugins || []), defaultPlugin], configPath)
241
+ );
242
+ const pluginPaths = styleguideConfig?.plugins
243
+ ?.filter(isString)
244
+ .map((p) => path.resolve(path.dirname(configPath), p));
245
+
246
+ const resolvedConfigPath = isAbsoluteUrl(configPath)
247
+ ? configPath
248
+ : configPath && path.resolve(configPath);
249
+
250
+ const extendConfigs: ResolvedStyleguideConfig[] = await Promise.all(
251
+ styleguideConfig?.extends?.map(async (presetItem) => {
252
+ if (!isAbsoluteUrl(presetItem) && !path.extname(presetItem)) {
253
+ return resolvePreset(presetItem, plugins);
254
+ }
255
+ const pathItem = isAbsoluteUrl(presetItem)
256
+ ? presetItem
257
+ : isAbsoluteUrl(configPath)
258
+ ? new URL(presetItem, configPath).href
259
+ : path.resolve(path.dirname(configPath), presetItem);
260
+ const extendedStyleguideConfig = await loadExtendStyleguideConfig(pathItem, resolver);
261
+ return await resolveAndMergeNestedStyleguideConfig(
262
+ {
263
+ styleguideConfig: extendedStyleguideConfig,
264
+ configPath: pathItem,
265
+ resolver: resolver,
266
+ },
267
+ [...parentConfigPaths, resolvedConfigPath],
268
+ extendPaths
269
+ );
270
+ }) || []
271
+ );
272
+
273
+ const { plugins: mergedPlugins = [], ...styleguide } = mergeExtends([
274
+ ...extendConfigs,
275
+ {
276
+ ...styleguideConfig,
277
+ plugins,
278
+ extends: undefined,
279
+ extendPaths: [...parentConfigPaths, resolvedConfigPath],
280
+ pluginPaths,
281
+ },
282
+ ]);
283
+
284
+ return {
285
+ ...styleguide,
286
+ extendPaths: styleguide.extendPaths?.filter((path) => path && !isAbsoluteUrl(path)),
287
+ plugins: getUniquePlugins(mergedPlugins),
288
+ recommendedFallback: styleguideConfig?.recommendedFallback,
289
+ doNotResolveExamples: styleguideConfig?.doNotResolveExamples,
290
+ };
291
+ }
292
+
293
+ export async function resolveStyleguideConfig(
294
+ opts: {
295
+ styleguideConfig?: StyleguideRawConfig;
296
+ configPath?: string;
297
+ resolver?: BaseResolver;
298
+ },
299
+ parentConfigPaths: string[] = [],
300
+ extendPaths: string[] = []
301
+ ): Promise<ResolvedStyleguideConfig> {
302
+ const resolvedStyleguideConfig = await resolveAndMergeNestedStyleguideConfig(
303
+ opts,
304
+ parentConfigPaths,
305
+ extendPaths
306
+ );
307
+
308
+ return {
309
+ ...resolvedStyleguideConfig,
310
+ rules:
311
+ resolvedStyleguideConfig.rules && groupStyleguideAssertionRules(resolvedStyleguideConfig),
312
+ };
313
+ }
314
+
315
+ export function resolvePreset(presetName: string, plugins: Plugin[]): ResolvedStyleguideConfig {
316
+ const { pluginId, configName } = parsePresetName(presetName);
317
+ const plugin = plugins.find((p) => p.id === pluginId);
318
+ if (!plugin) {
319
+ throw new Error(
320
+ `Invalid config ${colorize.red(presetName)}: plugin ${pluginId} is not included.`
321
+ );
322
+ }
323
+
324
+ const preset = plugin.configs?.[configName];
325
+ if (!preset) {
326
+ throw new Error(
327
+ pluginId
328
+ ? `Invalid config ${colorize.red(
329
+ presetName
330
+ )}: plugin ${pluginId} doesn't export config with name ${configName}.`
331
+ : `Invalid config ${colorize.red(presetName)}: there is no such built-in config.`
332
+ );
333
+ }
334
+ return preset;
335
+ }
336
+
337
+ async function loadExtendStyleguideConfig(
338
+ filePath: string,
339
+ resolver: BaseResolver
340
+ ): Promise<StyleguideRawConfig> {
341
+ try {
342
+ const fileSource = await resolver.loadExternalRef(filePath);
343
+ const rawConfig = transformConfig(
344
+ parseYaml(fileSource.body) as RawConfig & DeprecatedInRawConfig
345
+ );
346
+ if (!rawConfig.styleguide) {
347
+ throw new Error(`Styleguide configuration format not detected: "${filePath}"`);
348
+ }
349
+
350
+ return rawConfig.styleguide;
351
+ } catch (error) {
352
+ throw new Error(`Failed to load "${filePath}": ${error.message}`);
353
+ }
354
+ }
355
+
356
+ function getMergedRawStyleguideConfig(
357
+ rootStyleguideConfig: StyleguideRawConfig,
358
+ apiStyleguideConfig?: ApiStyleguideRawConfig
359
+ ) {
360
+ const resultLint = {
361
+ ...rootStyleguideConfig,
362
+ ...pickDefined(apiStyleguideConfig),
363
+ rules: { ...rootStyleguideConfig?.rules, ...apiStyleguideConfig?.rules },
364
+ oas2Rules: { ...rootStyleguideConfig?.oas2Rules, ...apiStyleguideConfig?.oas2Rules },
365
+ oas3_0Rules: { ...rootStyleguideConfig?.oas3_0Rules, ...apiStyleguideConfig?.oas3_0Rules },
366
+ oas3_1Rules: { ...rootStyleguideConfig?.oas3_1Rules, ...apiStyleguideConfig?.oas3_1Rules },
367
+ preprocessors: {
368
+ ...rootStyleguideConfig?.preprocessors,
369
+ ...apiStyleguideConfig?.preprocessors,
370
+ },
371
+ oas2Preprocessors: {
372
+ ...rootStyleguideConfig?.oas2Preprocessors,
373
+ ...apiStyleguideConfig?.oas2Preprocessors,
374
+ },
375
+ oas3_0Preprocessors: {
376
+ ...rootStyleguideConfig?.oas3_0Preprocessors,
377
+ ...apiStyleguideConfig?.oas3_0Preprocessors,
378
+ },
379
+ oas3_1Preprocessors: {
380
+ ...rootStyleguideConfig?.oas3_1Preprocessors,
381
+ ...apiStyleguideConfig?.oas3_1Preprocessors,
382
+ },
383
+ decorators: { ...rootStyleguideConfig?.decorators, ...apiStyleguideConfig?.decorators },
384
+ oas2Decorators: {
385
+ ...rootStyleguideConfig?.oas2Decorators,
386
+ ...apiStyleguideConfig?.oas2Decorators,
387
+ },
388
+ oas3_0Decorators: {
389
+ ...rootStyleguideConfig?.oas3_0Decorators,
390
+ ...apiStyleguideConfig?.oas3_0Decorators,
391
+ },
392
+ oas3_1Decorators: {
393
+ ...rootStyleguideConfig?.oas3_1Decorators,
394
+ ...apiStyleguideConfig?.oas3_1Decorators,
395
+ },
396
+ recommendedFallback: apiStyleguideConfig?.extends
397
+ ? false
398
+ : rootStyleguideConfig.recommendedFallback,
399
+ };
400
+ return resultLint;
401
+ }
402
+
403
+ function groupStyleguideAssertionRules({
404
+ rules,
405
+ plugins,
406
+ }: ResolvedStyleguideConfig): Record<string, RuleConfig> | undefined {
407
+ if (!rules) {
408
+ return rules;
409
+ }
410
+
411
+ // Create a new record to avoid mutating original
412
+ const transformedRules: Record<string, RuleConfig> = {};
413
+
414
+ // Collect assertion rules
415
+ const assertions: Assertion[] = [];
416
+ for (const [ruleKey, rule] of Object.entries(rules)) {
417
+ // keep the old assert/ syntax as an alias
418
+
419
+ if (
420
+ (ruleKey.startsWith('rule/') || ruleKey.startsWith('assert/')) &&
421
+ typeof rule === 'object' &&
422
+ rule !== null
423
+ ) {
424
+ const assertion = rule as RawAssertion;
425
+
426
+ if (plugins) {
427
+ registerCustomAssertions(plugins, assertion);
428
+
429
+ // We may have custom assertion inside where block
430
+ for (const context of assertion.where || []) {
431
+ registerCustomAssertions(plugins, context);
432
+ }
433
+ }
434
+ assertions.push({
435
+ ...assertion,
436
+ assertionId: ruleKey,
437
+ });
438
+ } else {
439
+ // If it's not an assertion, keep it as is
440
+ transformedRules[ruleKey] = rule;
441
+ }
442
+ }
443
+ if (assertions.length > 0) {
444
+ transformedRules.assertions = assertions;
445
+ }
446
+
447
+ return transformedRules;
448
+ }
449
+
450
+ function registerCustomAssertions(plugins: Plugin[], assertion: AssertionDefinition) {
451
+ for (const field of keysOf(assertion.assertions)) {
452
+ const [pluginId, fn] = field.split('/');
453
+
454
+ if (!pluginId || !fn) continue;
455
+
456
+ const plugin = plugins.find((plugin) => plugin.id === pluginId);
457
+
458
+ if (!plugin) {
459
+ throw Error(colorize.red(`Plugin ${colorize.blue(pluginId)} isn't found.`));
460
+ }
461
+
462
+ if (!plugin.assertions || !plugin.assertions[fn]) {
463
+ throw Error(
464
+ `Plugin ${colorize.red(
465
+ pluginId
466
+ )} doesn't export assertions function with name ${colorize.red(fn)}.`
467
+ );
468
+ }
469
+
470
+ (asserts as Asserts & { [name: string]: AssertionFn })[field] = buildAssertCustomFunction(
471
+ plugin.assertions[fn]
472
+ );
473
+ }
474
+ }