@redocly/openapi-core 1.0.0-rc.3 → 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,1545 @@
1
+ import outdent from 'outdent';
2
+ import each from 'jest-each';
3
+ import * as path from 'path';
4
+
5
+ import { lintDocument } from '../lint';
6
+
7
+ import {
8
+ parseYamlToDocument,
9
+ replaceSourceWithRef,
10
+ makeConfigForRuleset,
11
+ } from '../../__tests__/utils';
12
+ import { BaseResolver, Document } from '../resolve';
13
+ import { listOf } from '../types';
14
+ import { Oas3RuleSet } from '../oas-types';
15
+
16
+ describe('walk order', () => {
17
+ it('should run visitors', async () => {
18
+ const visitors = {
19
+ Root: {
20
+ enter: jest.fn(),
21
+ leave: jest.fn(),
22
+ },
23
+ Info: {
24
+ enter: jest.fn(),
25
+ leave: jest.fn(),
26
+ },
27
+ Contact: {
28
+ enter: jest.fn(),
29
+ leave: jest.fn(),
30
+ },
31
+ License: {
32
+ enter: jest.fn(),
33
+ leave: jest.fn(),
34
+ },
35
+ };
36
+
37
+ const testRuleSet: Oas3RuleSet = {
38
+ test: jest.fn(() => {
39
+ return visitors;
40
+ }),
41
+ };
42
+
43
+ const document = parseYamlToDocument(
44
+ outdent`
45
+ openapi: 3.0.0
46
+ info:
47
+ contact: {}
48
+ license: {}
49
+ `,
50
+ ''
51
+ );
52
+
53
+ await lintDocument({
54
+ externalRefResolver: new BaseResolver(),
55
+ document,
56
+ config: makeConfigForRuleset(testRuleSet),
57
+ });
58
+
59
+ expect(testRuleSet.test).toBeCalledTimes(1);
60
+ for (const fns of Object.values(visitors)) {
61
+ expect(fns.enter).toBeCalled();
62
+ expect(fns.leave).toBeCalled();
63
+ }
64
+ });
65
+
66
+ it('should run legacy visitors', async () => {
67
+ const visitors = {
68
+ DefinitionRoot: {
69
+ enter: jest.fn(),
70
+ leave: jest.fn(),
71
+ },
72
+ PathMap: {
73
+ enter: jest.fn(),
74
+ leave: jest.fn(),
75
+ },
76
+ ServerVariableMap: {
77
+ enter: jest.fn(),
78
+ leave: jest.fn(),
79
+ },
80
+ MediaTypeMap: {
81
+ enter: jest.fn(),
82
+ leave: jest.fn(),
83
+ },
84
+ ExampleMap: {
85
+ enter: jest.fn(),
86
+ leave: jest.fn(),
87
+ },
88
+ HeaderMap: {
89
+ enter: jest.fn(),
90
+ leave: jest.fn(),
91
+ },
92
+ };
93
+
94
+ const testRuleSet: Oas3RuleSet = {
95
+ test: jest.fn(() => {
96
+ return visitors;
97
+ }),
98
+ };
99
+
100
+ const document = parseYamlToDocument(
101
+ outdent`
102
+ openapi: 3.0.0
103
+ servers:
104
+ - url: http://{test}.url
105
+ variables:
106
+ test: test
107
+ paths:
108
+ /test-path:
109
+ get:
110
+ responses:
111
+ 200:
112
+ headers: {}
113
+ content:
114
+ application/json:
115
+ schema: {}
116
+ examples: {}
117
+ `,
118
+ ''
119
+ );
120
+
121
+ await lintDocument({
122
+ externalRefResolver: new BaseResolver(),
123
+ document,
124
+ config: makeConfigForRuleset(testRuleSet),
125
+ });
126
+
127
+ expect(testRuleSet.test).toBeCalledTimes(1);
128
+ for (const fns of Object.values(visitors)) {
129
+ expect(fns.enter).toBeCalled();
130
+ expect(fns.leave).toBeCalled();
131
+ }
132
+ });
133
+
134
+ it('should run nested visitors correctly', async () => {
135
+ const calls: string[] = [];
136
+
137
+ const testRuleSet: Oas3RuleSet = {
138
+ test: jest.fn(() => {
139
+ return {
140
+ Operation: {
141
+ enter: jest.fn((op) => calls.push(`enter operation: ${op.operationId}`)),
142
+ leave: jest.fn((op) => calls.push(`leave operation: ${op.operationId}`)),
143
+ Parameter: {
144
+ enter: jest.fn((param, _ctx, parents) =>
145
+ calls.push(`enter operation ${parents.Operation.operationId} > param ${param.name}`)
146
+ ),
147
+ leave: jest.fn((param, _ctx, parents) =>
148
+ calls.push(`leave operation ${parents.Operation.operationId} > param ${param.name}`)
149
+ ),
150
+ },
151
+ },
152
+ Parameter: {
153
+ enter: jest.fn((param) => calls.push(`enter param ${param.name}`)),
154
+ leave: jest.fn((param) => calls.push(`leave param ${param.name}`)),
155
+ },
156
+ };
157
+ }),
158
+ };
159
+
160
+ const document = parseYamlToDocument(
161
+ outdent`
162
+ openapi: 3.0.0
163
+ info:
164
+ contact: {}
165
+ license: {}
166
+ paths:
167
+ /pet:
168
+ parameters:
169
+ - name: path-param
170
+ get:
171
+ operationId: get
172
+ parameters:
173
+ - name: get_a
174
+ - name: get_b
175
+ post:
176
+ operationId: post
177
+ parameters:
178
+ - name: post_a
179
+
180
+ `,
181
+ ''
182
+ );
183
+
184
+ await lintDocument({
185
+ externalRefResolver: new BaseResolver(),
186
+ document,
187
+ config: makeConfigForRuleset(testRuleSet),
188
+ });
189
+
190
+ expect(calls).toMatchInlineSnapshot(`
191
+ Array [
192
+ "enter param path-param",
193
+ "leave param path-param",
194
+ "enter operation: get",
195
+ "enter operation get > param get_a",
196
+ "enter param get_a",
197
+ "leave param get_a",
198
+ "leave operation get > param get_a",
199
+ "enter operation get > param get_b",
200
+ "enter param get_b",
201
+ "leave param get_b",
202
+ "leave operation get > param get_b",
203
+ "leave operation: get",
204
+ "enter operation: post",
205
+ "enter operation post > param post_a",
206
+ "enter param post_a",
207
+ "leave param post_a",
208
+ "leave operation post > param post_a",
209
+ "leave operation: post",
210
+ ]
211
+ `);
212
+ });
213
+
214
+ it('should run nested visitors correctly oas2', async () => {
215
+ const calls: string[] = [];
216
+
217
+ const testRuleSet: Oas3RuleSet = {
218
+ test: jest.fn(() => {
219
+ return {
220
+ Operation: {
221
+ enter: jest.fn((op) => calls.push(`enter operation: ${op.operationId}`)),
222
+ leave: jest.fn((op) => calls.push(`leave operation: ${op.operationId}`)),
223
+ Parameter: {
224
+ enter: jest.fn((param, _ctx, parents) =>
225
+ calls.push(`enter operation ${parents.Operation.operationId} > param ${param.name}`)
226
+ ),
227
+ leave: jest.fn((param, _ctx, parents) =>
228
+ calls.push(`leave operation ${parents.Operation.operationId} > param ${param.name}`)
229
+ ),
230
+ },
231
+ },
232
+ Parameter: {
233
+ enter: jest.fn((param) => calls.push(`enter param ${param.name}`)),
234
+ leave: jest.fn((param) => calls.push(`leave param ${param.name}`)),
235
+ },
236
+ };
237
+ }),
238
+ };
239
+
240
+ const document = parseYamlToDocument(
241
+ outdent`
242
+ swagger: "2.0"
243
+ info:
244
+ contact: {}
245
+ license: {}
246
+ paths:
247
+ /pet:
248
+ parameters:
249
+ - name: path-param
250
+ get:
251
+ operationId: get
252
+ parameters:
253
+ - name: get_a
254
+ - name: get_b
255
+ post:
256
+ operationId: post
257
+ parameters:
258
+ - name: post_a
259
+
260
+ `,
261
+ ''
262
+ );
263
+
264
+ await lintDocument({
265
+ externalRefResolver: new BaseResolver(),
266
+ document,
267
+ config: makeConfigForRuleset(testRuleSet, undefined, 'oas2'),
268
+ });
269
+
270
+ expect(calls).toMatchInlineSnapshot(`
271
+ Array [
272
+ "enter param path-param",
273
+ "leave param path-param",
274
+ "enter operation: get",
275
+ "enter operation get > param get_a",
276
+ "enter param get_a",
277
+ "leave param get_a",
278
+ "leave operation get > param get_a",
279
+ "enter operation get > param get_b",
280
+ "enter param get_b",
281
+ "leave param get_b",
282
+ "leave operation get > param get_b",
283
+ "leave operation: get",
284
+ "enter operation: post",
285
+ "enter operation post > param post_a",
286
+ "enter param post_a",
287
+ "leave param post_a",
288
+ "leave operation post > param post_a",
289
+ "leave operation: post",
290
+ ]
291
+ `);
292
+ });
293
+
294
+ it('should resolve refs', async () => {
295
+ const calls: string[] = [];
296
+
297
+ const testRuleSet: Oas3RuleSet = {
298
+ test: jest.fn(() => {
299
+ return {
300
+ Operation: {
301
+ enter: jest.fn((op) => calls.push(`enter operation: ${op.operationId}`)),
302
+ leave: jest.fn((op) => calls.push(`leave operation: ${op.operationId}`)),
303
+ Parameter: {
304
+ enter: jest.fn((param, _ctx, parents) =>
305
+ calls.push(`enter operation ${parents.Operation.operationId} > param ${param.name}`)
306
+ ),
307
+ leave: jest.fn((param, _ctx, parents) =>
308
+ calls.push(`leave operation ${parents.Operation.operationId} > param ${param.name}`)
309
+ ),
310
+ },
311
+ },
312
+ Parameter: {
313
+ enter: jest.fn((param) => calls.push(`enter param ${param.name}`)),
314
+ leave: jest.fn((param) => calls.push(`leave param ${param.name}`)),
315
+ },
316
+ };
317
+ }),
318
+ };
319
+
320
+ const document = parseYamlToDocument(
321
+ outdent`
322
+ openapi: 3.0.0
323
+ info:
324
+ contact: {}
325
+ license: {}
326
+ paths:
327
+ /pet:
328
+ get:
329
+ operationId: get
330
+ parameters:
331
+ - $ref: '#/components/parameters/shared_a'
332
+ - name: get_b
333
+ post:
334
+ operationId: post
335
+ parameters:
336
+ - $ref: '#/components/parameters/shared_a'
337
+ components:
338
+ parameters:
339
+ shared_a:
340
+ name: shared-a
341
+ `,
342
+ ''
343
+ );
344
+
345
+ await lintDocument({
346
+ externalRefResolver: new BaseResolver(),
347
+ document,
348
+ config: makeConfigForRuleset(testRuleSet),
349
+ });
350
+
351
+ expect(calls).toMatchInlineSnapshot(`
352
+ Array [
353
+ "enter operation: get",
354
+ "enter operation get > param shared-a",
355
+ "enter param shared-a",
356
+ "leave param shared-a",
357
+ "leave operation get > param shared-a",
358
+ "enter operation get > param get_b",
359
+ "enter param get_b",
360
+ "leave param get_b",
361
+ "leave operation get > param get_b",
362
+ "leave operation: get",
363
+ "enter operation: post",
364
+ "enter operation post > param shared-a",
365
+ "leave operation post > param shared-a",
366
+ "leave operation: post",
367
+ ]
368
+ `);
369
+ });
370
+
371
+ it('should visit with context same refs with gaps in visitor simple', async () => {
372
+ const calls: string[] = [];
373
+
374
+ const testRuleSet: Oas3RuleSet = {
375
+ test: jest.fn(() => {
376
+ return {
377
+ PathItem: {
378
+ Parameter: {
379
+ enter: jest.fn((param, _ctx, parents) =>
380
+ calls.push(`enter path ${parents.PathItem.id} > param ${param.name}`)
381
+ ),
382
+ },
383
+ },
384
+ };
385
+ }),
386
+ };
387
+
388
+ const document = parseYamlToDocument(
389
+ outdent`
390
+ openapi: 3.0.0
391
+ paths:
392
+ /pet:
393
+ id: pet
394
+ parameters:
395
+ $ref: '#/components/fake_parameters_list'
396
+ get:
397
+ operationId: get
398
+ parameters:
399
+ - $ref: '#/components/parameters/shared_a'
400
+ - name: get_b
401
+ /dog:
402
+ id: dog
403
+ post:
404
+ operationId: post
405
+ parameters:
406
+ - $ref: '#/components/parameters/shared_a'
407
+ components:
408
+ fake_parameters_list:
409
+ - name: path-param
410
+ parameters:
411
+ shared_a:
412
+ name: shared-a
413
+ `,
414
+ ''
415
+ );
416
+
417
+ await lintDocument({
418
+ externalRefResolver: new BaseResolver(),
419
+ document,
420
+ config: makeConfigForRuleset(testRuleSet),
421
+ });
422
+
423
+ expect(calls).toMatchInlineSnapshot(`
424
+ Array [
425
+ "enter path pet > param path-param",
426
+ "enter path pet > param shared-a",
427
+ "enter path pet > param get_b",
428
+ "enter path dog > param shared-a",
429
+ ]
430
+ `);
431
+ });
432
+
433
+ it('should correctly visit more specific visitor', async () => {
434
+ const calls: string[] = [];
435
+
436
+ const testRuleSet: Oas3RuleSet = {
437
+ test: jest.fn(() => {
438
+ return {
439
+ PathItem: {
440
+ Parameter: {
441
+ enter: jest.fn((param, _ctx, parents) =>
442
+ calls.push(`enter path ${parents.PathItem.id} > param ${param.name}`)
443
+ ),
444
+ },
445
+ Operation: {
446
+ Parameter: {
447
+ enter: jest.fn((param, _ctx, parents) =>
448
+ calls.push(
449
+ `enter operation ${parents.Operation.operationId} > param ${param.name}`
450
+ )
451
+ ),
452
+ },
453
+ },
454
+ },
455
+ };
456
+ }),
457
+ };
458
+
459
+ const document = parseYamlToDocument(
460
+ outdent`
461
+ openapi: 3.0.0
462
+ paths:
463
+ /pet:
464
+ id: pet
465
+ parameters:
466
+ - name: path-param
467
+ get:
468
+ operationId: get
469
+ parameters:
470
+ - $ref: '#/components/parameters/shared_a'
471
+ - name: get_b
472
+ - name: get_c
473
+ /dog:
474
+ id: dog
475
+ post:
476
+ operationId: post
477
+ parameters:
478
+ - $ref: '#/components/parameters/shared_b'
479
+ components:
480
+ parameters:
481
+ shared_a:
482
+ name: shared-a
483
+ shared_b:
484
+ name: shared-b
485
+ `,
486
+ ''
487
+ );
488
+
489
+ await lintDocument({
490
+ externalRefResolver: new BaseResolver(),
491
+ document,
492
+ config: makeConfigForRuleset(testRuleSet),
493
+ });
494
+
495
+ expect(calls).toMatchInlineSnapshot(`
496
+ Array [
497
+ "enter path pet > param path-param",
498
+ "enter operation get > param shared-a",
499
+ "enter operation get > param get_b",
500
+ "enter operation get > param get_c",
501
+ "enter operation post > param shared-b",
502
+ ]
503
+ `);
504
+ });
505
+
506
+ it('should visit with context same refs with gaps in visitor and nested rule', async () => {
507
+ const calls: string[] = [];
508
+
509
+ const testRuleSet: Oas3RuleSet = {
510
+ test: jest.fn(() => {
511
+ return {
512
+ PathItem: {
513
+ Parameter: {
514
+ enter: jest.fn((param, _ctx, parents) =>
515
+ calls.push(`enter path ${parents.PathItem.id} > param ${param.name}`)
516
+ ),
517
+ leave: jest.fn((param, _ctx, parents) =>
518
+ calls.push(`leave path ${parents.PathItem.id} > param ${param.name}`)
519
+ ),
520
+ },
521
+ Operation(op, _ctx, parents) {
522
+ calls.push(`enter path ${parents.PathItem.id} > op ${op.operationId}`);
523
+ },
524
+ },
525
+ };
526
+ }),
527
+ };
528
+
529
+ const document = parseYamlToDocument(
530
+ outdent`
531
+ openapi: 3.0.0
532
+ paths:
533
+ /pet:
534
+ id: pet
535
+ parameters:
536
+ - name: path-param
537
+ get:
538
+ operationId: get
539
+ parameters:
540
+ - $ref: '#/components/parameters/shared_a'
541
+ - name: get_b
542
+ /dog:
543
+ id: dog
544
+ post:
545
+ operationId: post
546
+ parameters:
547
+ - $ref: '#/components/parameters/shared_a'
548
+ components:
549
+ parameters:
550
+ shared_a:
551
+ name: shared-a
552
+ `,
553
+ ''
554
+ );
555
+
556
+ await lintDocument({
557
+ externalRefResolver: new BaseResolver(),
558
+ document,
559
+ config: makeConfigForRuleset(testRuleSet),
560
+ });
561
+
562
+ expect(calls).toMatchInlineSnapshot(`
563
+ Array [
564
+ "enter path pet > param path-param",
565
+ "leave path pet > param path-param",
566
+ "enter path pet > op get",
567
+ "enter path pet > param shared-a",
568
+ "leave path pet > param shared-a",
569
+ "enter path pet > param get_b",
570
+ "leave path pet > param get_b",
571
+ "enter path dog > op post",
572
+ "enter path dog > param shared-a",
573
+ "leave path dog > param shared-a",
574
+ ]
575
+ `);
576
+ });
577
+
578
+ it('should visit and do not recurse for circular refs top-level', async () => {
579
+ const calls: string[] = [];
580
+
581
+ const testRuleSet: Oas3RuleSet = {
582
+ test: jest.fn(() => {
583
+ return {
584
+ Schema: jest.fn((schema: any) => calls.push(`enter schema ${schema.id}`)),
585
+ };
586
+ }),
587
+ };
588
+
589
+ const document = parseYamlToDocument(
590
+ outdent`
591
+ openapi: 3.0.0
592
+ paths:
593
+ /pet:
594
+ id: pet
595
+ parameters:
596
+ - name: path-param
597
+ schema:
598
+ $ref: "#/components/parameters/shared_a"
599
+ components:
600
+ parameters:
601
+ shared_a:
602
+ id: 'shared_a'
603
+ allOf:
604
+ - $ref: "#/components/parameters/shared_a"
605
+ - id: 'nested'
606
+ `,
607
+ ''
608
+ );
609
+
610
+ await lintDocument({
611
+ externalRefResolver: new BaseResolver(),
612
+ document,
613
+ config: makeConfigForRuleset(testRuleSet),
614
+ });
615
+
616
+ expect(calls).toMatchInlineSnapshot(`
617
+ Array [
618
+ "enter schema shared_a",
619
+ "enter schema nested",
620
+ ]
621
+ `);
622
+ });
623
+
624
+ it('should visit and do not recurse for circular refs with context', async () => {
625
+ const calls: string[] = [];
626
+
627
+ const testRuleSet: Oas3RuleSet = {
628
+ test: jest.fn(() => {
629
+ return {
630
+ Parameter: {
631
+ Schema: jest.fn((schema: any, _ctx, parents) =>
632
+ calls.push(`enter param ${parents.Parameter.name} > schema ${schema.id}`)
633
+ ),
634
+ },
635
+ };
636
+ }),
637
+ };
638
+
639
+ const document = parseYamlToDocument(
640
+ outdent`
641
+ openapi: 3.0.0
642
+ paths:
643
+ /pet:
644
+ id: pet
645
+ parameters:
646
+ - name: a
647
+ schema:
648
+ $ref: "#/components/parameters/shared_a"
649
+ - name: b
650
+ schema:
651
+ $ref: "#/components/parameters/shared_a"
652
+ components:
653
+ parameters:
654
+ shared_a:
655
+ id: 'shared_a'
656
+ properties:
657
+ a:
658
+ id: a
659
+ allOf:
660
+ - $ref: "#/components/parameters/shared_a"
661
+ - id: 'nested'
662
+ `,
663
+ ''
664
+ );
665
+
666
+ await lintDocument({
667
+ externalRefResolver: new BaseResolver(),
668
+ document,
669
+ config: makeConfigForRuleset(testRuleSet),
670
+ });
671
+
672
+ expect(calls).toMatchInlineSnapshot(`
673
+ Array [
674
+ "enter param a > schema shared_a",
675
+ "enter param b > schema shared_a",
676
+ ]
677
+ `);
678
+ });
679
+
680
+ it('should correctly skip top level', async () => {
681
+ const calls: string[] = [];
682
+
683
+ const testRuleSet: Oas3RuleSet = {
684
+ test: jest.fn(() => {
685
+ return {
686
+ Operation: {
687
+ skip: (op) => op.operationId === 'put',
688
+ enter: jest.fn((op) => calls.push(`enter operation ${op.operationId}`)),
689
+ leave: jest.fn((op) => calls.push(`leave operation ${op.operationId}`)),
690
+ },
691
+ };
692
+ }),
693
+ };
694
+
695
+ const document = parseYamlToDocument(
696
+ outdent`
697
+ openapi: 3.0.0
698
+ paths:
699
+ /pet:
700
+ get:
701
+ operationId: get
702
+ put:
703
+ operationId: put
704
+ `,
705
+ ''
706
+ );
707
+
708
+ await lintDocument({
709
+ externalRefResolver: new BaseResolver(),
710
+ document,
711
+ config: makeConfigForRuleset(testRuleSet),
712
+ });
713
+
714
+ expect(calls).toMatchInlineSnapshot(`
715
+ Array [
716
+ "enter operation get",
717
+ "leave operation get",
718
+ ]
719
+ `);
720
+ });
721
+
722
+ it('should correctly skip nested levels', async () => {
723
+ const calls: string[] = [];
724
+
725
+ const testRuleSet: Oas3RuleSet = {
726
+ test: jest.fn(() => {
727
+ return {
728
+ Operation: {
729
+ skip: (op) => op.operationId === 'put',
730
+ Parameter: jest.fn((param, _ctx, parents) =>
731
+ calls.push(`enter operation ${parents.Operation.operationId} > param ${param.name}`)
732
+ ),
733
+ },
734
+ };
735
+ }),
736
+ };
737
+
738
+ const document = parseYamlToDocument(
739
+ outdent`
740
+ openapi: 3.0.0
741
+ paths:
742
+ /pet:
743
+ get:
744
+ operationId: get
745
+ parameters:
746
+ - $ref: '#/components/parameters/shared_a'
747
+ - name: get_b
748
+ - name: get_c
749
+ put:
750
+ operationId: put
751
+ parameters:
752
+ - $ref: '#/components/parameters/shared_a'
753
+ - name: get_b
754
+ - name: get_c
755
+ components:
756
+ parameters:
757
+ shared_a:
758
+ name: shared-a
759
+ `,
760
+ ''
761
+ );
762
+
763
+ await lintDocument({
764
+ externalRefResolver: new BaseResolver(),
765
+ document,
766
+ config: makeConfigForRuleset(testRuleSet),
767
+ });
768
+
769
+ expect(calls).toMatchInlineSnapshot(`
770
+ Array [
771
+ "enter operation get > param shared-a",
772
+ "enter operation get > param get_b",
773
+ "enter operation get > param get_c",
774
+ ]
775
+ `);
776
+ });
777
+
778
+ it('should correctly visit more specific visitor with skips', async () => {
779
+ const calls: string[] = [];
780
+
781
+ const testRuleSet: Oas3RuleSet = {
782
+ test: jest.fn(() => {
783
+ return {
784
+ PathItem: {
785
+ Parameter: {
786
+ enter: jest.fn((param, _ctx, parents) =>
787
+ calls.push(`enter path ${parents.PathItem.id} > param ${param.name}`)
788
+ ),
789
+ leave: jest.fn((param, _ctx, parents) =>
790
+ calls.push(`leave path ${parents.PathItem.id} > param ${param.name}`)
791
+ ),
792
+ },
793
+ Operation: {
794
+ skip: (op) => op.operationId === 'put',
795
+ Parameter: {
796
+ enter: jest.fn((param, _ctx, parents) =>
797
+ calls.push(
798
+ `enter operation ${parents.Operation.operationId} > param ${param.name}`
799
+ )
800
+ ),
801
+ leave: jest.fn((param, _ctx, parents) =>
802
+ calls.push(
803
+ `leave operation ${parents.Operation.operationId} > param ${param.name}`
804
+ )
805
+ ),
806
+ },
807
+ },
808
+ },
809
+ };
810
+ }),
811
+ };
812
+
813
+ const document = parseYamlToDocument(
814
+ outdent`
815
+ openapi: 3.0.0
816
+ paths:
817
+ /pet:
818
+ id: pet
819
+ parameters:
820
+ - name: path-param
821
+ get:
822
+ operationId: get
823
+ parameters:
824
+ - $ref: '#/components/parameters/shared_a'
825
+ - name: get_b
826
+ - name: get_c
827
+ put:
828
+ operationId: put
829
+ parameters:
830
+ - $ref: '#/components/parameters/shared_a'
831
+ - name: get_b
832
+ - name: get_c
833
+ /dog:
834
+ id: dog
835
+ post:
836
+ operationId: post
837
+ parameters:
838
+ - $ref: '#/components/parameters/shared_b'
839
+ components:
840
+ parameters:
841
+ shared_a:
842
+ name: shared-a
843
+ shared_b:
844
+ name: shared-b
845
+ `,
846
+ ''
847
+ );
848
+
849
+ await lintDocument({
850
+ externalRefResolver: new BaseResolver(),
851
+ document,
852
+ config: makeConfigForRuleset(testRuleSet),
853
+ });
854
+
855
+ expect(calls).toMatchInlineSnapshot(`
856
+ Array [
857
+ "enter path pet > param path-param",
858
+ "leave path pet > param path-param",
859
+ "enter operation get > param shared-a",
860
+ "leave operation get > param shared-a",
861
+ "enter operation get > param get_b",
862
+ "leave operation get > param get_b",
863
+ "enter operation get > param get_c",
864
+ "leave operation get > param get_c",
865
+ "enter operation post > param shared-b",
866
+ "leave operation post > param shared-b",
867
+ ]
868
+ `);
869
+ });
870
+
871
+ it('should correctly visit with nested rules', async () => {
872
+ const calls: string[] = [];
873
+
874
+ const testRuleSet: Oas3RuleSet = {
875
+ test: jest.fn(() => {
876
+ return {
877
+ Schema: {
878
+ Schema: {
879
+ enter: jest.fn((schema: any, _ctx, parents) =>
880
+ calls.push(`enter nested schema ${parents.Schema.id} > ${schema.id}`)
881
+ ),
882
+ leave: jest.fn((schema: any, _ctx, parents) =>
883
+ calls.push(`leave nested schema ${parents.Schema.id} > ${schema.id}`)
884
+ ),
885
+ },
886
+ },
887
+ };
888
+ }),
889
+ };
890
+
891
+ const document = parseYamlToDocument(
892
+ outdent`
893
+ openapi: 3.0.0
894
+ paths:
895
+ /pet:
896
+ get:
897
+ requestBody:
898
+ content:
899
+ application/json:
900
+ schema:
901
+ id: inline-top
902
+ type: object
903
+ properties:
904
+ b:
905
+ $ref: "#/components/schemas/b"
906
+ a:
907
+ type: object
908
+ id: inline-nested-2
909
+ properties:
910
+ a:
911
+ id: inline-nested-nested-2
912
+ components:
913
+ schemas:
914
+ b:
915
+ id: inline-top
916
+ type: object
917
+ properties:
918
+ a:
919
+ type: object
920
+ id: inline-nested
921
+ properties:
922
+ a:
923
+ id: inline-nested-nested
924
+ `,
925
+ 'foobar.yaml'
926
+ );
927
+
928
+ await lintDocument({
929
+ externalRefResolver: new BaseResolver(),
930
+ document,
931
+ config: makeConfigForRuleset(testRuleSet),
932
+ });
933
+
934
+ expect(calls).toMatchInlineSnapshot(`
935
+ Array [
936
+ "enter nested schema inline-top > inline-top",
937
+ "enter nested schema inline-top > inline-nested",
938
+ "enter nested schema inline-nested > inline-nested-nested",
939
+ "leave nested schema inline-nested > inline-nested-nested",
940
+ "leave nested schema inline-top > inline-nested",
941
+ "leave nested schema inline-top > inline-top",
942
+ "enter nested schema inline-top > inline-nested-2",
943
+ "enter nested schema inline-nested-2 > inline-nested-nested-2",
944
+ "leave nested schema inline-nested-2 > inline-nested-nested-2",
945
+ "leave nested schema inline-top > inline-nested-2",
946
+ ]
947
+ `);
948
+ });
949
+
950
+ it('should correctly visit refs', async () => {
951
+ const calls: string[] = [];
952
+
953
+ const testRuleSet: Oas3RuleSet = {
954
+ test: jest.fn(() => {
955
+ return {
956
+ ref(node, _, { node: target }) {
957
+ calls.push(`enter $ref ${node.$ref} with target ${target?.name}`);
958
+ },
959
+ };
960
+ }),
961
+ };
962
+
963
+ const document = parseYamlToDocument(
964
+ outdent`
965
+ openapi: 3.0.0
966
+ paths:
967
+ /pet:
968
+ id: pet
969
+ parameters:
970
+ - name: path-param
971
+ get:
972
+ operationId: get
973
+ parameters:
974
+ - $ref: '#/components/parameters/shared_b'
975
+ put:
976
+ operationId: put
977
+ parameters:
978
+ - $ref: '#/components/parameters/shared_a'
979
+ /dog:
980
+ id: dog
981
+ post:
982
+ operationId: post
983
+ schema:
984
+ example:
985
+ $ref: 123
986
+ parameters:
987
+ - $ref: '#/components/parameters/shared_a'
988
+ components:
989
+ parameters:
990
+ shared_a:
991
+ name: shared-a
992
+ shared_b:
993
+ name: shared-b
994
+ schema:
995
+ $ref: '#/components/parameters/shared_b'
996
+ `,
997
+ 'foobar.yaml'
998
+ );
999
+
1000
+ await lintDocument({
1001
+ externalRefResolver: new BaseResolver(),
1002
+ document,
1003
+ config: makeConfigForRuleset(testRuleSet),
1004
+ });
1005
+
1006
+ expect(calls).toMatchInlineSnapshot(`
1007
+ Array [
1008
+ "enter $ref #/components/parameters/shared_b with target shared-b",
1009
+ "enter $ref #/components/parameters/shared_b with target shared-b",
1010
+ "enter $ref #/components/parameters/shared_a with target shared-a",
1011
+ "enter $ref #/components/parameters/shared_a with target shared-a",
1012
+ ]
1013
+ `);
1014
+ });
1015
+
1016
+ it('should correctly visit refs', async () => {
1017
+ const calls: string[] = [];
1018
+
1019
+ const testRuleSet: Oas3RuleSet = {
1020
+ test: jest.fn(() => {
1021
+ return {
1022
+ NamedSchemas: {
1023
+ Schema(node, { key }) {
1024
+ calls.push(`enter schema ${key}: ${node.type}`);
1025
+ },
1026
+ },
1027
+ };
1028
+ }),
1029
+ };
1030
+
1031
+ const document = parseYamlToDocument(
1032
+ outdent`
1033
+ openapi: 3.0.0
1034
+ components:
1035
+ schemas:
1036
+ a:
1037
+ type: string
1038
+ b:
1039
+ type: number
1040
+ `,
1041
+ 'foobar.yaml'
1042
+ );
1043
+
1044
+ await lintDocument({
1045
+ externalRefResolver: new BaseResolver(),
1046
+ document,
1047
+ config: makeConfigForRuleset(testRuleSet),
1048
+ });
1049
+
1050
+ expect(calls).toMatchInlineSnapshot(`
1051
+ Array [
1052
+ "enter schema a: string",
1053
+ "enter schema b: number",
1054
+ ]
1055
+ `);
1056
+ });
1057
+
1058
+ it('should correctly visit any visitor', async () => {
1059
+ const calls: string[] = [];
1060
+
1061
+ const testRuleSet: Oas3RuleSet = {
1062
+ test: jest.fn(() => {
1063
+ return {
1064
+ ref: {
1065
+ enter(ref: any) {
1066
+ calls.push(`enter ref ${ref.$ref}`);
1067
+ },
1068
+ leave(ref) {
1069
+ calls.push(`leave ref ${ref.$ref}`);
1070
+ },
1071
+ },
1072
+ any: {
1073
+ enter(_node: any, { type }) {
1074
+ calls.push(`enter ${type.name}`);
1075
+ },
1076
+ leave(_node, { type }) {
1077
+ calls.push(`leave ${type.name}`);
1078
+ },
1079
+ },
1080
+ };
1081
+ }),
1082
+ };
1083
+
1084
+ const document = parseYamlToDocument(
1085
+ outdent`
1086
+ openapi: 3.0.0
1087
+ paths:
1088
+ /pet:
1089
+ id: pet
1090
+ parameters:
1091
+ - name: path-param
1092
+ get:
1093
+ operationId: get
1094
+ parameters:
1095
+ - $ref: '#/components/parameters/shared_a'
1096
+ - name: get_b
1097
+ - name: get_c
1098
+ components:
1099
+ parameters:
1100
+ shared_a:
1101
+ name: shared-a
1102
+ schemas:
1103
+ a:
1104
+ type: object
1105
+ `,
1106
+ ''
1107
+ );
1108
+
1109
+ await lintDocument({
1110
+ externalRefResolver: new BaseResolver(),
1111
+ document,
1112
+ config: makeConfigForRuleset(testRuleSet),
1113
+ });
1114
+
1115
+ expect(calls).toMatchInlineSnapshot(`
1116
+ Array [
1117
+ "enter Root",
1118
+ "enter Paths",
1119
+ "enter PathItem",
1120
+ "enter ParameterList",
1121
+ "enter Parameter",
1122
+ "leave Parameter",
1123
+ "leave ParameterList",
1124
+ "enter Operation",
1125
+ "enter ParameterList",
1126
+ "enter ref #/components/parameters/shared_a",
1127
+ "enter Parameter",
1128
+ "leave Parameter",
1129
+ "leave ref #/components/parameters/shared_a",
1130
+ "enter Parameter",
1131
+ "leave Parameter",
1132
+ "enter Parameter",
1133
+ "leave Parameter",
1134
+ "leave ParameterList",
1135
+ "leave Operation",
1136
+ "leave PathItem",
1137
+ "leave Paths",
1138
+ "enter Components",
1139
+ "enter NamedParameters",
1140
+ "leave NamedParameters",
1141
+ "enter NamedSchemas",
1142
+ "enter Schema",
1143
+ "leave Schema",
1144
+ "leave NamedSchemas",
1145
+ "leave Components",
1146
+ "leave Root",
1147
+ ]
1148
+ `);
1149
+ });
1150
+ });
1151
+
1152
+ describe('context.report', () => {
1153
+ it('should report errors correctly', async () => {
1154
+ const testRuleSet: Oas3RuleSet = {
1155
+ test: jest.fn(() => {
1156
+ return {
1157
+ Parameter: {
1158
+ enter: jest.fn((param, ctx) => {
1159
+ if (param.name.indexOf('_') > -1) {
1160
+ ctx.report({
1161
+ message: `Parameter name shouldn't contain '_: ${param.name}`,
1162
+ });
1163
+ }
1164
+ }),
1165
+ },
1166
+ };
1167
+ }),
1168
+ };
1169
+
1170
+ const document = parseYamlToDocument(
1171
+ outdent`
1172
+ openapi: 3.0.0
1173
+ info:
1174
+ contact: {}
1175
+ license: {}
1176
+ paths:
1177
+ /pet:
1178
+ parameters:
1179
+ - name: path-param
1180
+ get:
1181
+ operationId: get
1182
+ parameters:
1183
+ - name: get_a
1184
+ - name: get_b
1185
+ post:
1186
+ operationId: post
1187
+ parameters:
1188
+ - $ref: '#/components/parameters/shared_a'
1189
+ components:
1190
+ parameters:
1191
+ shared_a:
1192
+ name: shared_a
1193
+ `,
1194
+ 'foobar.yaml'
1195
+ );
1196
+
1197
+ const results = await lintDocument({
1198
+ externalRefResolver: new BaseResolver(),
1199
+ document,
1200
+ config: makeConfigForRuleset(testRuleSet),
1201
+ });
1202
+
1203
+ expect(results).toHaveLength(3);
1204
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
1205
+ Array [
1206
+ Object {
1207
+ "location": Array [
1208
+ Object {
1209
+ "pointer": "#/paths/~1pet/get/parameters/0",
1210
+ "reportOnKey": false,
1211
+ "source": "foobar.yaml",
1212
+ },
1213
+ ],
1214
+ "message": "Parameter name shouldn't contain '_: get_a",
1215
+ "ruleId": "test/test",
1216
+ "severity": "error",
1217
+ "suggest": Array [],
1218
+ },
1219
+ Object {
1220
+ "location": Array [
1221
+ Object {
1222
+ "pointer": "#/paths/~1pet/get/parameters/1",
1223
+ "reportOnKey": false,
1224
+ "source": "foobar.yaml",
1225
+ },
1226
+ ],
1227
+ "message": "Parameter name shouldn't contain '_: get_b",
1228
+ "ruleId": "test/test",
1229
+ "severity": "error",
1230
+ "suggest": Array [],
1231
+ },
1232
+ Object {
1233
+ "location": Array [
1234
+ Object {
1235
+ "pointer": "#/components/parameters/shared_a",
1236
+ "reportOnKey": false,
1237
+ "source": "foobar.yaml",
1238
+ },
1239
+ ],
1240
+ "message": "Parameter name shouldn't contain '_: shared_a",
1241
+ "ruleId": "test/test",
1242
+ "severity": "error",
1243
+ "suggest": Array [],
1244
+ },
1245
+ ]
1246
+ `);
1247
+ });
1248
+
1249
+ it('should report errors correctly', async () => {
1250
+ const testRuleSet: Oas3RuleSet = {
1251
+ test: jest.fn(() => {
1252
+ return {
1253
+ Parameter: {
1254
+ enter: jest.fn((param, ctx) => {
1255
+ if (param.name.indexOf('_') > -1) {
1256
+ ctx.report({
1257
+ message: `Parameter name shouldn't contain '_: ${param.name}`,
1258
+ });
1259
+ }
1260
+ }),
1261
+ },
1262
+ };
1263
+ }),
1264
+ };
1265
+
1266
+ const cwd = path.join(__dirname, 'fixtures/refs');
1267
+ const externalRefResolver = new BaseResolver();
1268
+ const document = (await externalRefResolver.resolveDocument(
1269
+ null,
1270
+ `${cwd}/openapi-with-external-refs.yaml`
1271
+ )) as Document;
1272
+
1273
+ if (document === null) {
1274
+ throw 'Should never happen';
1275
+ }
1276
+
1277
+ const results = await lintDocument({
1278
+ externalRefResolver: new BaseResolver(),
1279
+ document,
1280
+ config: makeConfigForRuleset(testRuleSet),
1281
+ });
1282
+
1283
+ expect(results).toHaveLength(4);
1284
+ expect(replaceSourceWithRef(results, cwd)).toMatchInlineSnapshot(`
1285
+ Array [
1286
+ Object {
1287
+ "location": Array [
1288
+ Object {
1289
+ "pointer": "#/components/parameters/path-param",
1290
+ "reportOnKey": false,
1291
+ "source": "openapi-with-external-refs.yaml",
1292
+ },
1293
+ ],
1294
+ "message": "Parameter name shouldn't contain '_: path_param",
1295
+ "ruleId": "test/test",
1296
+ "severity": "error",
1297
+ "suggest": Array [],
1298
+ },
1299
+ Object {
1300
+ "location": Array [
1301
+ Object {
1302
+ "pointer": "#/components/parameters/param-a",
1303
+ "reportOnKey": false,
1304
+ "source": "openapi-with-external-refs.yaml",
1305
+ },
1306
+ ],
1307
+ "message": "Parameter name shouldn't contain '_: param_a",
1308
+ "ruleId": "test/test",
1309
+ "severity": "error",
1310
+ "suggest": Array [],
1311
+ },
1312
+ Object {
1313
+ "location": Array [
1314
+ Object {
1315
+ "pointer": "#/",
1316
+ "reportOnKey": false,
1317
+ "source": "param-c.yaml",
1318
+ },
1319
+ ],
1320
+ "message": "Parameter name shouldn't contain '_: param_c",
1321
+ "ruleId": "test/test",
1322
+ "severity": "error",
1323
+ "suggest": Array [],
1324
+ },
1325
+ Object {
1326
+ "location": Array [
1327
+ Object {
1328
+ "pointer": "#/",
1329
+ "reportOnKey": false,
1330
+ "source": "param-b.yaml",
1331
+ },
1332
+ ],
1333
+ "message": "Parameter name shouldn't contain '_: param_b",
1334
+ "ruleId": "test/test",
1335
+ "severity": "error",
1336
+ "suggest": Array [],
1337
+ },
1338
+ ]
1339
+ `);
1340
+ });
1341
+ });
1342
+
1343
+ describe('context.resolve', () => {
1344
+ it('should resolve refs correctly', async () => {
1345
+ const testRuleSet: Oas3RuleSet = {
1346
+ test: jest.fn(() => {
1347
+ return {
1348
+ Schema: jest.fn((schema, { resolve }) => {
1349
+ if (schema.properties) {
1350
+ expect(schema.properties.a.$ref).toBeDefined();
1351
+ const { location, node } = resolve(schema.properties.a);
1352
+ expect(node).toMatchInlineSnapshot(`
1353
+ Object {
1354
+ "type": "string",
1355
+ }
1356
+ `);
1357
+ expect(location?.pointer).toEqual('#/components/schemas/b');
1358
+ expect(location?.source).toStrictEqual(document.source);
1359
+ }
1360
+ }),
1361
+ };
1362
+ }),
1363
+ };
1364
+
1365
+ const document = parseYamlToDocument(
1366
+ outdent`
1367
+ openapi: 3.0.0
1368
+ info:
1369
+ contact: {}
1370
+ license: {}
1371
+ paths: {}
1372
+ components:
1373
+ schemas:
1374
+ b:
1375
+ type: string
1376
+ a:
1377
+ type: object
1378
+ properties:
1379
+ a:
1380
+ $ref: '#/components/schemas/b'
1381
+ `,
1382
+ 'foobar.yaml'
1383
+ );
1384
+
1385
+ await lintDocument({
1386
+ externalRefResolver: new BaseResolver(),
1387
+ document,
1388
+ config: makeConfigForRuleset(testRuleSet),
1389
+ });
1390
+ });
1391
+ });
1392
+
1393
+ describe('type extensions', () => {
1394
+ each([
1395
+ ['3.0.0', 'oas3_0'],
1396
+ ['3.1.0', 'oas3_1'],
1397
+ ]).it('should correctly visit OpenAPI %s extended types', async (openapi, oas) => {
1398
+ const calls: string[] = [];
1399
+
1400
+ const testRuleSet: Oas3RuleSet = {
1401
+ test: jest.fn(() => {
1402
+ return {
1403
+ any: {
1404
+ enter(_node: any, { type }) {
1405
+ calls.push(`enter ${type.name}`);
1406
+ },
1407
+ leave(_node, { type }) {
1408
+ calls.push(`leave ${type.name}`);
1409
+ },
1410
+ },
1411
+ XWebHooks: {
1412
+ enter(hook: any) {
1413
+ calls.push(`enter hook ${hook.name}`);
1414
+ },
1415
+ leave(hook) {
1416
+ calls.push(`leave hook ${hook.name}`);
1417
+ },
1418
+ },
1419
+ };
1420
+ }),
1421
+ };
1422
+
1423
+ const document = parseYamlToDocument(
1424
+ outdent`
1425
+ openapi: ${openapi}
1426
+ x-webhooks:
1427
+ name: test
1428
+ parameters:
1429
+ - name: a
1430
+ `,
1431
+ 'foobar.yaml'
1432
+ );
1433
+
1434
+ await lintDocument({
1435
+ externalRefResolver: new BaseResolver(),
1436
+ document,
1437
+ config: makeConfigForRuleset(testRuleSet, {
1438
+ typeExtension: {
1439
+ oas3(types, version) {
1440
+ expect(version).toEqual(oas);
1441
+
1442
+ return {
1443
+ ...types,
1444
+ XWebHooks: {
1445
+ properties: {
1446
+ parameters: listOf('Parameter'),
1447
+ },
1448
+ },
1449
+ Root: {
1450
+ ...types.Root,
1451
+ properties: {
1452
+ ...types.Root.properties,
1453
+ 'x-webhooks': 'XWebHooks',
1454
+ },
1455
+ },
1456
+ };
1457
+ },
1458
+ },
1459
+ }),
1460
+ });
1461
+
1462
+ expect(calls).toMatchInlineSnapshot(`
1463
+ Array [
1464
+ "enter Root",
1465
+ "enter XWebHooks",
1466
+ "enter hook test",
1467
+ "enter ParameterList",
1468
+ "enter Parameter",
1469
+ "leave Parameter",
1470
+ "leave ParameterList",
1471
+ "leave hook test",
1472
+ "leave XWebHooks",
1473
+ "leave Root",
1474
+ ]
1475
+ `);
1476
+ });
1477
+ });
1478
+
1479
+ describe('ignoreNextRules', () => {
1480
+ it('should correctly skip top level', async () => {
1481
+ const calls: string[] = [];
1482
+
1483
+ const testRuleSet: Oas3RuleSet = {
1484
+ skip: jest.fn(() => {
1485
+ return {
1486
+ Operation: {
1487
+ enter: jest.fn((op, ctx) => {
1488
+ if (op.operationId === 'get') {
1489
+ ctx.ignoreNextVisitorsOnNode();
1490
+ calls.push(`enter and skip operation ${op.operationId}`);
1491
+ } else {
1492
+ calls.push(`enter and not skip operation ${op.operationId}`);
1493
+ }
1494
+ }),
1495
+ leave: jest.fn((op) => {
1496
+ if (op.operationId === 'get') {
1497
+ calls.push(`leave skipped operation ${op.operationId}`);
1498
+ } else {
1499
+ calls.push(`leave not skipped operation ${op.operationId}`);
1500
+ }
1501
+ }),
1502
+ },
1503
+ };
1504
+ }),
1505
+ test: jest.fn(() => {
1506
+ return {
1507
+ Operation: {
1508
+ enter: jest.fn((op) => calls.push(`enter operation ${op.operationId}`)),
1509
+ leave: jest.fn((op) => calls.push(`leave operation ${op.operationId}`)),
1510
+ },
1511
+ };
1512
+ }),
1513
+ };
1514
+
1515
+ const document = parseYamlToDocument(
1516
+ outdent`
1517
+ openapi: 3.0.0
1518
+ paths:
1519
+ /pet:
1520
+ get:
1521
+ operationId: get
1522
+ put:
1523
+ operationId: put
1524
+ `,
1525
+ ''
1526
+ );
1527
+
1528
+ await lintDocument({
1529
+ externalRefResolver: new BaseResolver(),
1530
+ document,
1531
+ config: makeConfigForRuleset(testRuleSet),
1532
+ });
1533
+
1534
+ expect(calls).toMatchInlineSnapshot(`
1535
+ Array [
1536
+ "enter and skip operation get",
1537
+ "leave skipped operation get",
1538
+ "enter and not skip operation put",
1539
+ "enter operation put",
1540
+ "leave not skipped operation put",
1541
+ "leave operation put",
1542
+ ]
1543
+ `);
1544
+ });
1545
+ });