@graphql-eslint/eslint-plugin 4.0.0-alpha-20220821140530-e968cfc → 4.0.0-alpha-20230810155929-e89edf7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (346) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +14 -252
  3. package/cjs/cache.d.ts +12 -0
  4. package/cjs/cache.js +53 -0
  5. package/cjs/configs/index.d.ts +174 -0
  6. package/cjs/configs/index.js +40 -0
  7. package/cjs/configs/operations-all.d.ts +23 -0
  8. package/cjs/configs/operations-all.js +28 -0
  9. package/cjs/configs/operations-recommended.d.ts +54 -0
  10. package/{configs/operations-recommended.json → cjs/configs/operations-recommended.js} +19 -15
  11. package/cjs/configs/schema-all.d.ts +24 -0
  12. package/cjs/configs/schema-all.js +24 -0
  13. package/cjs/configs/schema-recommended.d.ts +67 -0
  14. package/cjs/configs/schema-recommended.js +68 -0
  15. package/cjs/configs/schema-relay.d.ts +12 -0
  16. package/{configs/relay.json → cjs/configs/schema-relay.js} +6 -4
  17. package/cjs/documents.d.ts +6 -0
  18. package/cjs/documents.js +66 -0
  19. package/cjs/estree-converter/converter.d.ts +8 -0
  20. package/cjs/estree-converter/converter.js +70 -0
  21. package/cjs/estree-converter/index.d.ts +8 -0
  22. package/cjs/estree-converter/index.js +23 -0
  23. package/cjs/estree-converter/types.d.ts +42 -0
  24. package/cjs/estree-converter/types.js +14 -0
  25. package/cjs/estree-converter/utils.d.ts +17 -0
  26. package/cjs/estree-converter/utils.js +105 -0
  27. package/cjs/flat-configs.d.ts +307 -0
  28. package/cjs/flat-configs.js +55 -0
  29. package/cjs/graphql-config.d.ts +14 -0
  30. package/cjs/graphql-config.js +65 -0
  31. package/cjs/index.d.ts +28 -0
  32. package/cjs/index.js +41 -0
  33. package/cjs/parser.d.ts +13 -0
  34. package/cjs/parser.js +96 -0
  35. package/cjs/processor.d.ts +13 -0
  36. package/cjs/processor.js +95 -0
  37. package/cjs/rules/alphabetize.d.ts +74 -0
  38. package/cjs/rules/alphabetize.js +323 -0
  39. package/cjs/rules/description-style.d.ts +29 -0
  40. package/cjs/rules/description-style.js +102 -0
  41. package/cjs/rules/graphql-js-validation.d.ts +13 -0
  42. package/cjs/rules/graphql-js-validation.js +595 -0
  43. package/cjs/rules/index.d.ts +127 -0
  44. package/cjs/rules/index.js +62 -0
  45. package/cjs/rules/input-name.d.ts +44 -0
  46. package/cjs/rules/input-name.js +154 -0
  47. package/cjs/rules/lone-executable-definition.d.ts +35 -0
  48. package/cjs/rules/lone-executable-definition.js +105 -0
  49. package/cjs/rules/match-document-filename.d.ts +81 -0
  50. package/cjs/rules/match-document-filename.js +251 -0
  51. package/cjs/rules/naming-convention.d.ts +108 -0
  52. package/cjs/rules/naming-convention.js +416 -0
  53. package/cjs/rules/no-anonymous-operations.d.ts +13 -0
  54. package/cjs/rules/no-anonymous-operations.js +91 -0
  55. package/cjs/rules/no-deprecated.d.ts +13 -0
  56. package/cjs/rules/no-deprecated.js +143 -0
  57. package/cjs/rules/no-duplicate-fields.d.ts +13 -0
  58. package/cjs/rules/no-duplicate-fields.js +136 -0
  59. package/cjs/rules/no-hashtag-description.d.ts +14 -0
  60. package/cjs/rules/no-hashtag-description.js +127 -0
  61. package/cjs/rules/no-one-place-fragments.d.ts +13 -0
  62. package/cjs/rules/no-one-place-fragments.js +101 -0
  63. package/cjs/rules/no-root-type.d.ts +34 -0
  64. package/cjs/rules/no-root-type.js +103 -0
  65. package/cjs/rules/no-scalar-result-type-on-mutation.d.ts +13 -0
  66. package/cjs/rules/no-scalar-result-type-on-mutation.js +88 -0
  67. package/cjs/rules/no-typename-prefix.d.ts +13 -0
  68. package/cjs/rules/no-typename-prefix.js +91 -0
  69. package/cjs/rules/no-unreachable-types.d.ts +13 -0
  70. package/cjs/rules/no-unreachable-types.js +177 -0
  71. package/cjs/rules/no-unused-fields.d.ts +13 -0
  72. package/cjs/rules/no-unused-fields.js +132 -0
  73. package/cjs/rules/relay-arguments.d.ts +30 -0
  74. package/cjs/rules/relay-arguments.js +126 -0
  75. package/cjs/rules/relay-connection-types.d.ts +14 -0
  76. package/cjs/rules/relay-connection-types.js +111 -0
  77. package/cjs/rules/relay-edge-types.d.ts +40 -0
  78. package/cjs/rules/relay-edge-types.js +161 -0
  79. package/cjs/rules/relay-page-info.d.ts +13 -0
  80. package/cjs/rules/relay-page-info.js +98 -0
  81. package/cjs/rules/require-deprecation-date.d.ts +27 -0
  82. package/cjs/rules/require-deprecation-date.js +148 -0
  83. package/cjs/rules/require-deprecation-reason.d.ts +13 -0
  84. package/cjs/rules/require-deprecation-reason.js +85 -0
  85. package/cjs/rules/require-description.d.ts +24 -0
  86. package/cjs/rules/require-description.js +179 -0
  87. package/cjs/rules/require-field-of-type-query-in-mutation-result.d.ts +13 -0
  88. package/cjs/rules/require-field-of-type-query-in-mutation-result.js +87 -0
  89. package/cjs/rules/require-import-fragment.d.ts +13 -0
  90. package/cjs/rules/require-import-fragment.js +148 -0
  91. package/cjs/rules/require-nullable-fields-with-oneof.d.ts +13 -0
  92. package/cjs/rules/require-nullable-fields-with-oneof.js +81 -0
  93. package/cjs/rules/require-nullable-result-in-root.d.ts +13 -0
  94. package/cjs/rules/require-nullable-result-in-root.js +99 -0
  95. package/cjs/rules/require-selections.d.ts +45 -0
  96. package/cjs/rules/require-selections.js +198 -0
  97. package/cjs/rules/require-type-pattern-with-oneof.d.ts +13 -0
  98. package/cjs/rules/require-type-pattern-with-oneof.js +83 -0
  99. package/cjs/rules/selection-set-depth.d.ts +37 -0
  100. package/cjs/rules/selection-set-depth.js +159 -0
  101. package/cjs/rules/strict-id-in-types.d.ts +66 -0
  102. package/cjs/rules/strict-id-in-types.js +168 -0
  103. package/cjs/rules/unique-enum-value-names.d.ts +13 -0
  104. package/cjs/rules/unique-enum-value-names.js +91 -0
  105. package/cjs/rules/unique-fragment-name.d.ts +14 -0
  106. package/cjs/rules/unique-fragment-name.js +106 -0
  107. package/cjs/rules/unique-operation-name.d.ts +13 -0
  108. package/cjs/rules/unique-operation-name.js +93 -0
  109. package/cjs/schema.d.ts +13 -0
  110. package/cjs/schema.js +52 -0
  111. package/{sibling-operations.d.ts → cjs/siblings.d.ts} +9 -6
  112. package/cjs/siblings.js +96 -0
  113. package/cjs/types.d.ts +75 -0
  114. package/cjs/types.js +14 -0
  115. package/cjs/utils.d.ts +45 -0
  116. package/cjs/utils.js +188 -0
  117. package/esm/cache.d.mts +12 -0
  118. package/esm/cache.js +25 -0
  119. package/esm/chunk-U3TKCM4X.js +8 -0
  120. package/esm/configs/index.d.mts +174 -0
  121. package/esm/configs/index.js +16 -0
  122. package/esm/configs/operations-all.d.mts +23 -0
  123. package/esm/configs/operations-all.js +35 -0
  124. package/esm/configs/operations-recommended.d.mts +54 -0
  125. package/esm/configs/operations-recommended.js +61 -0
  126. package/esm/configs/schema-all.d.mts +24 -0
  127. package/esm/configs/schema-all.js +31 -0
  128. package/esm/configs/schema-recommended.d.mts +67 -0
  129. package/esm/configs/schema-recommended.js +75 -0
  130. package/esm/configs/schema-relay.d.mts +12 -0
  131. package/esm/configs/schema-relay.js +18 -0
  132. package/esm/documents.d.mts +6 -0
  133. package/esm/documents.js +41 -0
  134. package/esm/estree-converter/converter.d.mts +8 -0
  135. package/esm/estree-converter/converter.js +56 -0
  136. package/esm/estree-converter/index.d.mts +8 -0
  137. package/esm/estree-converter/index.js +3 -0
  138. package/esm/estree-converter/types.d.mts +42 -0
  139. package/esm/estree-converter/types.js +0 -0
  140. package/esm/estree-converter/utils.d.mts +17 -0
  141. package/esm/estree-converter/utils.js +87 -0
  142. package/esm/flat-configs.d.mts +307 -0
  143. package/esm/flat-configs.js +36 -0
  144. package/esm/graphql-config.d.mts +14 -0
  145. package/esm/graphql-config.js +39 -0
  146. package/esm/index.d.mts +28 -0
  147. package/esm/index.js +18 -0
  148. package/esm/package.json +1 -0
  149. package/esm/parser.d.mts +13 -0
  150. package/esm/parser.js +76 -0
  151. package/esm/processor.d.mts +13 -0
  152. package/esm/processor.js +81 -0
  153. package/esm/rules/alphabetize.d.mts +74 -0
  154. package/esm/rules/alphabetize.js +299 -0
  155. package/esm/rules/description-style.d.mts +29 -0
  156. package/esm/rules/description-style.js +82 -0
  157. package/esm/rules/graphql-js-validation.d.mts +13 -0
  158. package/esm/rules/graphql-js-validation.js +618 -0
  159. package/esm/rules/index.d.mts +127 -0
  160. package/esm/rules/index.js +76 -0
  161. package/esm/rules/input-name.d.mts +44 -0
  162. package/esm/rules/input-name.js +136 -0
  163. package/esm/rules/lone-executable-definition.d.mts +35 -0
  164. package/esm/rules/lone-executable-definition.js +86 -0
  165. package/esm/rules/match-document-filename.d.mts +81 -0
  166. package/esm/rules/match-document-filename.js +237 -0
  167. package/esm/rules/naming-convention.d.mts +108 -0
  168. package/esm/rules/naming-convention.js +403 -0
  169. package/esm/rules/no-anonymous-operations.d.mts +13 -0
  170. package/esm/rules/no-anonymous-operations.js +72 -0
  171. package/esm/rules/no-deprecated.d.mts +13 -0
  172. package/esm/rules/no-deprecated.js +124 -0
  173. package/esm/rules/no-duplicate-fields.d.mts +13 -0
  174. package/esm/rules/no-duplicate-fields.js +116 -0
  175. package/esm/rules/no-hashtag-description.d.mts +14 -0
  176. package/esm/rules/no-hashtag-description.js +107 -0
  177. package/esm/rules/no-one-place-fragments.d.mts +13 -0
  178. package/esm/rules/no-one-place-fragments.js +83 -0
  179. package/esm/rules/no-root-type.d.mts +34 -0
  180. package/esm/rules/no-root-type.js +83 -0
  181. package/esm/rules/no-scalar-result-type-on-mutation.d.mts +13 -0
  182. package/esm/rules/no-scalar-result-type-on-mutation.js +69 -0
  183. package/esm/rules/no-typename-prefix.d.mts +13 -0
  184. package/esm/rules/no-typename-prefix.js +71 -0
  185. package/esm/rules/no-unreachable-types.d.mts +13 -0
  186. package/esm/rules/no-unreachable-types.js +156 -0
  187. package/esm/rules/no-unused-fields.d.mts +13 -0
  188. package/esm/rules/no-unused-fields.js +113 -0
  189. package/esm/rules/relay-arguments.d.mts +30 -0
  190. package/esm/rules/relay-arguments.js +107 -0
  191. package/esm/rules/relay-connection-types.d.mts +14 -0
  192. package/esm/rules/relay-connection-types.js +90 -0
  193. package/esm/rules/relay-edge-types.d.mts +40 -0
  194. package/esm/rules/relay-edge-types.js +148 -0
  195. package/esm/rules/relay-page-info.d.mts +13 -0
  196. package/esm/rules/relay-page-info.js +80 -0
  197. package/esm/rules/require-deprecation-date.d.mts +27 -0
  198. package/esm/rules/require-deprecation-date.js +129 -0
  199. package/esm/rules/require-deprecation-reason.d.mts +13 -0
  200. package/esm/rules/require-deprecation-reason.js +66 -0
  201. package/esm/rules/require-description.d.mts +24 -0
  202. package/esm/rules/require-description.js +165 -0
  203. package/esm/rules/require-field-of-type-query-in-mutation-result.d.mts +13 -0
  204. package/esm/rules/require-field-of-type-query-in-mutation-result.js +68 -0
  205. package/esm/rules/require-import-fragment.d.mts +13 -0
  206. package/esm/rules/require-import-fragment.js +121 -0
  207. package/esm/rules/require-nullable-fields-with-oneof.d.mts +13 -0
  208. package/esm/rules/require-nullable-fields-with-oneof.js +62 -0
  209. package/esm/rules/require-nullable-result-in-root.d.mts +13 -0
  210. package/esm/rules/require-nullable-result-in-root.js +80 -0
  211. package/esm/rules/require-selections.d.mts +45 -0
  212. package/esm/rules/require-selections.js +194 -0
  213. package/esm/rules/require-type-pattern-with-oneof.d.mts +13 -0
  214. package/esm/rules/require-type-pattern-with-oneof.js +63 -0
  215. package/esm/rules/selection-set-depth.d.mts +37 -0
  216. package/esm/rules/selection-set-depth.js +133 -0
  217. package/esm/rules/strict-id-in-types.d.mts +66 -0
  218. package/esm/rules/strict-id-in-types.js +155 -0
  219. package/esm/rules/unique-enum-value-names.d.mts +13 -0
  220. package/esm/rules/unique-enum-value-names.js +72 -0
  221. package/esm/rules/unique-fragment-name.d.mts +14 -0
  222. package/esm/rules/unique-fragment-name.js +87 -0
  223. package/esm/rules/unique-operation-name.d.mts +13 -0
  224. package/esm/rules/unique-operation-name.js +73 -0
  225. package/esm/schema.d.mts +13 -0
  226. package/esm/schema.js +27 -0
  227. package/esm/siblings.d.mts +24 -0
  228. package/esm/siblings.js +80 -0
  229. package/esm/types.d.mts +75 -0
  230. package/esm/types.js +0 -0
  231. package/esm/utils.d.mts +45 -0
  232. package/esm/utils.js +144 -0
  233. package/index.browser.mjs +5323 -0
  234. package/package.json +43 -35
  235. package/configs/base.json +0 -4
  236. package/configs/operations-all.json +0 -24
  237. package/configs/schema-all.json +0 -26
  238. package/configs/schema-recommended.json +0 -49
  239. package/docs/README.md +0 -75
  240. package/docs/custom-rules.md +0 -148
  241. package/docs/deprecated-rules.md +0 -21
  242. package/docs/parser-options.md +0 -85
  243. package/docs/parser.md +0 -49
  244. package/docs/rules/alphabetize.md +0 -178
  245. package/docs/rules/description-style.md +0 -54
  246. package/docs/rules/executable-definitions.md +0 -17
  247. package/docs/rules/fields-on-correct-type.md +0 -17
  248. package/docs/rules/fragments-on-composite-type.md +0 -17
  249. package/docs/rules/input-name.md +0 -76
  250. package/docs/rules/known-argument-names.md +0 -17
  251. package/docs/rules/known-directives.md +0 -44
  252. package/docs/rules/known-fragment-names.md +0 -69
  253. package/docs/rules/known-type-names.md +0 -17
  254. package/docs/rules/lone-anonymous-operation.md +0 -17
  255. package/docs/rules/lone-schema-definition.md +0 -17
  256. package/docs/rules/match-document-filename.md +0 -156
  257. package/docs/rules/naming-convention.md +0 -300
  258. package/docs/rules/no-anonymous-operations.md +0 -39
  259. package/docs/rules/no-case-insensitive-enum-values-duplicates.md +0 -43
  260. package/docs/rules/no-deprecated.md +0 -85
  261. package/docs/rules/no-duplicate-fields.md +0 -65
  262. package/docs/rules/no-fragment-cycles.md +0 -17
  263. package/docs/rules/no-hashtag-description.md +0 -59
  264. package/docs/rules/no-root-type.md +0 -53
  265. package/docs/rules/no-scalar-result-type-on-mutation.md +0 -37
  266. package/docs/rules/no-typename-prefix.md +0 -39
  267. package/docs/rules/no-undefined-variables.md +0 -17
  268. package/docs/rules/no-unreachable-types.md +0 -49
  269. package/docs/rules/no-unused-fields.md +0 -62
  270. package/docs/rules/no-unused-fragments.md +0 -17
  271. package/docs/rules/no-unused-variables.md +0 -17
  272. package/docs/rules/one-field-subscriptions.md +0 -17
  273. package/docs/rules/overlapping-fields-can-be-merged.md +0 -17
  274. package/docs/rules/possible-fragment-spread.md +0 -17
  275. package/docs/rules/possible-type-extension.md +0 -15
  276. package/docs/rules/provided-required-arguments.md +0 -17
  277. package/docs/rules/relay-arguments.md +0 -57
  278. package/docs/rules/relay-connection-types.md +0 -42
  279. package/docs/rules/relay-edge-types.md +0 -56
  280. package/docs/rules/relay-page-info.md +0 -32
  281. package/docs/rules/require-deprecation-date.md +0 -56
  282. package/docs/rules/require-deprecation-reason.md +0 -47
  283. package/docs/rules/require-description.md +0 -115
  284. package/docs/rules/require-field-of-type-query-in-mutation-result.md +0 -47
  285. package/docs/rules/require-id-when-available.md +0 -88
  286. package/docs/rules/scalar-leafs.md +0 -17
  287. package/docs/rules/selection-set-depth.md +0 -76
  288. package/docs/rules/strict-id-in-types.md +0 -130
  289. package/docs/rules/unique-argument-names.md +0 -17
  290. package/docs/rules/unique-directive-names-per-location.md +0 -17
  291. package/docs/rules/unique-directive-names.md +0 -17
  292. package/docs/rules/unique-enum-value-names.md +0 -15
  293. package/docs/rules/unique-field-definition-names.md +0 -17
  294. package/docs/rules/unique-fragment-name.md +0 -51
  295. package/docs/rules/unique-input-field-names.md +0 -17
  296. package/docs/rules/unique-operation-name.md +0 -55
  297. package/docs/rules/unique-operation-types.md +0 -17
  298. package/docs/rules/unique-type-names.md +0 -17
  299. package/docs/rules/unique-variable-names.md +0 -17
  300. package/docs/rules/value-literals-of-correct-type.md +0 -17
  301. package/docs/rules/variables-are-input-types.md +0 -17
  302. package/docs/rules/variables-in-allowed-position.md +0 -17
  303. package/estree-converter/converter.d.ts +0 -3
  304. package/estree-converter/index.d.ts +0 -3
  305. package/estree-converter/types.d.ts +0 -40
  306. package/estree-converter/utils.d.ts +0 -13
  307. package/graphql-config.d.ts +0 -3
  308. package/index.d.ts +0 -16
  309. package/index.js +0 -4653
  310. package/index.mjs +0 -4641
  311. package/parser.d.ts +0 -2
  312. package/processor.d.ts +0 -7
  313. package/rules/alphabetize.d.ts +0 -16
  314. package/rules/description-style.d.ts +0 -6
  315. package/rules/graphql-js-validation.d.ts +0 -2
  316. package/rules/index.d.ts +0 -41
  317. package/rules/input-name.d.ts +0 -9
  318. package/rules/match-document-filename.d.ts +0 -18
  319. package/rules/naming-convention.d.ts +0 -37
  320. package/rules/no-anonymous-operations.d.ts +0 -3
  321. package/rules/no-case-insensitive-enum-values-duplicates.d.ts +0 -3
  322. package/rules/no-deprecated.d.ts +0 -3
  323. package/rules/no-duplicate-fields.d.ts +0 -3
  324. package/rules/no-hashtag-description.d.ts +0 -3
  325. package/rules/no-root-type.d.ts +0 -7
  326. package/rules/no-scalar-result-type-on-mutation.d.ts +0 -3
  327. package/rules/no-typename-prefix.d.ts +0 -3
  328. package/rules/no-unreachable-types.d.ts +0 -3
  329. package/rules/no-unused-fields.d.ts +0 -3
  330. package/rules/relay-arguments.d.ts +0 -6
  331. package/rules/relay-connection-types.d.ts +0 -5
  332. package/rules/relay-edge-types.d.ts +0 -8
  333. package/rules/relay-page-info.d.ts +0 -3
  334. package/rules/require-deprecation-date.d.ts +0 -5
  335. package/rules/require-deprecation-reason.d.ts +0 -3
  336. package/rules/require-description.d.ts +0 -11
  337. package/rules/require-field-of-type-query-in-mutation-result.d.ts +0 -3
  338. package/rules/require-id-when-available.d.ts +0 -6
  339. package/rules/selection-set-depth.d.ts +0 -7
  340. package/rules/strict-id-in-types.d.ts +0 -11
  341. package/rules/unique-fragment-name.d.ts +0 -6
  342. package/rules/unique-operation-name.d.ts +0 -3
  343. package/schema.d.ts +0 -3
  344. package/testkit.d.ts +0 -27
  345. package/types.d.ts +0 -79
  346. package/utils.d.ts +0 -39
package/index.js DELETED
@@ -1,4653 +0,0 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
6
-
7
- const graphqlTagPluck = require('@graphql-tools/graphql-tag-pluck');
8
- const graphql = require('graphql');
9
- const validate = require('graphql/validation/validate');
10
- const fs = require('fs');
11
- const path = require('path');
12
- const lowerCase = _interopDefault(require('lodash.lowercase'));
13
- const chalk = _interopDefault(require('chalk'));
14
- const utils = require('@graphql-tools/utils');
15
- const valueFromASTUntyped = require('graphql/utilities/valueFromASTUntyped');
16
- const depthLimit = _interopDefault(require('graphql-depth-limit'));
17
- const debugFactory = _interopDefault(require('debug'));
18
- const fastGlob = _interopDefault(require('fast-glob'));
19
- const graphqlConfig = require('graphql-config');
20
- const codeFileLoader = require('@graphql-tools/code-file-loader');
21
- const eslint = require('eslint');
22
- const codeFrame = require('@babel/code-frame');
23
-
24
- const RELEVANT_KEYWORDS = ['gql', 'graphql', '/* GraphQL */'];
25
- const blocksMap = new Map();
26
- const processor = {
27
- supportsAutofix: true,
28
- preprocess(code, filePath) {
29
- if (RELEVANT_KEYWORDS.every(keyword => !code.includes(keyword))) {
30
- return [code];
31
- }
32
- const extractedDocuments = graphqlTagPluck.parseCode({
33
- code,
34
- filePath,
35
- options: {
36
- globalGqlIdentifierName: ['gql', 'graphql'],
37
- skipIndent: true,
38
- },
39
- });
40
- const blocks = extractedDocuments.map(item => ({
41
- filename: 'document.graphql',
42
- text: item.content,
43
- lineOffset: item.loc.start.line - 1,
44
- offset: item.start + 1,
45
- }));
46
- blocksMap.set(filePath, blocks);
47
- return [...blocks, code /* source code must be provided and be last */];
48
- },
49
- postprocess(messages, filePath) {
50
- const blocks = blocksMap.get(filePath) || [];
51
- for (let i = 0; i < blocks.length; i += 1) {
52
- const { lineOffset, offset } = blocks[i];
53
- for (const message of messages[i]) {
54
- message.line += lineOffset;
55
- // endLine can not exist if only `loc: { start, column }` was provided to context.report
56
- if (typeof message.endLine === 'number') {
57
- message.endLine += lineOffset;
58
- }
59
- if (message.fix) {
60
- message.fix.range[0] += offset;
61
- message.fix.range[1] += offset;
62
- }
63
- for (const suggestion of message.suggestions || []) {
64
- suggestion.fix.range[0] += offset;
65
- suggestion.fix.range[1] += offset;
66
- }
67
- }
68
- }
69
- return messages.flat();
70
- },
71
- };
72
-
73
- function requireSiblingsOperations(ruleId, context) {
74
- const { siblingOperations } = context.parserServices;
75
- if (!siblingOperations.available) {
76
- throw new Error(`Rule \`${ruleId}\` requires \`parserOptions.operations\` to be set and loaded. See https://bit.ly/graphql-eslint-operations for more info`);
77
- }
78
- return siblingOperations;
79
- }
80
- function requireGraphQLSchemaFromContext(ruleId, context) {
81
- const { schema } = context.parserServices;
82
- if (!schema) {
83
- throw new Error(`Rule \`${ruleId}\` requires \`parserOptions.schema\` to be set and loaded. See https://bit.ly/graphql-eslint-schema for more info`);
84
- }
85
- else if (schema instanceof Error) {
86
- throw schema;
87
- }
88
- return schema;
89
- }
90
- const logger = {
91
- // eslint-disable-next-line no-console
92
- error: (...args) => console.error(chalk.red('error'), '[graphql-eslint]', chalk(...args)),
93
- // eslint-disable-next-line no-console
94
- warn: (...args) => console.warn(chalk.yellow('warning'), '[graphql-eslint]', chalk(...args)),
95
- };
96
- const normalizePath = (path) => (path || '').replace(/\\/g, '/');
97
- /**
98
- * https://github.com/prettier/eslint-plugin-prettier/blob/76bd45ece6d56eb52f75db6b4a1efdd2efb56392/eslint-plugin-prettier.js#L71
99
- * Given a filepath, get the nearest path that is a regular file.
100
- * The filepath provided by eslint may be a virtual filepath rather than a file
101
- * on disk. This attempts to transform a virtual path into an on-disk path
102
- */
103
- const getOnDiskFilepath = (filepath) => {
104
- try {
105
- if (fs.statSync(filepath).isFile()) {
106
- return filepath;
107
- }
108
- }
109
- catch (err) {
110
- // https://github.com/eslint/eslint/issues/11989
111
- if (err.code === 'ENOTDIR') {
112
- return getOnDiskFilepath(path.dirname(filepath));
113
- }
114
- }
115
- return filepath;
116
- };
117
- const getTypeName = (node) => ('type' in node ? getTypeName(node.type) : node.name.value);
118
- const TYPES_KINDS = [
119
- graphql.Kind.OBJECT_TYPE_DEFINITION,
120
- graphql.Kind.INTERFACE_TYPE_DEFINITION,
121
- graphql.Kind.ENUM_TYPE_DEFINITION,
122
- graphql.Kind.SCALAR_TYPE_DEFINITION,
123
- graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION,
124
- graphql.Kind.UNION_TYPE_DEFINITION,
125
- ];
126
- const pascalCase = (str) => lowerCase(str)
127
- .split(' ')
128
- .map(word => word.charAt(0).toUpperCase() + word.slice(1))
129
- .join('');
130
- const camelCase = (str) => {
131
- const result = pascalCase(str);
132
- return result.charAt(0).toLowerCase() + result.slice(1);
133
- };
134
- const convertCase = (style, str) => {
135
- switch (style) {
136
- case 'camelCase':
137
- return camelCase(str);
138
- case 'PascalCase':
139
- return pascalCase(str);
140
- case 'snake_case':
141
- return lowerCase(str).replace(/ /g, '_');
142
- case 'UPPER_CASE':
143
- return lowerCase(str).replace(/ /g, '_').toUpperCase();
144
- case 'kebab-case':
145
- return lowerCase(str).replace(/ /g, '-');
146
- }
147
- };
148
- function getLocation(start, fieldName = '') {
149
- const { line, column } = start;
150
- return {
151
- start: {
152
- line,
153
- column,
154
- },
155
- end: {
156
- line,
157
- column: column + fieldName.length,
158
- },
159
- };
160
- }
161
- const REPORT_ON_FIRST_CHARACTER = { column: 0, line: 1 };
162
- const ARRAY_DEFAULT_OPTIONS = {
163
- type: 'array',
164
- uniqueItems: true,
165
- minItems: 1,
166
- items: {
167
- type: 'string',
168
- },
169
- };
170
- const englishJoinWords = words => new Intl.ListFormat('en-US', { type: 'disjunction' }).format(words);
171
-
172
- function validateDocument(context, schema = null, documentNode, rule) {
173
- if (documentNode.definitions.length === 0) {
174
- return;
175
- }
176
- try {
177
- const validationErrors = schema
178
- ? graphql.validate(schema, documentNode, [rule])
179
- : validate.validateSDL(documentNode, null, [rule]);
180
- for (const error of validationErrors) {
181
- const { line, column } = error.locations[0];
182
- const sourceCode = context.getSourceCode();
183
- const { tokens } = sourceCode.ast;
184
- const token = tokens.find(token => token.loc.start.line === line && token.loc.start.column === column - 1);
185
- let loc = {
186
- line,
187
- column: column - 1,
188
- };
189
- if (token) {
190
- loc =
191
- // if cursor on `@` symbol than use next node
192
- token.type === '@' ? sourceCode.getNodeByRangeIndex(token.range[1] + 1).loc : token.loc;
193
- }
194
- context.report({
195
- loc,
196
- message: error.message,
197
- });
198
- }
199
- }
200
- catch (e) {
201
- context.report({
202
- loc: REPORT_ON_FIRST_CHARACTER,
203
- message: e.message,
204
- });
205
- }
206
- }
207
- const getFragmentDefsAndFragmentSpreads = (node) => {
208
- const fragmentDefs = new Set();
209
- const fragmentSpreads = new Set();
210
- const visitor = {
211
- FragmentDefinition(node) {
212
- fragmentDefs.add(node.name.value);
213
- },
214
- FragmentSpread(node) {
215
- fragmentSpreads.add(node.name.value);
216
- },
217
- };
218
- graphql.visit(node, visitor);
219
- return { fragmentDefs, fragmentSpreads };
220
- };
221
- const getMissingFragments = (node) => {
222
- const { fragmentDefs, fragmentSpreads } = getFragmentDefsAndFragmentSpreads(node);
223
- return [...fragmentSpreads].filter(name => !fragmentDefs.has(name));
224
- };
225
- const handleMissingFragments = ({ ruleId, context, node }) => {
226
- const missingFragments = getMissingFragments(node);
227
- if (missingFragments.length > 0) {
228
- const siblings = requireSiblingsOperations(ruleId, context);
229
- const fragmentsToAdd = [];
230
- for (const fragmentName of missingFragments) {
231
- const [foundFragment] = siblings.getFragment(fragmentName).map(source => source.document);
232
- if (foundFragment) {
233
- fragmentsToAdd.push(foundFragment);
234
- }
235
- }
236
- if (fragmentsToAdd.length > 0) {
237
- // recall fn to make sure to add fragments inside fragments
238
- return handleMissingFragments({
239
- ruleId,
240
- context,
241
- node: {
242
- kind: graphql.Kind.DOCUMENT,
243
- definitions: [...node.definitions, ...fragmentsToAdd],
244
- },
245
- });
246
- }
247
- }
248
- return node;
249
- };
250
- const validationToRule = (ruleId, ruleName, docs, getDocumentNode, schema = []) => {
251
- let ruleFn = null;
252
- try {
253
- ruleFn = require(`graphql/validation/rules/${ruleName}Rule`)[`${ruleName}Rule`];
254
- }
255
- catch (_a) {
256
- try {
257
- ruleFn = require(`graphql/validation/rules/${ruleName}`)[`${ruleName}Rule`];
258
- }
259
- catch (_b) {
260
- ruleFn = require('graphql/validation')[`${ruleName}Rule`];
261
- }
262
- }
263
- return {
264
- [ruleId]: {
265
- meta: {
266
- docs: {
267
- recommended: true,
268
- ...docs,
269
- graphQLJSRuleName: ruleName,
270
- url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${ruleId}.md`,
271
- description: `${docs.description}\n\n> This rule is a wrapper around a \`graphql-js\` validation function.`,
272
- },
273
- schema,
274
- },
275
- create(context) {
276
- if (!ruleFn) {
277
- logger.warn(`Rule "${ruleId}" depends on a GraphQL validation rule "${ruleName}" but it's not available in the "graphql" version you are using. Skipping…`);
278
- return {};
279
- }
280
- return {
281
- Document(node) {
282
- const schema = docs.requiresSchema ? requireGraphQLSchemaFromContext(ruleId, context) : null;
283
- const documentNode = getDocumentNode
284
- ? getDocumentNode({ ruleId, context, node: node.rawNode() })
285
- : node.rawNode();
286
- validateDocument(context, schema, documentNode, ruleFn);
287
- },
288
- };
289
- },
290
- },
291
- };
292
- };
293
- const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-definitions', 'ExecutableDefinitions', {
294
- category: 'Operations',
295
- description: 'A GraphQL document is only valid for execution if all definitions are either operation or fragment definitions.',
296
- requiresSchema: true,
297
- }), validationToRule('fields-on-correct-type', 'FieldsOnCorrectType', {
298
- category: 'Operations',
299
- description: 'A GraphQL document is only valid if all fields selected are defined by the parent type, or are an allowed meta field such as `__typename`.',
300
- requiresSchema: true,
301
- }), validationToRule('fragments-on-composite-type', 'FragmentsOnCompositeTypes', {
302
- category: 'Operations',
303
- description: 'Fragments use a type condition to determine if they apply, since fragments can only be spread into a composite type (object, interface, or union), the type condition must also be a composite type.',
304
- requiresSchema: true,
305
- }), validationToRule('known-argument-names', 'KnownArgumentNames', {
306
- category: ['Schema', 'Operations'],
307
- description: 'A GraphQL field is only valid if all supplied arguments are defined by that field.',
308
- requiresSchema: true,
309
- }), validationToRule('known-directives', 'KnownDirectives', {
310
- category: ['Schema', 'Operations'],
311
- description: 'A GraphQL document is only valid if all `@directive`s are known by the schema and legally positioned.',
312
- requiresSchema: true,
313
- examples: [
314
- {
315
- title: 'Valid',
316
- usage: [{ ignoreClientDirectives: ['client'] }],
317
- code: /* GraphQL */ `
318
- {
319
- product {
320
- someClientField @client
321
- }
322
- }
323
- `,
324
- },
325
- ],
326
- }, ({ context, node: documentNode }) => {
327
- const { ignoreClientDirectives = [] } = context.options[0] || {};
328
- if (ignoreClientDirectives.length === 0) {
329
- return documentNode;
330
- }
331
- const filterDirectives = (node) => ({
332
- ...node,
333
- directives: node.directives.filter(directive => !ignoreClientDirectives.includes(directive.name.value)),
334
- });
335
- return graphql.visit(documentNode, {
336
- Field: filterDirectives,
337
- OperationDefinition: filterDirectives,
338
- });
339
- }, {
340
- type: 'array',
341
- maxItems: 1,
342
- items: {
343
- type: 'object',
344
- additionalProperties: false,
345
- required: ['ignoreClientDirectives'],
346
- properties: {
347
- ignoreClientDirectives: ARRAY_DEFAULT_OPTIONS,
348
- },
349
- },
350
- }), validationToRule('known-fragment-names', 'KnownFragmentNames', {
351
- category: 'Operations',
352
- description: 'A GraphQL document is only valid if all `...Fragment` fragment spreads refer to fragments defined in the same document.',
353
- requiresSchema: true,
354
- requiresSiblings: true,
355
- examples: [
356
- {
357
- title: 'Incorrect',
358
- code: /* GraphQL */ `
359
- query {
360
- user {
361
- id
362
- ...UserFields # fragment not defined in the document
363
- }
364
- }
365
- `,
366
- },
367
- {
368
- title: 'Correct',
369
- code: /* GraphQL */ `
370
- fragment UserFields on User {
371
- firstName
372
- lastName
373
- }
374
-
375
- query {
376
- user {
377
- id
378
- ...UserFields
379
- }
380
- }
381
- `,
382
- },
383
- {
384
- title: 'Correct (`UserFields` fragment located in a separate file)',
385
- code: /* GraphQL */ `
386
- # user.gql
387
- query {
388
- user {
389
- id
390
- ...UserFields
391
- }
392
- }
393
-
394
- # user-fields.gql
395
- fragment UserFields on User {
396
- id
397
- }
398
- `,
399
- },
400
- ],
401
- }, handleMissingFragments), validationToRule('known-type-names', 'KnownTypeNames', {
402
- category: ['Schema', 'Operations'],
403
- description: 'A GraphQL document is only valid if referenced types (specifically variable definitions and fragment conditions) are defined by the type schema.',
404
- requiresSchema: true,
405
- }), validationToRule('lone-anonymous-operation', 'LoneAnonymousOperation', {
406
- category: 'Operations',
407
- description: 'A GraphQL document is only valid if when it contains an anonymous operation (the query short-hand) that it contains only that one operation definition.',
408
- requiresSchema: true,
409
- }), validationToRule('lone-schema-definition', 'LoneSchemaDefinition', {
410
- category: 'Schema',
411
- description: 'A GraphQL document is only valid if it contains only one schema definition.',
412
- }), validationToRule('no-fragment-cycles', 'NoFragmentCycles', {
413
- category: 'Operations',
414
- description: 'A GraphQL fragment is only valid when it does not have cycles in fragments usage.',
415
- requiresSchema: true,
416
- }), validationToRule('no-undefined-variables', 'NoUndefinedVariables', {
417
- category: 'Operations',
418
- description: 'A GraphQL operation is only valid if all variables encountered, both directly and via fragment spreads, are defined by that operation.',
419
- requiresSchema: true,
420
- requiresSiblings: true,
421
- }, handleMissingFragments), validationToRule('no-unused-fragments', 'NoUnusedFragments', {
422
- category: 'Operations',
423
- description: 'A GraphQL document is only valid if all fragment definitions are spread within operations, or spread within other fragments spread within operations.',
424
- requiresSchema: true,
425
- requiresSiblings: true,
426
- }, ({ ruleId, context, node }) => {
427
- const siblings = requireSiblingsOperations(ruleId, context);
428
- const FilePathToDocumentsMap = [...siblings.getOperations(), ...siblings.getFragments()].reduce((map, { filePath, document }) => {
429
- var _a;
430
- (_a = map[filePath]) !== null && _a !== void 0 ? _a : (map[filePath] = []);
431
- map[filePath].push(document);
432
- return map;
433
- }, Object.create(null));
434
- const getParentNode = (currentFilePath, node) => {
435
- const { fragmentDefs } = getFragmentDefsAndFragmentSpreads(node);
436
- if (fragmentDefs.size === 0) {
437
- return node;
438
- }
439
- // skip iteration over documents for current filepath
440
- delete FilePathToDocumentsMap[currentFilePath];
441
- for (const [filePath, documents] of Object.entries(FilePathToDocumentsMap)) {
442
- const missingFragments = getMissingFragments({
443
- kind: graphql.Kind.DOCUMENT,
444
- definitions: documents,
445
- });
446
- const isCurrentFileImportFragment = missingFragments.some(fragment => fragmentDefs.has(fragment));
447
- if (isCurrentFileImportFragment) {
448
- return getParentNode(filePath, {
449
- kind: graphql.Kind.DOCUMENT,
450
- definitions: [...node.definitions, ...documents],
451
- });
452
- }
453
- }
454
- return node;
455
- };
456
- return getParentNode(context.getFilename(), node);
457
- }), validationToRule('no-unused-variables', 'NoUnusedVariables', {
458
- category: 'Operations',
459
- description: 'A GraphQL operation is only valid if all variables defined by an operation are used, either directly or within a spread fragment.',
460
- requiresSchema: true,
461
- requiresSiblings: true,
462
- }, handleMissingFragments), validationToRule('overlapping-fields-can-be-merged', 'OverlappingFieldsCanBeMerged', {
463
- category: 'Operations',
464
- description: 'A selection set is only valid if all fields (including spreading any fragments) either correspond to distinct response names or can be merged without ambiguity.',
465
- requiresSchema: true,
466
- }), validationToRule('possible-fragment-spread', 'PossibleFragmentSpreads', {
467
- category: 'Operations',
468
- description: 'A fragment spread is only valid if the type condition could ever possibly be true: if there is a non-empty intersection of the possible parent types, and possible types which pass the type condition.',
469
- requiresSchema: true,
470
- }), validationToRule('possible-type-extension', 'PossibleTypeExtensions', {
471
- category: 'Schema',
472
- description: 'A type extension is only valid if the type is defined and has the same kind.',
473
- // TODO: add in graphql-eslint v4
474
- recommended: false,
475
- requiresSchema: true,
476
- isDisabledForAllConfig: true,
477
- }), validationToRule('provided-required-arguments', 'ProvidedRequiredArguments', {
478
- category: ['Schema', 'Operations'],
479
- description: 'A field or directive is only valid if all required (non-null without a default value) field arguments have been provided.',
480
- requiresSchema: true,
481
- }), validationToRule('scalar-leafs', 'ScalarLeafs', {
482
- category: 'Operations',
483
- description: 'A GraphQL document is valid only if all leaf fields (fields without sub selections) are of scalar or enum types.',
484
- requiresSchema: true,
485
- }), validationToRule('one-field-subscriptions', 'SingleFieldSubscriptions', {
486
- category: 'Operations',
487
- description: 'A GraphQL subscription is valid only if it contains a single root field.',
488
- requiresSchema: true,
489
- }), validationToRule('unique-argument-names', 'UniqueArgumentNames', {
490
- category: 'Operations',
491
- description: 'A GraphQL field or directive is only valid if all supplied arguments are uniquely named.',
492
- requiresSchema: true,
493
- }), validationToRule('unique-directive-names', 'UniqueDirectiveNames', {
494
- category: 'Schema',
495
- description: 'A GraphQL document is only valid if all defined directives have unique names.',
496
- }), validationToRule('unique-directive-names-per-location', 'UniqueDirectivesPerLocation', {
497
- category: ['Schema', 'Operations'],
498
- description: 'A GraphQL document is only valid if all non-repeatable directives at a given location are uniquely named.',
499
- requiresSchema: true,
500
- }), validationToRule('unique-enum-value-names', 'UniqueEnumValueNames', {
501
- category: 'Schema',
502
- description: 'A GraphQL enum type is only valid if all its values are uniquely named.',
503
- recommended: false,
504
- isDisabledForAllConfig: true,
505
- }), validationToRule('unique-field-definition-names', 'UniqueFieldDefinitionNames', {
506
- category: 'Schema',
507
- description: 'A GraphQL complex type is only valid if all its fields are uniquely named.',
508
- }), validationToRule('unique-input-field-names', 'UniqueInputFieldNames', {
509
- category: 'Operations',
510
- description: 'A GraphQL input object value is only valid if all supplied fields are uniquely named.',
511
- }), validationToRule('unique-operation-types', 'UniqueOperationTypes', {
512
- category: 'Schema',
513
- description: 'A GraphQL document is only valid if it has only one type per operation.',
514
- }), validationToRule('unique-type-names', 'UniqueTypeNames', {
515
- category: 'Schema',
516
- description: 'A GraphQL document is only valid if all defined types have unique names.',
517
- }), validationToRule('unique-variable-names', 'UniqueVariableNames', {
518
- category: 'Operations',
519
- description: 'A GraphQL operation is only valid if all its variables are uniquely named.',
520
- requiresSchema: true,
521
- }), validationToRule('value-literals-of-correct-type', 'ValuesOfCorrectType', {
522
- category: 'Operations',
523
- description: 'A GraphQL document is only valid if all value literals are of the type expected at their position.',
524
- requiresSchema: true,
525
- }), validationToRule('variables-are-input-types', 'VariablesAreInputTypes', {
526
- category: 'Operations',
527
- description: 'A GraphQL operation is only valid if all the variables it defines are of input types (scalar, enum, or input object).',
528
- requiresSchema: true,
529
- }), validationToRule('variables-in-allowed-position', 'VariablesInAllowedPosition', {
530
- category: 'Operations',
531
- description: 'Variables passed to field arguments conform to type.',
532
- requiresSchema: true,
533
- }));
534
-
535
- const RULE_ID = 'alphabetize';
536
- const fieldsEnum = [
537
- graphql.Kind.OBJECT_TYPE_DEFINITION,
538
- graphql.Kind.INTERFACE_TYPE_DEFINITION,
539
- graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION,
540
- ];
541
- const valuesEnum = [graphql.Kind.ENUM_TYPE_DEFINITION];
542
- const selectionsEnum = [
543
- graphql.Kind.OPERATION_DEFINITION,
544
- graphql.Kind.FRAGMENT_DEFINITION,
545
- ];
546
- const variablesEnum = [graphql.Kind.OPERATION_DEFINITION];
547
- const argumentsEnum = [
548
- graphql.Kind.FIELD_DEFINITION,
549
- graphql.Kind.FIELD,
550
- graphql.Kind.DIRECTIVE_DEFINITION,
551
- graphql.Kind.DIRECTIVE,
552
- ];
553
- const rule = {
554
- meta: {
555
- type: 'suggestion',
556
- fixable: 'code',
557
- docs: {
558
- category: ['Schema', 'Operations'],
559
- description: 'Enforce arrange in alphabetical order for type fields, enum values, input object fields, operation selections and more.',
560
- url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
561
- examples: [
562
- {
563
- title: 'Incorrect',
564
- usage: [{ fields: [graphql.Kind.OBJECT_TYPE_DEFINITION] }],
565
- code: /* GraphQL */ `
566
- type User {
567
- password: String
568
- firstName: String! # should be before "password"
569
- age: Int # should be before "firstName"
570
- lastName: String!
571
- }
572
- `,
573
- },
574
- {
575
- title: 'Correct',
576
- usage: [{ fields: [graphql.Kind.OBJECT_TYPE_DEFINITION] }],
577
- code: /* GraphQL */ `
578
- type User {
579
- age: Int
580
- firstName: String!
581
- lastName: String!
582
- password: String
583
- }
584
- `,
585
- },
586
- {
587
- title: 'Incorrect',
588
- usage: [{ values: [graphql.Kind.ENUM_TYPE_DEFINITION] }],
589
- code: /* GraphQL */ `
590
- enum Role {
591
- SUPER_ADMIN
592
- ADMIN # should be before "SUPER_ADMIN"
593
- USER
594
- GOD # should be before "USER"
595
- }
596
- `,
597
- },
598
- {
599
- title: 'Correct',
600
- usage: [{ values: [graphql.Kind.ENUM_TYPE_DEFINITION] }],
601
- code: /* GraphQL */ `
602
- enum Role {
603
- ADMIN
604
- GOD
605
- SUPER_ADMIN
606
- USER
607
- }
608
- `,
609
- },
610
- {
611
- title: 'Incorrect',
612
- usage: [{ selections: [graphql.Kind.OPERATION_DEFINITION] }],
613
- code: /* GraphQL */ `
614
- query {
615
- me {
616
- firstName
617
- lastName
618
- email # should be before "lastName"
619
- }
620
- }
621
- `,
622
- },
623
- {
624
- title: 'Correct',
625
- usage: [{ selections: [graphql.Kind.OPERATION_DEFINITION] }],
626
- code: /* GraphQL */ `
627
- query {
628
- me {
629
- email
630
- firstName
631
- lastName
632
- }
633
- }
634
- `,
635
- },
636
- ],
637
- configOptions: {
638
- schema: [
639
- {
640
- fields: fieldsEnum,
641
- values: valuesEnum,
642
- arguments: argumentsEnum,
643
- // TODO: add in graphql-eslint v4
644
- // definitions: true,
645
- },
646
- ],
647
- operations: [
648
- {
649
- selections: selectionsEnum,
650
- variables: variablesEnum,
651
- arguments: [graphql.Kind.FIELD, graphql.Kind.DIRECTIVE],
652
- },
653
- ],
654
- },
655
- },
656
- messages: {
657
- [RULE_ID]: '`{{ currName }}` should be before {{ prevName }}.',
658
- },
659
- schema: {
660
- type: 'array',
661
- minItems: 1,
662
- maxItems: 1,
663
- items: {
664
- type: 'object',
665
- additionalProperties: false,
666
- minProperties: 1,
667
- properties: {
668
- fields: {
669
- ...ARRAY_DEFAULT_OPTIONS,
670
- items: {
671
- enum: fieldsEnum,
672
- },
673
- description: 'Fields of `type`, `interface`, and `input`.',
674
- },
675
- values: {
676
- ...ARRAY_DEFAULT_OPTIONS,
677
- items: {
678
- enum: valuesEnum,
679
- },
680
- description: 'Values of `enum`.',
681
- },
682
- selections: {
683
- ...ARRAY_DEFAULT_OPTIONS,
684
- items: {
685
- enum: selectionsEnum,
686
- },
687
- description: 'Selections of `fragment` and operations `query`, `mutation` and `subscription`.',
688
- },
689
- variables: {
690
- ...ARRAY_DEFAULT_OPTIONS,
691
- items: {
692
- enum: variablesEnum,
693
- },
694
- description: 'Variables of operations `query`, `mutation` and `subscription`.',
695
- },
696
- arguments: {
697
- ...ARRAY_DEFAULT_OPTIONS,
698
- items: {
699
- enum: argumentsEnum,
700
- },
701
- description: 'Arguments of fields and directives.',
702
- },
703
- definitions: {
704
- type: 'boolean',
705
- description: 'Definitions – `type`, `interface`, `enum`, `scalar`, `input`, `union` and `directive`.',
706
- default: false,
707
- },
708
- },
709
- },
710
- },
711
- },
712
- create(context) {
713
- var _a, _b, _c, _d, _e;
714
- const sourceCode = context.getSourceCode();
715
- function isNodeAndCommentOnSameLine(node, comment) {
716
- return node.loc.end.line === comment.loc.start.line;
717
- }
718
- function getBeforeComments(node) {
719
- const commentsBefore = sourceCode.getCommentsBefore(node);
720
- if (commentsBefore.length === 0) {
721
- return [];
722
- }
723
- const tokenBefore = sourceCode.getTokenBefore(node);
724
- if (tokenBefore) {
725
- return commentsBefore.filter(comment => !isNodeAndCommentOnSameLine(tokenBefore, comment));
726
- }
727
- const filteredComments = [];
728
- const nodeLine = node.loc.start.line;
729
- // Break on comment that not attached to node
730
- for (let i = commentsBefore.length - 1; i >= 0; i -= 1) {
731
- const comment = commentsBefore[i];
732
- if (nodeLine - comment.loc.start.line - filteredComments.length > 1) {
733
- break;
734
- }
735
- filteredComments.unshift(comment);
736
- }
737
- return filteredComments;
738
- }
739
- function getRangeWithComments(node) {
740
- if (node.kind === graphql.Kind.VARIABLE) {
741
- node = node.parent;
742
- }
743
- const [firstBeforeComment] = getBeforeComments(node);
744
- const [firstAfterComment] = sourceCode.getCommentsAfter(node);
745
- const from = firstBeforeComment || node;
746
- const to = firstAfterComment && isNodeAndCommentOnSameLine(node, firstAfterComment) ? firstAfterComment : node;
747
- return [from.range[0], to.range[1]];
748
- }
749
- function checkNodes(nodes) {
750
- var _a, _b, _c, _d;
751
- // Starts from 1, ignore nodes.length <= 1
752
- for (let i = 1; i < nodes.length; i += 1) {
753
- const currNode = nodes[i];
754
- const currName = ('alias' in currNode && ((_a = currNode.alias) === null || _a === void 0 ? void 0 : _a.value)) || ('name' in currNode && ((_b = currNode.name) === null || _b === void 0 ? void 0 : _b.value));
755
- if (!currName) {
756
- // we don't move unnamed current nodes
757
- continue;
758
- }
759
- const prevNode = nodes[i - 1];
760
- const prevName = ('alias' in prevNode && ((_c = prevNode.alias) === null || _c === void 0 ? void 0 : _c.value)) || ('name' in prevNode && ((_d = prevNode.name) === null || _d === void 0 ? void 0 : _d.value));
761
- if (prevName) {
762
- // Compare with lexicographic order
763
- const compareResult = prevName.localeCompare(currName);
764
- const shouldSort = compareResult === 1;
765
- if (!shouldSort) {
766
- const isSameName = compareResult === 0;
767
- if (!isSameName || !prevNode.kind.endsWith('Extension') || currNode.kind.endsWith('Extension')) {
768
- continue;
769
- }
770
- }
771
- }
772
- context.report({
773
- node: ('alias' in currNode && currNode.alias) || currNode.name,
774
- messageId: RULE_ID,
775
- data: {
776
- currName,
777
- prevName: prevName ? `\`${prevName}\`` : lowerCase(prevNode.kind),
778
- },
779
- *fix(fixer) {
780
- const prevRange = getRangeWithComments(prevNode);
781
- const currRange = getRangeWithComments(currNode);
782
- yield fixer.replaceTextRange(prevRange, sourceCode.getText({ range: currRange }));
783
- yield fixer.replaceTextRange(currRange, sourceCode.getText({ range: prevRange }));
784
- },
785
- });
786
- }
787
- }
788
- const opts = context.options[0];
789
- const fields = new Set((_a = opts.fields) !== null && _a !== void 0 ? _a : []);
790
- const listeners = {};
791
- const kinds = [
792
- fields.has(graphql.Kind.OBJECT_TYPE_DEFINITION) && [graphql.Kind.OBJECT_TYPE_DEFINITION, graphql.Kind.OBJECT_TYPE_EXTENSION],
793
- fields.has(graphql.Kind.INTERFACE_TYPE_DEFINITION) && [graphql.Kind.INTERFACE_TYPE_DEFINITION, graphql.Kind.INTERFACE_TYPE_EXTENSION],
794
- fields.has(graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION) && [
795
- graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION,
796
- graphql.Kind.INPUT_OBJECT_TYPE_EXTENSION,
797
- ],
798
- ]
799
- .filter(Boolean)
800
- .flat();
801
- const fieldsSelector = kinds.join(',');
802
- const hasEnumValues = ((_b = opts.values) === null || _b === void 0 ? void 0 : _b[0]) === graphql.Kind.ENUM_TYPE_DEFINITION;
803
- const selectionsSelector = (_c = opts.selections) === null || _c === void 0 ? void 0 : _c.join(',');
804
- const hasVariables = ((_d = opts.variables) === null || _d === void 0 ? void 0 : _d[0]) === graphql.Kind.OPERATION_DEFINITION;
805
- const argumentsSelector = (_e = opts.arguments) === null || _e === void 0 ? void 0 : _e.join(',');
806
- if (fieldsSelector) {
807
- listeners[fieldsSelector] = (node) => {
808
- checkNodes(node.fields);
809
- };
810
- }
811
- if (hasEnumValues) {
812
- const enumValuesSelector = [graphql.Kind.ENUM_TYPE_DEFINITION, graphql.Kind.ENUM_TYPE_EXTENSION].join(',');
813
- listeners[enumValuesSelector] = (node) => {
814
- checkNodes(node.values);
815
- };
816
- }
817
- if (selectionsSelector) {
818
- listeners[`:matches(${selectionsSelector}) SelectionSet`] = (node) => {
819
- checkNodes(node.selections);
820
- };
821
- }
822
- if (hasVariables) {
823
- listeners.OperationDefinition = (node) => {
824
- checkNodes(node.variableDefinitions.map(varDef => varDef.variable));
825
- };
826
- }
827
- if (argumentsSelector) {
828
- listeners[argumentsSelector] = (node) => {
829
- checkNodes(node.arguments);
830
- };
831
- }
832
- if (opts.definitions) {
833
- listeners.Document = node => {
834
- checkNodes(node.definitions);
835
- };
836
- }
837
- return listeners;
838
- },
839
- };
840
-
841
- const rule$1 = {
842
- meta: {
843
- type: 'suggestion',
844
- hasSuggestions: true,
845
- docs: {
846
- examples: [
847
- {
848
- title: 'Incorrect',
849
- usage: [{ style: 'inline' }],
850
- code: /* GraphQL */ `
851
- """ Description """
852
- type someTypeName {
853
- # ...
854
- }
855
- `,
856
- },
857
- {
858
- title: 'Correct',
859
- usage: [{ style: 'inline' }],
860
- code: /* GraphQL */ `
861
- " Description "
862
- type someTypeName {
863
- # ...
864
- }
865
- `,
866
- },
867
- ],
868
- description: 'Require all comments to follow the same style (either block or inline).',
869
- category: 'Schema',
870
- url: 'https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/description-style.md',
871
- recommended: true,
872
- },
873
- schema: [
874
- {
875
- type: 'object',
876
- additionalProperties: false,
877
- properties: {
878
- style: {
879
- enum: ['block', 'inline'],
880
- default: 'block',
881
- },
882
- },
883
- },
884
- ],
885
- },
886
- create(context) {
887
- const { style = 'block' } = context.options[0] || {};
888
- const isBlock = style === 'block';
889
- return {
890
- [`.description[type=StringValue][block!=${isBlock}]`](node) {
891
- context.report({
892
- loc: isBlock ? node.loc : node.loc.start,
893
- message: `Unexpected ${isBlock ? 'inline' : 'block'} description.`,
894
- suggest: [
895
- {
896
- desc: `Change to ${isBlock ? 'block' : 'inline'} style description`,
897
- fix(fixer) {
898
- const sourceCode = context.getSourceCode();
899
- const originalText = sourceCode.getText(node);
900
- const newText = isBlock
901
- ? originalText.replace(/(^")|("$)/g, '"""')
902
- : originalText.replace(/(^""")|("""$)/g, '"').replace(/\s+/g, ' ');
903
- return fixer.replaceText(node, newText);
904
- },
905
- },
906
- ],
907
- });
908
- },
909
- };
910
- },
911
- };
912
-
913
- const isObjectType = (node) => [graphql.Kind.OBJECT_TYPE_DEFINITION, graphql.Kind.OBJECT_TYPE_EXTENSION].includes(node.type);
914
- const isQueryType = (node) => isObjectType(node) && node.name.value === 'Query';
915
- const isMutationType = (node) => isObjectType(node) && node.name.value === 'Mutation';
916
- const rule$2 = {
917
- meta: {
918
- type: 'suggestion',
919
- hasSuggestions: true,
920
- docs: {
921
- description: 'Require mutation argument to be always called "input" and input type to be called Mutation name + "Input".\nUsing the same name for all input parameters will make your schemas easier to consume and more predictable. Using the same name as mutation for InputType will make it easier to find mutations that InputType belongs to.',
922
- category: 'Schema',
923
- url: 'https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/input-name.md',
924
- examples: [
925
- {
926
- title: 'Incorrect',
927
- usage: [{ checkInputType: true }],
928
- code: /* GraphQL */ `
929
- type Mutation {
930
- SetMessage(message: InputMessage): String
931
- }
932
- `,
933
- },
934
- {
935
- title: 'Correct (with checkInputType)',
936
- usage: [{ checkInputType: true }],
937
- code: /* GraphQL */ `
938
- type Mutation {
939
- SetMessage(input: SetMessageInput): String
940
- }
941
- `,
942
- },
943
- {
944
- title: 'Correct (without checkInputType)',
945
- usage: [{ checkInputType: false }],
946
- code: /* GraphQL */ `
947
- type Mutation {
948
- SetMessage(input: AnyInputTypeName): String
949
- }
950
- `,
951
- },
952
- ],
953
- },
954
- schema: [
955
- {
956
- type: 'object',
957
- additionalProperties: false,
958
- properties: {
959
- checkInputType: {
960
- type: 'boolean',
961
- default: false,
962
- description: 'Check that the input type name follows the convention <mutationName>Input',
963
- },
964
- caseSensitiveInputType: {
965
- type: 'boolean',
966
- default: true,
967
- description: 'Allow for case discrepancies in the input type name',
968
- },
969
- checkQueries: {
970
- type: 'boolean',
971
- default: false,
972
- description: 'Apply the rule to Queries',
973
- },
974
- checkMutations: {
975
- type: 'boolean',
976
- default: true,
977
- description: 'Apply the rule to Mutations',
978
- },
979
- },
980
- },
981
- ],
982
- },
983
- create(context) {
984
- const options = {
985
- checkInputType: false,
986
- caseSensitiveInputType: true,
987
- checkQueries: false,
988
- checkMutations: true,
989
- ...context.options[0],
990
- };
991
- const shouldCheckType = node => (options.checkMutations && isMutationType(node)) || (options.checkQueries && isQueryType(node));
992
- const listeners = {
993
- 'FieldDefinition > InputValueDefinition[name.value!=input] > Name'(node) {
994
- if (shouldCheckType(node.parent.parent.parent)) {
995
- const inputName = node.value;
996
- context.report({
997
- node,
998
- message: `Input \`${inputName}\` should be called \`input\`.`,
999
- suggest: [
1000
- {
1001
- desc: 'Rename to `input`',
1002
- fix: fixer => fixer.replaceText(node, 'input'),
1003
- },
1004
- ],
1005
- });
1006
- }
1007
- },
1008
- };
1009
- if (options.checkInputType) {
1010
- listeners['FieldDefinition > InputValueDefinition NamedType'] = (node) => {
1011
- const findInputType = item => {
1012
- let currentNode = item;
1013
- while (currentNode.type !== graphql.Kind.INPUT_VALUE_DEFINITION) {
1014
- currentNode = currentNode.parent;
1015
- }
1016
- return currentNode;
1017
- };
1018
- const inputValueNode = findInputType(node);
1019
- if (shouldCheckType(inputValueNode.parent.parent)) {
1020
- const mutationName = `${inputValueNode.parent.name.value}Input`;
1021
- const name = node.name.value;
1022
- if ((options.caseSensitiveInputType && node.name.value !== mutationName) ||
1023
- name.toLowerCase() !== mutationName.toLowerCase()) {
1024
- context.report({
1025
- node: node.name,
1026
- message: `Input type \`${name}\` name should be \`${mutationName}\`.`,
1027
- suggest: [
1028
- {
1029
- desc: `Rename to \`${mutationName}\``,
1030
- fix: fixer => fixer.replaceText(node, mutationName),
1031
- },
1032
- ],
1033
- });
1034
- }
1035
- }
1036
- };
1037
- }
1038
- return listeners;
1039
- },
1040
- };
1041
-
1042
- const MATCH_EXTENSION = 'MATCH_EXTENSION';
1043
- const MATCH_STYLE = 'MATCH_STYLE';
1044
- const ACCEPTED_EXTENSIONS = ['.gql', '.graphql'];
1045
- const CASE_STYLES = [
1046
- 'camelCase',
1047
- 'PascalCase',
1048
- 'snake_case',
1049
- 'UPPER_CASE',
1050
- 'kebab-case',
1051
- 'matchDocumentStyle',
1052
- ];
1053
- const schemaOption = {
1054
- oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
1055
- };
1056
- const rule$3 = {
1057
- meta: {
1058
- type: 'suggestion',
1059
- docs: {
1060
- category: 'Operations',
1061
- description: 'This rule allows you to enforce that the file name should match the operation name.',
1062
- url: 'https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/match-document-filename.md',
1063
- examples: [
1064
- {
1065
- title: 'Correct',
1066
- usage: [{ fileExtension: '.gql' }],
1067
- code: /* GraphQL */ `
1068
- # user.gql
1069
- type User {
1070
- id: ID!
1071
- }
1072
- `,
1073
- },
1074
- {
1075
- title: 'Correct',
1076
- usage: [{ query: 'snake_case' }],
1077
- code: /* GraphQL */ `
1078
- # user_by_id.gql
1079
- query UserById {
1080
- userById(id: 5) {
1081
- id
1082
- name
1083
- fullName
1084
- }
1085
- }
1086
- `,
1087
- },
1088
- {
1089
- title: 'Correct',
1090
- usage: [{ fragment: { style: 'kebab-case', suffix: '.fragment' } }],
1091
- code: /* GraphQL */ `
1092
- # user-fields.fragment.gql
1093
- fragment user_fields on User {
1094
- id
1095
- email
1096
- }
1097
- `,
1098
- },
1099
- {
1100
- title: 'Correct',
1101
- usage: [{ mutation: { style: 'PascalCase', suffix: 'Mutation' } }],
1102
- code: /* GraphQL */ `
1103
- # DeleteUserMutation.gql
1104
- mutation DELETE_USER {
1105
- deleteUser(id: 5)
1106
- }
1107
- `,
1108
- },
1109
- {
1110
- title: 'Incorrect',
1111
- usage: [{ fileExtension: '.graphql' }],
1112
- code: /* GraphQL */ `
1113
- # post.gql
1114
- type Post {
1115
- id: ID!
1116
- }
1117
- `,
1118
- },
1119
- {
1120
- title: 'Incorrect',
1121
- usage: [{ query: 'PascalCase' }],
1122
- code: /* GraphQL */ `
1123
- # user-by-id.gql
1124
- query UserById {
1125
- userById(id: 5) {
1126
- id
1127
- name
1128
- fullName
1129
- }
1130
- }
1131
- `,
1132
- },
1133
- {
1134
- title: 'Correct',
1135
- usage: [{ fragment: { style: 'kebab-case', suffix: 'Mutation', prefix: 'mutation.' } }],
1136
- name: 'mutation.add-alert.graphql',
1137
- code: /* GraphQL */ `
1138
- # mutation.add-alert.graphql
1139
- mutation addAlert($input: AddAlertInput!) {
1140
- addAlert(input: $input) {
1141
- ...AlertFields
1142
- }
1143
- }
1144
- `,
1145
- },
1146
- {
1147
- title: 'Correct',
1148
- usage: [{ fragment: { prefix: 'query.' } }],
1149
- name: 'query.me.graphql',
1150
- code: /* GraphQL */ `
1151
- # query.me.graphql
1152
- query me {
1153
- me {
1154
- ...UserFields
1155
- }
1156
- }
1157
- `,
1158
- },
1159
- ],
1160
- configOptions: [
1161
- {
1162
- query: 'kebab-case',
1163
- mutation: 'kebab-case',
1164
- subscription: 'kebab-case',
1165
- fragment: 'kebab-case',
1166
- },
1167
- ],
1168
- },
1169
- messages: {
1170
- [MATCH_EXTENSION]: 'File extension "{{ fileExtension }}" don\'t match extension "{{ expectedFileExtension }}"',
1171
- [MATCH_STYLE]: 'Unexpected filename "{{ filename }}". Rename it to "{{ expectedFilename }}"',
1172
- },
1173
- schema: {
1174
- definitions: {
1175
- asString: {
1176
- enum: CASE_STYLES,
1177
- description: `One of: ${CASE_STYLES.map(t => `\`${t}\``).join(', ')}`,
1178
- },
1179
- asObject: {
1180
- type: 'object',
1181
- additionalProperties: false,
1182
- minProperties: 1,
1183
- properties: {
1184
- style: { enum: CASE_STYLES },
1185
- suffix: { type: 'string' },
1186
- prefix: { type: 'string' },
1187
- },
1188
- },
1189
- },
1190
- type: 'array',
1191
- minItems: 1,
1192
- maxItems: 1,
1193
- items: {
1194
- type: 'object',
1195
- additionalProperties: false,
1196
- minProperties: 1,
1197
- properties: {
1198
- fileExtension: { enum: ACCEPTED_EXTENSIONS },
1199
- query: schemaOption,
1200
- mutation: schemaOption,
1201
- subscription: schemaOption,
1202
- fragment: schemaOption,
1203
- },
1204
- },
1205
- },
1206
- },
1207
- create(context) {
1208
- const options = context.options[0] || {
1209
- fileExtension: null,
1210
- };
1211
- const filePath = context.getFilename();
1212
- const isVirtualFile = !fs.existsSync(filePath);
1213
- if (process.env.NODE_ENV !== 'test' && isVirtualFile) {
1214
- // Skip validation for code files
1215
- return {};
1216
- }
1217
- const fileExtension = path.extname(filePath);
1218
- const filename = path.basename(filePath, fileExtension);
1219
- return {
1220
- Document(documentNode) {
1221
- var _a;
1222
- if (options.fileExtension && options.fileExtension !== fileExtension) {
1223
- context.report({
1224
- loc: REPORT_ON_FIRST_CHARACTER,
1225
- messageId: MATCH_EXTENSION,
1226
- data: {
1227
- fileExtension,
1228
- expectedFileExtension: options.fileExtension,
1229
- },
1230
- });
1231
- }
1232
- const firstOperation = documentNode.definitions.find(n => n.kind === graphql.Kind.OPERATION_DEFINITION);
1233
- const firstFragment = documentNode.definitions.find(n => n.kind === graphql.Kind.FRAGMENT_DEFINITION);
1234
- const node = firstOperation || firstFragment;
1235
- if (!node) {
1236
- return;
1237
- }
1238
- const docName = (_a = node.name) === null || _a === void 0 ? void 0 : _a.value;
1239
- if (!docName) {
1240
- return;
1241
- }
1242
- const docType = 'operation' in node ? node.operation : 'fragment';
1243
- let option = options[docType];
1244
- if (!option) {
1245
- // Config not provided
1246
- return;
1247
- }
1248
- if (typeof option === 'string') {
1249
- option = { style: option };
1250
- }
1251
- const expectedExtension = options.fileExtension || fileExtension;
1252
- let expectedFilename = option.prefix || '';
1253
- if (option.style) {
1254
- expectedFilename += option.style === 'matchDocumentStyle' ? docName : convertCase(option.style, docName);
1255
- }
1256
- else {
1257
- expectedFilename += filename;
1258
- }
1259
- expectedFilename += (option.suffix || '') + expectedExtension;
1260
- const filenameWithExtension = filename + expectedExtension;
1261
- if (expectedFilename !== filenameWithExtension) {
1262
- context.report({
1263
- loc: REPORT_ON_FIRST_CHARACTER,
1264
- messageId: MATCH_STYLE,
1265
- data: {
1266
- expectedFilename,
1267
- filename: filenameWithExtension,
1268
- },
1269
- });
1270
- }
1271
- },
1272
- };
1273
- },
1274
- };
1275
-
1276
- const KindToDisplayName = {
1277
- // types
1278
- [graphql.Kind.OBJECT_TYPE_DEFINITION]: 'Type',
1279
- [graphql.Kind.INTERFACE_TYPE_DEFINITION]: 'Interface',
1280
- [graphql.Kind.ENUM_TYPE_DEFINITION]: 'Enumerator',
1281
- [graphql.Kind.SCALAR_TYPE_DEFINITION]: 'Scalar',
1282
- [graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION]: 'Input type',
1283
- [graphql.Kind.UNION_TYPE_DEFINITION]: 'Union',
1284
- // fields
1285
- [graphql.Kind.FIELD_DEFINITION]: 'Field',
1286
- [graphql.Kind.INPUT_VALUE_DEFINITION]: 'Input property',
1287
- [graphql.Kind.ARGUMENT]: 'Argument',
1288
- [graphql.Kind.DIRECTIVE_DEFINITION]: 'Directive',
1289
- // rest
1290
- [graphql.Kind.ENUM_VALUE_DEFINITION]: 'Enumeration value',
1291
- [graphql.Kind.OPERATION_DEFINITION]: 'Operation',
1292
- [graphql.Kind.FRAGMENT_DEFINITION]: 'Fragment',
1293
- [graphql.Kind.VARIABLE_DEFINITION]: 'Variable',
1294
- };
1295
- const StyleToRegex = {
1296
- camelCase: /^[a-z][\dA-Za-z]*$/,
1297
- PascalCase: /^[A-Z][\dA-Za-z]*$/,
1298
- snake_case: /^[a-z][\d_a-z]*[\da-z]*$/,
1299
- UPPER_CASE: /^[A-Z][\dA-Z_]*[\dA-Z]*$/,
1300
- };
1301
- const ALLOWED_KINDS = Object.keys(KindToDisplayName).sort();
1302
- const ALLOWED_STYLES = Object.keys(StyleToRegex);
1303
- const schemaOption$1 = {
1304
- oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
1305
- };
1306
- const rule$4 = {
1307
- meta: {
1308
- type: 'suggestion',
1309
- docs: {
1310
- description: 'Require names to follow specified conventions.',
1311
- category: ['Schema', 'Operations'],
1312
- recommended: true,
1313
- url: 'https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/naming-convention.md',
1314
- examples: [
1315
- {
1316
- title: 'Incorrect',
1317
- usage: [{ types: 'PascalCase', FieldDefinition: 'camelCase' }],
1318
- code: /* GraphQL */ `
1319
- type user {
1320
- first_name: String!
1321
- }
1322
- `,
1323
- },
1324
- {
1325
- title: 'Incorrect',
1326
- usage: [{ FragmentDefinition: { style: 'PascalCase', forbiddenSuffixes: ['Fragment'] } }],
1327
- code: /* GraphQL */ `
1328
- fragment UserFragment on User {
1329
- # ...
1330
- }
1331
- `,
1332
- },
1333
- {
1334
- title: 'Incorrect',
1335
- usage: [{ 'FieldDefinition[parent.name.value=Query]': { forbiddenPrefixes: ['get'] } }],
1336
- code: /* GraphQL */ `
1337
- type Query {
1338
- getUsers: [User!]!
1339
- }
1340
- `,
1341
- },
1342
- {
1343
- title: 'Correct',
1344
- usage: [{ types: 'PascalCase', FieldDefinition: 'camelCase' }],
1345
- code: /* GraphQL */ `
1346
- type User {
1347
- firstName: String
1348
- }
1349
- `,
1350
- },
1351
- {
1352
- title: 'Correct',
1353
- usage: [{ FragmentDefinition: { style: 'PascalCase', forbiddenSuffixes: ['Fragment'] } }],
1354
- code: /* GraphQL */ `
1355
- fragment UserFields on User {
1356
- # ...
1357
- }
1358
- `,
1359
- },
1360
- {
1361
- title: 'Correct',
1362
- usage: [{ 'FieldDefinition[parent.name.value=Query]': { forbiddenPrefixes: ['get'] } }],
1363
- code: /* GraphQL */ `
1364
- type Query {
1365
- users: [User!]!
1366
- }
1367
- `,
1368
- },
1369
- {
1370
- title: 'Correct',
1371
- usage: [{ FieldDefinition: { style: 'camelCase', ignorePattern: '^(EAN13|UPC|UK)' } }],
1372
- code: /* GraphQL */ `
1373
- type Product {
1374
- EAN13: String
1375
- UPC: String
1376
- UKFlag: String
1377
- }
1378
- `,
1379
- },
1380
- ],
1381
- configOptions: {
1382
- schema: [
1383
- {
1384
- types: 'PascalCase',
1385
- FieldDefinition: 'camelCase',
1386
- InputValueDefinition: 'camelCase',
1387
- Argument: 'camelCase',
1388
- DirectiveDefinition: 'camelCase',
1389
- EnumValueDefinition: 'UPPER_CASE',
1390
- 'FieldDefinition[parent.name.value=Query]': {
1391
- forbiddenPrefixes: ['query', 'get'],
1392
- forbiddenSuffixes: ['Query'],
1393
- },
1394
- 'FieldDefinition[parent.name.value=Mutation]': {
1395
- forbiddenPrefixes: ['mutation'],
1396
- forbiddenSuffixes: ['Mutation'],
1397
- },
1398
- 'FieldDefinition[parent.name.value=Subscription]': {
1399
- forbiddenPrefixes: ['subscription'],
1400
- forbiddenSuffixes: ['Subscription'],
1401
- },
1402
- },
1403
- ],
1404
- operations: [
1405
- {
1406
- VariableDefinition: 'camelCase',
1407
- OperationDefinition: {
1408
- style: 'PascalCase',
1409
- forbiddenPrefixes: ['Query', 'Mutation', 'Subscription', 'Get'],
1410
- forbiddenSuffixes: ['Query', 'Mutation', 'Subscription'],
1411
- },
1412
- FragmentDefinition: {
1413
- style: 'PascalCase',
1414
- forbiddenPrefixes: ['Fragment'],
1415
- forbiddenSuffixes: ['Fragment'],
1416
- },
1417
- },
1418
- ],
1419
- },
1420
- },
1421
- hasSuggestions: true,
1422
- schema: {
1423
- definitions: {
1424
- asString: {
1425
- enum: ALLOWED_STYLES,
1426
- description: `One of: ${ALLOWED_STYLES.map(t => `\`${t}\``).join(', ')}`,
1427
- },
1428
- asObject: {
1429
- type: 'object',
1430
- additionalProperties: false,
1431
- properties: {
1432
- style: { enum: ALLOWED_STYLES },
1433
- prefix: { type: 'string' },
1434
- suffix: { type: 'string' },
1435
- forbiddenPrefixes: ARRAY_DEFAULT_OPTIONS,
1436
- forbiddenSuffixes: ARRAY_DEFAULT_OPTIONS,
1437
- ignorePattern: {
1438
- type: 'string',
1439
- description: 'Option to skip validation of some words, e.g. acronyms',
1440
- },
1441
- },
1442
- },
1443
- },
1444
- type: 'array',
1445
- maxItems: 1,
1446
- items: {
1447
- type: 'object',
1448
- additionalProperties: false,
1449
- properties: {
1450
- types: {
1451
- ...schemaOption$1,
1452
- description: `Includes:\n\n${TYPES_KINDS.map(kind => `- \`${kind}\``).join('\n')}`,
1453
- },
1454
- ...Object.fromEntries(ALLOWED_KINDS.map(kind => [
1455
- kind,
1456
- {
1457
- ...schemaOption$1,
1458
- description: `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`,
1459
- },
1460
- ])),
1461
- allowLeadingUnderscore: {
1462
- type: 'boolean',
1463
- default: false,
1464
- },
1465
- allowTrailingUnderscore: {
1466
- type: 'boolean',
1467
- default: false,
1468
- },
1469
- },
1470
- patternProperties: {
1471
- [`^(${ALLOWED_KINDS.join('|')})(.+)?$`]: schemaOption$1,
1472
- },
1473
- description: [
1474
- "> It's possible to use a [`selector`](https://eslint.org/docs/developer-guide/selectors) that starts with allowed `ASTNode` names which are described below.",
1475
- '>',
1476
- '> Paste or drop code into the editor in [ASTExplorer](https://astexplorer.net) and inspect the generated AST to compose your selector.',
1477
- '>',
1478
- '> Example: pattern property `FieldDefinition[parent.name.value=Query]` will match only fields for type `Query`.',
1479
- ].join('\n'),
1480
- },
1481
- },
1482
- },
1483
- create(context) {
1484
- const options = context.options[0] || {};
1485
- const { allowLeadingUnderscore, allowTrailingUnderscore, types, ...restOptions } = options;
1486
- function normalisePropertyOption(kind) {
1487
- const style = restOptions[kind] || types;
1488
- return typeof style === 'object' ? style : { style };
1489
- }
1490
- function report(node, message, suggestedName) {
1491
- context.report({
1492
- node,
1493
- message,
1494
- suggest: [
1495
- {
1496
- desc: `Rename to \`${suggestedName}\``,
1497
- fix: fixer => fixer.replaceText(node, suggestedName),
1498
- },
1499
- ],
1500
- });
1501
- }
1502
- const checkNode = (selector) => (n) => {
1503
- const { name: node } = n.kind === graphql.Kind.VARIABLE_DEFINITION ? n.variable : n;
1504
- if (!node) {
1505
- return;
1506
- }
1507
- const { prefix, suffix, forbiddenPrefixes, forbiddenSuffixes, style, ignorePattern } = normalisePropertyOption(selector);
1508
- const nodeType = KindToDisplayName[n.kind] || n.kind;
1509
- const nodeName = node.value;
1510
- const error = getError();
1511
- if (error) {
1512
- const { errorMessage, renameToName } = error;
1513
- const [leadingUnderscores] = nodeName.match(/^_*/);
1514
- const [trailingUnderscores] = nodeName.match(/_*$/);
1515
- const suggestedName = leadingUnderscores + renameToName + trailingUnderscores;
1516
- report(node, `${nodeType} "${nodeName}" should ${errorMessage}`, suggestedName);
1517
- }
1518
- function getError() {
1519
- const name = nodeName.replace(/(^_+)|(_+$)/g, '');
1520
- if (ignorePattern && new RegExp(ignorePattern, 'u').test(name)) {
1521
- return;
1522
- }
1523
- if (prefix && !name.startsWith(prefix)) {
1524
- return {
1525
- errorMessage: `have "${prefix}" prefix`,
1526
- renameToName: prefix + name,
1527
- };
1528
- }
1529
- if (suffix && !name.endsWith(suffix)) {
1530
- return {
1531
- errorMessage: `have "${suffix}" suffix`,
1532
- renameToName: name + suffix,
1533
- };
1534
- }
1535
- const forbiddenPrefix = forbiddenPrefixes === null || forbiddenPrefixes === void 0 ? void 0 : forbiddenPrefixes.find(prefix => name.startsWith(prefix));
1536
- if (forbiddenPrefix) {
1537
- return {
1538
- errorMessage: `not have "${forbiddenPrefix}" prefix`,
1539
- renameToName: name.replace(new RegExp(`^${forbiddenPrefix}`), ''),
1540
- };
1541
- }
1542
- const forbiddenSuffix = forbiddenSuffixes === null || forbiddenSuffixes === void 0 ? void 0 : forbiddenSuffixes.find(suffix => name.endsWith(suffix));
1543
- if (forbiddenSuffix) {
1544
- return {
1545
- errorMessage: `not have "${forbiddenSuffix}" suffix`,
1546
- renameToName: name.replace(new RegExp(`${forbiddenSuffix}$`), ''),
1547
- };
1548
- }
1549
- // Style is optional
1550
- if (!style) {
1551
- return;
1552
- }
1553
- const caseRegex = StyleToRegex[style];
1554
- if (!caseRegex.test(name)) {
1555
- return {
1556
- errorMessage: `be in ${style} format`,
1557
- renameToName: convertCase(style, name),
1558
- };
1559
- }
1560
- }
1561
- };
1562
- const checkUnderscore = (isLeading) => (node) => {
1563
- const suggestedName = node.value.replace(isLeading ? /^_+/ : /_+$/, '');
1564
- report(node, `${isLeading ? 'Leading' : 'Trailing'} underscores are not allowed`, suggestedName);
1565
- };
1566
- const listeners = {};
1567
- if (!allowLeadingUnderscore) {
1568
- listeners['Name[value=/^_/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] =
1569
- checkUnderscore(true);
1570
- }
1571
- if (!allowTrailingUnderscore) {
1572
- listeners['Name[value=/_$/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] =
1573
- checkUnderscore(false);
1574
- }
1575
- const selectors = new Set([types && TYPES_KINDS, Object.keys(restOptions)].flat().filter(Boolean));
1576
- for (const selector of selectors) {
1577
- listeners[selector] = checkNode(selector);
1578
- }
1579
- return listeners;
1580
- },
1581
- };
1582
-
1583
- const RULE_ID$1 = 'no-anonymous-operations';
1584
- const rule$5 = {
1585
- meta: {
1586
- type: 'suggestion',
1587
- hasSuggestions: true,
1588
- docs: {
1589
- category: 'Operations',
1590
- description: 'Require name for your GraphQL operations. This is useful since most GraphQL client libraries are using the operation name for caching purposes.',
1591
- recommended: true,
1592
- url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID$1}.md`,
1593
- examples: [
1594
- {
1595
- title: 'Incorrect',
1596
- code: /* GraphQL */ `
1597
- query {
1598
- # ...
1599
- }
1600
- `,
1601
- },
1602
- {
1603
- title: 'Correct',
1604
- code: /* GraphQL */ `
1605
- query user {
1606
- # ...
1607
- }
1608
- `,
1609
- },
1610
- ],
1611
- },
1612
- messages: {
1613
- [RULE_ID$1]: 'Anonymous GraphQL operations are forbidden. Make sure to name your {{ operation }}!',
1614
- },
1615
- schema: [],
1616
- },
1617
- create(context) {
1618
- return {
1619
- 'OperationDefinition[name=undefined]'(node) {
1620
- const [firstSelection] = node.selectionSet.selections;
1621
- const suggestedName = firstSelection.kind === graphql.Kind.FIELD ? (firstSelection.alias || firstSelection.name).value : node.operation;
1622
- context.report({
1623
- loc: getLocation(node.loc.start, node.operation),
1624
- messageId: RULE_ID$1,
1625
- data: {
1626
- operation: node.operation,
1627
- },
1628
- suggest: [
1629
- {
1630
- desc: `Rename to \`${suggestedName}\``,
1631
- fix(fixer) {
1632
- const sourceCode = context.getSourceCode();
1633
- const hasQueryKeyword = sourceCode.getText({ range: [node.range[0], node.range[0] + 1] }) !== '{';
1634
- return fixer.insertTextAfterRange([node.range[0], node.range[0] + (hasQueryKeyword ? node.operation.length : 0)], `${hasQueryKeyword ? '' : 'query'} ${suggestedName}${hasQueryKeyword ? '' : ' '}`);
1635
- },
1636
- },
1637
- ],
1638
- });
1639
- },
1640
- };
1641
- },
1642
- };
1643
-
1644
- const rule$6 = {
1645
- meta: {
1646
- type: 'suggestion',
1647
- hasSuggestions: true,
1648
- docs: {
1649
- url: 'https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/no-case-insensitive-enum-values-duplicates.md',
1650
- category: 'Schema',
1651
- recommended: true,
1652
- description: 'Disallow case-insensitive enum values duplicates.',
1653
- examples: [
1654
- {
1655
- title: 'Incorrect',
1656
- code: /* GraphQL */ `
1657
- enum MyEnum {
1658
- Value
1659
- VALUE
1660
- ValuE
1661
- }
1662
- `,
1663
- },
1664
- {
1665
- title: 'Correct',
1666
- code: /* GraphQL */ `
1667
- enum MyEnum {
1668
- Value1
1669
- Value2
1670
- Value3
1671
- }
1672
- `,
1673
- },
1674
- ],
1675
- },
1676
- schema: [],
1677
- },
1678
- create(context) {
1679
- const selector = [graphql.Kind.ENUM_TYPE_DEFINITION, graphql.Kind.ENUM_TYPE_EXTENSION].join(',');
1680
- return {
1681
- [selector](node) {
1682
- const duplicates = node.values.filter((item, index, array) => array.findIndex(v => v.name.value.toLowerCase() === item.name.value.toLowerCase()) !== index);
1683
- for (const duplicate of duplicates) {
1684
- const enumName = duplicate.name.value;
1685
- context.report({
1686
- node: duplicate.name,
1687
- message: `Case-insensitive enum values duplicates are not allowed! Found: \`${enumName}\`.`,
1688
- suggest: [
1689
- {
1690
- desc: `Remove \`${enumName}\` enum value`,
1691
- fix: fixer => fixer.remove(duplicate),
1692
- },
1693
- ],
1694
- });
1695
- }
1696
- },
1697
- };
1698
- },
1699
- };
1700
-
1701
- const RULE_ID$2 = 'no-deprecated';
1702
- const rule$7 = {
1703
- meta: {
1704
- type: 'suggestion',
1705
- hasSuggestions: true,
1706
- docs: {
1707
- category: 'Operations',
1708
- description: 'Enforce that deprecated fields or enum values are not in use by operations.',
1709
- url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID$2}.md`,
1710
- requiresSchema: true,
1711
- examples: [
1712
- {
1713
- title: 'Incorrect (field)',
1714
- code: /* GraphQL */ `
1715
- # In your schema
1716
- type User {
1717
- id: ID!
1718
- name: String! @deprecated(reason: "old field, please use fullName instead")
1719
- fullName: String!
1720
- }
1721
-
1722
- # Query
1723
- query user {
1724
- user {
1725
- name # This is deprecated, so you'll get an error
1726
- }
1727
- }
1728
- `,
1729
- },
1730
- {
1731
- title: 'Incorrect (enum value)',
1732
- code: /* GraphQL */ `
1733
- # In your schema
1734
- type Mutation {
1735
- changeSomething(type: SomeType): Boolean!
1736
- }
1737
-
1738
- enum SomeType {
1739
- NEW
1740
- OLD @deprecated(reason: "old field, please use NEW instead")
1741
- }
1742
-
1743
- # Mutation
1744
- mutation {
1745
- changeSomething(
1746
- type: OLD # This is deprecated, so you'll get an error
1747
- ) {
1748
- ...
1749
- }
1750
- }
1751
- `,
1752
- },
1753
- {
1754
- title: 'Correct',
1755
- code: /* GraphQL */ `
1756
- # In your schema
1757
- type User {
1758
- id: ID!
1759
- name: String! @deprecated(reason: "old field, please use fullName instead")
1760
- fullName: String!
1761
- }
1762
-
1763
- # Query
1764
- query user {
1765
- user {
1766
- id
1767
- fullName
1768
- }
1769
- }
1770
- `,
1771
- },
1772
- ],
1773
- recommended: true,
1774
- },
1775
- messages: {
1776
- [RULE_ID$2]: 'This {{ type }} is marked as deprecated in your GraphQL schema (reason: {{ reason }})',
1777
- },
1778
- schema: [],
1779
- },
1780
- create(context) {
1781
- requireGraphQLSchemaFromContext(RULE_ID$2, context);
1782
- function report(node, reason) {
1783
- const nodeName = node.kind === graphql.Kind.ENUM ? node.value : node.name.value;
1784
- const nodeType = node.kind === graphql.Kind.ENUM ? 'enum value' : 'field';
1785
- context.report({
1786
- node,
1787
- messageId: RULE_ID$2,
1788
- data: {
1789
- type: nodeType,
1790
- reason,
1791
- },
1792
- suggest: [
1793
- {
1794
- desc: `Remove \`${nodeName}\` ${nodeType}`,
1795
- fix: fixer => fixer.remove(node),
1796
- },
1797
- ],
1798
- });
1799
- }
1800
- return {
1801
- EnumValue(node) {
1802
- var _a;
1803
- const typeInfo = node.typeInfo();
1804
- const reason = (_a = typeInfo.enumValue) === null || _a === void 0 ? void 0 : _a.deprecationReason;
1805
- if (reason) {
1806
- report(node, reason);
1807
- }
1808
- },
1809
- Field(node) {
1810
- var _a;
1811
- const typeInfo = node.typeInfo();
1812
- const reason = (_a = typeInfo.fieldDef) === null || _a === void 0 ? void 0 : _a.deprecationReason;
1813
- if (reason) {
1814
- report(node, reason);
1815
- }
1816
- },
1817
- };
1818
- },
1819
- };
1820
-
1821
- const RULE_ID$3 = 'no-duplicate-fields';
1822
- const rule$8 = {
1823
- meta: {
1824
- type: 'suggestion',
1825
- hasSuggestions: true,
1826
- docs: {
1827
- description: 'Checks for duplicate fields in selection set, variables in operation definition, or in arguments set of a field.',
1828
- category: 'Operations',
1829
- url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID$3}.md`,
1830
- recommended: true,
1831
- examples: [
1832
- {
1833
- title: 'Incorrect',
1834
- code: /* GraphQL */ `
1835
- query {
1836
- user {
1837
- name
1838
- email
1839
- name # duplicate field
1840
- }
1841
- }
1842
- `,
1843
- },
1844
- {
1845
- title: 'Incorrect',
1846
- code: /* GraphQL */ `
1847
- query {
1848
- users(
1849
- first: 100
1850
- skip: 50
1851
- after: "cji629tngfgou0b73kt7vi5jo"
1852
- first: 100 # duplicate argument
1853
- ) {
1854
- id
1855
- }
1856
- }
1857
- `,
1858
- },
1859
- {
1860
- title: 'Incorrect',
1861
- code: /* GraphQL */ `
1862
- query (
1863
- $first: Int!
1864
- $first: Int! # duplicate variable
1865
- ) {
1866
- users(first: $first, skip: 50) {
1867
- id
1868
- }
1869
- }
1870
- `,
1871
- },
1872
- ],
1873
- },
1874
- messages: {
1875
- [RULE_ID$3]: '{{ type }} `{{ fieldName }}` defined multiple times.',
1876
- },
1877
- schema: [],
1878
- },
1879
- create(context) {
1880
- function checkNode(usedFields, node) {
1881
- const fieldName = node.value;
1882
- if (usedFields.has(fieldName)) {
1883
- const { parent } = node;
1884
- context.report({
1885
- node,
1886
- messageId: RULE_ID$3,
1887
- data: {
1888
- type: parent.type,
1889
- fieldName,
1890
- },
1891
- suggest: [
1892
- {
1893
- desc: `Remove \`${fieldName}\` ${parent.type.toLowerCase()}`,
1894
- fix(fixer) {
1895
- return fixer.remove((parent.type === graphql.Kind.VARIABLE ? parent.parent : parent));
1896
- },
1897
- },
1898
- ],
1899
- });
1900
- }
1901
- else {
1902
- usedFields.add(fieldName);
1903
- }
1904
- }
1905
- return {
1906
- OperationDefinition(node) {
1907
- const set = new Set();
1908
- for (const varDef of node.variableDefinitions) {
1909
- checkNode(set, varDef.variable.name);
1910
- }
1911
- },
1912
- Field(node) {
1913
- const set = new Set();
1914
- for (const arg of node.arguments) {
1915
- checkNode(set, arg.name);
1916
- }
1917
- },
1918
- SelectionSet(node) {
1919
- const set = new Set();
1920
- for (const selection of node.selections) {
1921
- if (selection.kind === graphql.Kind.FIELD) {
1922
- checkNode(set, selection.alias || selection.name);
1923
- }
1924
- }
1925
- },
1926
- };
1927
- },
1928
- };
1929
-
1930
- const HASHTAG_COMMENT = 'HASHTAG_COMMENT';
1931
- const rule$9 = {
1932
- meta: {
1933
- type: 'suggestion',
1934
- hasSuggestions: true,
1935
- schema: [],
1936
- messages: {
1937
- [HASHTAG_COMMENT]: 'Using hashtag `#` for adding GraphQL descriptions is not allowed. Prefer using `"""` for multiline, or `"` for a single line description.',
1938
- },
1939
- docs: {
1940
- description: 'Requires to use `"""` or `"` for adding a GraphQL description instead of `#`.\nAllows to use hashtag for comments, as long as it\'s not attached to an AST definition.',
1941
- category: 'Schema',
1942
- url: 'https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/no-hashtag-description.md',
1943
- examples: [
1944
- {
1945
- title: 'Incorrect',
1946
- code: /* GraphQL */ `
1947
- # Represents a user
1948
- type User {
1949
- id: ID!
1950
- name: String
1951
- }
1952
- `,
1953
- },
1954
- {
1955
- title: 'Correct',
1956
- code: /* GraphQL */ `
1957
- " Represents a user "
1958
- type User {
1959
- id: ID!
1960
- name: String
1961
- }
1962
- `,
1963
- },
1964
- {
1965
- title: 'Correct',
1966
- code: /* GraphQL */ `
1967
- # This file defines the basic User type.
1968
- # This comment is valid because it's not attached specifically to an AST object.
1969
-
1970
- " Represents a user "
1971
- type User {
1972
- id: ID! # This one is also valid, since it comes after the AST object
1973
- name: String
1974
- }
1975
- `,
1976
- },
1977
- ],
1978
- recommended: true,
1979
- },
1980
- },
1981
- create(context) {
1982
- const selector = 'Document[definitions.0.kind!=/^(OperationDefinition|FragmentDefinition)$/]';
1983
- return {
1984
- [selector](node) {
1985
- const rawNode = node.rawNode();
1986
- let token = rawNode.loc.startToken;
1987
- while (token) {
1988
- const { kind, prev, next, value, line, column } = token;
1989
- if (kind === graphql.TokenKind.COMMENT && prev && next) {
1990
- const isEslintComment = value.trimStart().startsWith('eslint');
1991
- const linesAfter = next.line - line;
1992
- if (!isEslintComment && line !== prev.line && next.kind === graphql.TokenKind.NAME && linesAfter < 2) {
1993
- context.report({
1994
- messageId: HASHTAG_COMMENT,
1995
- loc: {
1996
- line,
1997
- column: column - 1,
1998
- },
1999
- suggest: ['"""', '"'].map(descriptionSyntax => ({
2000
- desc: `Replace with \`${descriptionSyntax}\` description syntax`,
2001
- fix: fixer => fixer.replaceTextRange([token.start, token.end], [descriptionSyntax, value.trim(), descriptionSyntax].join('')),
2002
- })),
2003
- });
2004
- }
2005
- }
2006
- token = next;
2007
- }
2008
- },
2009
- };
2010
- },
2011
- };
2012
-
2013
- const ROOT_TYPES = ['mutation', 'subscription'];
2014
- const rule$a = {
2015
- meta: {
2016
- type: 'suggestion',
2017
- hasSuggestions: true,
2018
- docs: {
2019
- category: 'Schema',
2020
- description: 'Disallow using root types `mutation` and/or `subscription`.',
2021
- url: 'https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/no-root-type.md',
2022
- requiresSchema: true,
2023
- isDisabledForAllConfig: true,
2024
- examples: [
2025
- {
2026
- title: 'Incorrect',
2027
- usage: [{ disallow: ['mutation', 'subscription'] }],
2028
- code: /* GraphQL */ `
2029
- type Mutation {
2030
- createUser(input: CreateUserInput!): User!
2031
- }
2032
- `,
2033
- },
2034
- {
2035
- title: 'Correct',
2036
- usage: [{ disallow: ['mutation', 'subscription'] }],
2037
- code: /* GraphQL */ `
2038
- type Query {
2039
- users: [User!]!
2040
- }
2041
- `,
2042
- },
2043
- ],
2044
- },
2045
- schema: {
2046
- type: 'array',
2047
- minItems: 1,
2048
- maxItems: 1,
2049
- items: {
2050
- type: 'object',
2051
- additionalProperties: false,
2052
- required: ['disallow'],
2053
- properties: {
2054
- disallow: {
2055
- ...ARRAY_DEFAULT_OPTIONS,
2056
- items: {
2057
- enum: ROOT_TYPES,
2058
- },
2059
- },
2060
- },
2061
- },
2062
- },
2063
- },
2064
- create(context) {
2065
- const schema = requireGraphQLSchemaFromContext('no-root-type', context);
2066
- const disallow = new Set(context.options[0].disallow);
2067
- const rootTypeNames = [
2068
- disallow.has('mutation') && schema.getMutationType(),
2069
- disallow.has('subscription') && schema.getSubscriptionType(),
2070
- ]
2071
- .filter(Boolean)
2072
- .map(type => type.name)
2073
- .join('|');
2074
- if (!rootTypeNames) {
2075
- return {};
2076
- }
2077
- const selector = `:matches(ObjectTypeDefinition, ObjectTypeExtension) > .name[value=/^(${rootTypeNames})$/]`;
2078
- return {
2079
- [selector](node) {
2080
- const typeName = node.value;
2081
- context.report({
2082
- node,
2083
- message: `Root type \`${typeName}\` is forbidden.`,
2084
- suggest: [
2085
- {
2086
- desc: `Remove \`${typeName}\` type`,
2087
- fix: fixer => fixer.remove(node.parent),
2088
- },
2089
- ],
2090
- });
2091
- },
2092
- };
2093
- },
2094
- };
2095
-
2096
- const RULE_ID$4 = 'no-scalar-result-type-on-mutation';
2097
- const rule$b = {
2098
- meta: {
2099
- type: 'suggestion',
2100
- hasSuggestions: true,
2101
- docs: {
2102
- category: 'Schema',
2103
- description: 'Avoid scalar result type on mutation type to make sure to return a valid state.',
2104
- url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID$4}.md`,
2105
- requiresSchema: true,
2106
- examples: [
2107
- {
2108
- title: 'Incorrect',
2109
- code: /* GraphQL */ `
2110
- type Mutation {
2111
- createUser: Boolean
2112
- }
2113
- `,
2114
- },
2115
- {
2116
- title: 'Correct',
2117
- code: /* GraphQL */ `
2118
- type Mutation {
2119
- createUser: User!
2120
- }
2121
- `,
2122
- },
2123
- ],
2124
- },
2125
- schema: [],
2126
- },
2127
- create(context) {
2128
- const schema = requireGraphQLSchemaFromContext(RULE_ID$4, context);
2129
- const mutationType = schema.getMutationType();
2130
- if (!mutationType) {
2131
- return {};
2132
- }
2133
- const selector = [
2134
- `:matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=${mutationType.name}]`,
2135
- '> FieldDefinition > .gqlType Name',
2136
- ].join(' ');
2137
- return {
2138
- [selector](node) {
2139
- const typeName = node.value;
2140
- const graphQLType = schema.getType(typeName);
2141
- if (graphql.isScalarType(graphQLType)) {
2142
- context.report({
2143
- node,
2144
- message: `Unexpected scalar result type \`${typeName}\`.`,
2145
- suggest: [
2146
- {
2147
- desc: `Remove \`${typeName}\``,
2148
- fix: fixer => fixer.remove(node),
2149
- },
2150
- ],
2151
- });
2152
- }
2153
- },
2154
- };
2155
- },
2156
- };
2157
-
2158
- const NO_TYPENAME_PREFIX = 'NO_TYPENAME_PREFIX';
2159
- const rule$c = {
2160
- meta: {
2161
- type: 'suggestion',
2162
- hasSuggestions: true,
2163
- docs: {
2164
- category: 'Schema',
2165
- description: 'Enforces users to avoid using the type name in a field name while defining your schema.',
2166
- recommended: true,
2167
- url: 'https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/no-typename-prefix.md',
2168
- examples: [
2169
- {
2170
- title: 'Incorrect',
2171
- code: /* GraphQL */ `
2172
- type User {
2173
- userId: ID!
2174
- }
2175
- `,
2176
- },
2177
- {
2178
- title: 'Correct',
2179
- code: /* GraphQL */ `
2180
- type User {
2181
- id: ID!
2182
- }
2183
- `,
2184
- },
2185
- ],
2186
- },
2187
- messages: {
2188
- [NO_TYPENAME_PREFIX]: 'Field "{{ fieldName }}" starts with the name of the parent type "{{ typeName }}"',
2189
- },
2190
- schema: [],
2191
- },
2192
- create(context) {
2193
- return {
2194
- 'ObjectTypeDefinition, ObjectTypeExtension, InterfaceTypeDefinition, InterfaceTypeExtension'(node) {
2195
- const typeName = node.name.value;
2196
- const lowerTypeName = typeName.toLowerCase();
2197
- for (const field of node.fields) {
2198
- const fieldName = field.name.value;
2199
- if (fieldName.toLowerCase().startsWith(lowerTypeName)) {
2200
- context.report({
2201
- data: {
2202
- fieldName,
2203
- typeName,
2204
- },
2205
- messageId: NO_TYPENAME_PREFIX,
2206
- node: field.name,
2207
- suggest: [
2208
- {
2209
- desc: `Remove \`${fieldName.slice(0, typeName.length)}\` prefix`,
2210
- fix: fixer => fixer.replaceText(field.name, fieldName.replace(new RegExp(`^${typeName}`, 'i'), '')),
2211
- },
2212
- ],
2213
- });
2214
- }
2215
- }
2216
- },
2217
- };
2218
- },
2219
- };
2220
-
2221
- const RULE_ID$5 = 'no-unreachable-types';
2222
- const KINDS = [
2223
- graphql.Kind.DIRECTIVE_DEFINITION,
2224
- graphql.Kind.OBJECT_TYPE_DEFINITION,
2225
- graphql.Kind.OBJECT_TYPE_EXTENSION,
2226
- graphql.Kind.INTERFACE_TYPE_DEFINITION,
2227
- graphql.Kind.INTERFACE_TYPE_EXTENSION,
2228
- graphql.Kind.SCALAR_TYPE_DEFINITION,
2229
- graphql.Kind.SCALAR_TYPE_EXTENSION,
2230
- graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION,
2231
- graphql.Kind.INPUT_OBJECT_TYPE_EXTENSION,
2232
- graphql.Kind.UNION_TYPE_DEFINITION,
2233
- graphql.Kind.UNION_TYPE_EXTENSION,
2234
- graphql.Kind.ENUM_TYPE_DEFINITION,
2235
- graphql.Kind.ENUM_TYPE_EXTENSION,
2236
- ];
2237
- let reachableTypesCache;
2238
- const RequestDirectiveLocations = new Set([
2239
- graphql.DirectiveLocation.QUERY,
2240
- graphql.DirectiveLocation.MUTATION,
2241
- graphql.DirectiveLocation.SUBSCRIPTION,
2242
- graphql.DirectiveLocation.FIELD,
2243
- graphql.DirectiveLocation.FRAGMENT_DEFINITION,
2244
- graphql.DirectiveLocation.FRAGMENT_SPREAD,
2245
- graphql.DirectiveLocation.INLINE_FRAGMENT,
2246
- graphql.DirectiveLocation.VARIABLE_DEFINITION,
2247
- ]);
2248
- function getReachableTypes(schema) {
2249
- // We don't want cache reachableTypes on test environment
2250
- // Otherwise reachableTypes will be same for all tests
2251
- if (process.env.NODE_ENV !== 'test' && reachableTypesCache) {
2252
- return reachableTypesCache;
2253
- }
2254
- const reachableTypes = new Set();
2255
- const collect = (node) => {
2256
- const typeName = getTypeName(node);
2257
- if (reachableTypes.has(typeName)) {
2258
- return;
2259
- }
2260
- reachableTypes.add(typeName);
2261
- const type = schema.getType(typeName) || schema.getDirective(typeName);
2262
- if (graphql.isInterfaceType(type)) {
2263
- const { objects, interfaces } = schema.getImplementations(type);
2264
- for (const { astNode } of [...objects, ...interfaces]) {
2265
- graphql.visit(astNode, visitor);
2266
- }
2267
- }
2268
- else if (type.astNode) {
2269
- // astNode can be undefined for ID, String, Boolean
2270
- graphql.visit(type.astNode, visitor);
2271
- }
2272
- };
2273
- const visitor = {
2274
- InterfaceTypeDefinition: collect,
2275
- ObjectTypeDefinition: collect,
2276
- InputValueDefinition: collect,
2277
- UnionTypeDefinition: collect,
2278
- FieldDefinition: collect,
2279
- Directive: collect,
2280
- NamedType: collect,
2281
- };
2282
- for (const type of [
2283
- schema,
2284
- schema.getQueryType(),
2285
- schema.getMutationType(),
2286
- schema.getSubscriptionType(),
2287
- ]) {
2288
- // if schema don't have Query type, schema.astNode will be undefined
2289
- if (type === null || type === void 0 ? void 0 : type.astNode) {
2290
- graphql.visit(type.astNode, visitor);
2291
- }
2292
- }
2293
- for (const node of schema.getDirectives()) {
2294
- if (node.locations.some(location => RequestDirectiveLocations.has(location))) {
2295
- reachableTypes.add(node.name);
2296
- }
2297
- }
2298
- reachableTypesCache = reachableTypes;
2299
- return reachableTypesCache;
2300
- }
2301
- const rule$d = {
2302
- meta: {
2303
- messages: {
2304
- [RULE_ID$5]: '{{ type }} `{{ typeName }}` is unreachable.',
2305
- },
2306
- docs: {
2307
- description: 'Requires all types to be reachable at some level by root level fields.',
2308
- category: 'Schema',
2309
- url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID$5}.md`,
2310
- requiresSchema: true,
2311
- examples: [
2312
- {
2313
- title: 'Incorrect',
2314
- code: /* GraphQL */ `
2315
- type User {
2316
- id: ID!
2317
- name: String
2318
- }
2319
-
2320
- type Query {
2321
- me: String
2322
- }
2323
- `,
2324
- },
2325
- {
2326
- title: 'Correct',
2327
- code: /* GraphQL */ `
2328
- type User {
2329
- id: ID!
2330
- name: String
2331
- }
2332
-
2333
- type Query {
2334
- me: User
2335
- }
2336
- `,
2337
- },
2338
- ],
2339
- recommended: true,
2340
- },
2341
- type: 'suggestion',
2342
- schema: [],
2343
- hasSuggestions: true,
2344
- },
2345
- create(context) {
2346
- const schema = requireGraphQLSchemaFromContext(RULE_ID$5, context);
2347
- const reachableTypes = getReachableTypes(schema);
2348
- return {
2349
- [`:matches(${KINDS}) > .name`](node) {
2350
- const typeName = node.value;
2351
- if (!reachableTypes.has(typeName)) {
2352
- const type = lowerCase(node.parent.kind.replace(/(Extension|Definition)$/, ''));
2353
- context.report({
2354
- node,
2355
- messageId: RULE_ID$5,
2356
- data: {
2357
- type: type[0].toUpperCase() + type.slice(1),
2358
- typeName,
2359
- },
2360
- suggest: [
2361
- {
2362
- desc: `Remove \`${typeName}\``,
2363
- fix: fixer => fixer.remove(node.parent),
2364
- },
2365
- ],
2366
- });
2367
- }
2368
- },
2369
- };
2370
- },
2371
- };
2372
-
2373
- const RULE_ID$6 = 'no-unused-fields';
2374
- let usedFieldsCache;
2375
- function getUsedFields(schema, operations) {
2376
- // We don't want cache usedFields on test environment
2377
- // Otherwise usedFields will be same for all tests
2378
- if (process.env.NODE_ENV !== 'test' && usedFieldsCache) {
2379
- return usedFieldsCache;
2380
- }
2381
- const usedFields = Object.create(null);
2382
- const typeInfo = new graphql.TypeInfo(schema);
2383
- const visitor = graphql.visitWithTypeInfo(typeInfo, {
2384
- Field(node) {
2385
- var _a;
2386
- const fieldDef = typeInfo.getFieldDef();
2387
- if (!fieldDef) {
2388
- // skip visiting this node if field is not defined in schema
2389
- return false;
2390
- }
2391
- const parentTypeName = typeInfo.getParentType().name;
2392
- const fieldName = node.name.value;
2393
- (_a = usedFields[parentTypeName]) !== null && _a !== void 0 ? _a : (usedFields[parentTypeName] = new Set());
2394
- usedFields[parentTypeName].add(fieldName);
2395
- },
2396
- });
2397
- const allDocuments = [...operations.getOperations(), ...operations.getFragments()];
2398
- for (const { document } of allDocuments) {
2399
- graphql.visit(document, visitor);
2400
- }
2401
- usedFieldsCache = usedFields;
2402
- return usedFieldsCache;
2403
- }
2404
- const rule$e = {
2405
- meta: {
2406
- messages: {
2407
- [RULE_ID$6]: 'Field "{{fieldName}}" is unused',
2408
- },
2409
- docs: {
2410
- description: 'Requires all fields to be used at some level by siblings operations.',
2411
- category: 'Schema',
2412
- url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID$6}.md`,
2413
- requiresSiblings: true,
2414
- requiresSchema: true,
2415
- isDisabledForAllConfig: true,
2416
- examples: [
2417
- {
2418
- title: 'Incorrect',
2419
- code: /* GraphQL */ `
2420
- type User {
2421
- id: ID!
2422
- name: String
2423
- someUnusedField: String
2424
- }
2425
-
2426
- type Query {
2427
- me: User
2428
- }
2429
-
2430
- query {
2431
- me {
2432
- id
2433
- name
2434
- }
2435
- }
2436
- `,
2437
- },
2438
- {
2439
- title: 'Correct',
2440
- code: /* GraphQL */ `
2441
- type User {
2442
- id: ID!
2443
- name: String
2444
- }
2445
-
2446
- type Query {
2447
- me: User
2448
- }
2449
-
2450
- query {
2451
- me {
2452
- id
2453
- name
2454
- }
2455
- }
2456
- `,
2457
- },
2458
- ],
2459
- },
2460
- type: 'suggestion',
2461
- schema: [],
2462
- hasSuggestions: true,
2463
- },
2464
- create(context) {
2465
- const schema = requireGraphQLSchemaFromContext(RULE_ID$6, context);
2466
- const siblingsOperations = requireSiblingsOperations(RULE_ID$6, context);
2467
- const usedFields = getUsedFields(schema, siblingsOperations);
2468
- return {
2469
- FieldDefinition(node) {
2470
- var _a;
2471
- const fieldName = node.name.value;
2472
- const parentTypeName = node.parent.name.value;
2473
- const isUsed = (_a = usedFields[parentTypeName]) === null || _a === void 0 ? void 0 : _a.has(fieldName);
2474
- if (isUsed) {
2475
- return;
2476
- }
2477
- context.report({
2478
- node: node.name,
2479
- messageId: RULE_ID$6,
2480
- data: { fieldName },
2481
- suggest: [
2482
- {
2483
- desc: `Remove \`${fieldName}\` field`,
2484
- fix(fixer) {
2485
- const sourceCode = context.getSourceCode();
2486
- const tokenBefore = sourceCode.getTokenBefore(node);
2487
- const tokenAfter = sourceCode.getTokenAfter(node);
2488
- const isEmptyType = tokenBefore.type === '{' && tokenAfter.type === '}';
2489
- return fixer.remove((isEmptyType ? node.parent : node));
2490
- },
2491
- },
2492
- ],
2493
- });
2494
- },
2495
- };
2496
- },
2497
- };
2498
-
2499
- const RULE_ID$7 = 'relay-arguments';
2500
- const MISSING_ARGUMENTS = 'MISSING_ARGUMENTS';
2501
- const rule$f = {
2502
- meta: {
2503
- type: 'problem',
2504
- docs: {
2505
- category: 'Schema',
2506
- description: [
2507
- 'Set of rules to follow Relay specification for Arguments.',
2508
- '',
2509
- '- A field that returns a Connection type must include forward pagination arguments (`first` and `after`), backward pagination arguments (`last` and `before`), or both',
2510
- '',
2511
- 'Forward pagination arguments',
2512
- '',
2513
- '- `first` takes a non-negative integer',
2514
- '- `after` takes the Cursor type',
2515
- '',
2516
- 'Backward pagination arguments',
2517
- '',
2518
- '- `last` takes a non-negative integer',
2519
- '- `before` takes the Cursor type',
2520
- ].join('\n'),
2521
- url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID$7}.md`,
2522
- examples: [
2523
- {
2524
- title: 'Incorrect',
2525
- code: /* GraphQL */ `
2526
- type User {
2527
- posts: PostConnection
2528
- }
2529
- `,
2530
- },
2531
- {
2532
- title: 'Correct',
2533
- code: /* GraphQL */ `
2534
- type User {
2535
- posts(after: String, first: Int, before: String, last: Int): PostConnection
2536
- }
2537
- `,
2538
- },
2539
- ],
2540
- isDisabledForAllConfig: true,
2541
- },
2542
- messages: {
2543
- [MISSING_ARGUMENTS]: 'A field that returns a Connection type must include forward pagination arguments (`first` and `after`), backward pagination arguments (`last` and `before`), or both.',
2544
- },
2545
- schema: {
2546
- type: 'array',
2547
- maxItems: 1,
2548
- items: {
2549
- type: 'object',
2550
- additionalProperties: false,
2551
- minProperties: 1,
2552
- properties: {
2553
- includeBoth: {
2554
- type: 'boolean',
2555
- default: true,
2556
- description: 'Enforce including both forward and backward pagination arguments',
2557
- },
2558
- },
2559
- },
2560
- },
2561
- },
2562
- create(context) {
2563
- const schema = requireGraphQLSchemaFromContext(RULE_ID$7, context);
2564
- const { includeBoth = true } = context.options[0] || {};
2565
- return {
2566
- 'FieldDefinition > .gqlType Name[value=/Connection$/]'(node) {
2567
- let fieldNode = node.parent;
2568
- while (fieldNode.kind !== graphql.Kind.FIELD_DEFINITION) {
2569
- fieldNode = fieldNode.parent;
2570
- }
2571
- const args = Object.fromEntries(fieldNode.arguments.map(argument => [argument.name.value, argument]));
2572
- const hasForwardPagination = Boolean(args.first && args.after);
2573
- const hasBackwardPagination = Boolean(args.last && args.before);
2574
- if (!hasForwardPagination && !hasBackwardPagination) {
2575
- context.report({
2576
- node: fieldNode.name,
2577
- messageId: MISSING_ARGUMENTS,
2578
- });
2579
- return;
2580
- }
2581
- function checkField(typeName, argumentName) {
2582
- const argument = args[argumentName];
2583
- const hasArgument = Boolean(argument);
2584
- let type = argument;
2585
- if (hasArgument && type.gqlType.kind === graphql.Kind.NON_NULL_TYPE) {
2586
- type = type.gqlType;
2587
- }
2588
- const isAllowedNonNullType = hasArgument &&
2589
- type.gqlType.kind === graphql.Kind.NAMED_TYPE &&
2590
- (type.gqlType.name.value === typeName ||
2591
- (typeName === 'String' && graphql.isScalarType(schema.getType(type.gqlType.name.value))));
2592
- if (!isAllowedNonNullType) {
2593
- const returnType = typeName === 'String' ? 'String or Scalar' : typeName;
2594
- context.report({
2595
- node: (argument || fieldNode).name,
2596
- message: hasArgument
2597
- ? `Argument \`${argumentName}\` must return ${returnType}.`
2598
- : `Field \`${fieldNode.name.value}\` must contain an argument \`${argumentName}\`, that return ${returnType}.`,
2599
- });
2600
- }
2601
- }
2602
- if (includeBoth || args.first || args.after) {
2603
- checkField('Int', 'first');
2604
- checkField('String', 'after');
2605
- }
2606
- if (includeBoth || args.last || args.before) {
2607
- checkField('Int', 'last');
2608
- checkField('String', 'before');
2609
- }
2610
- },
2611
- };
2612
- },
2613
- };
2614
-
2615
- const MUST_BE_OBJECT_TYPE = 'MUST_BE_OBJECT_TYPE';
2616
- const MUST_CONTAIN_FIELD_EDGES = 'MUST_CONTAIN_FIELD_EDGES';
2617
- const MUST_CONTAIN_FIELD_PAGE_INFO = 'MUST_CONTAIN_FIELD_PAGE_INFO';
2618
- const MUST_HAVE_CONNECTION_SUFFIX = 'MUST_HAVE_CONNECTION_SUFFIX';
2619
- const EDGES_FIELD_MUST_RETURN_LIST_TYPE = 'EDGES_FIELD_MUST_RETURN_LIST_TYPE';
2620
- const PAGE_INFO_FIELD_MUST_RETURN_NON_NULL_TYPE = 'PAGE_INFO_FIELD_MUST_RETURN_NON_NULL_TYPE';
2621
- const NON_OBJECT_TYPES = [
2622
- graphql.Kind.SCALAR_TYPE_DEFINITION,
2623
- graphql.Kind.UNION_TYPE_DEFINITION,
2624
- graphql.Kind.UNION_TYPE_EXTENSION,
2625
- graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION,
2626
- graphql.Kind.INPUT_OBJECT_TYPE_EXTENSION,
2627
- graphql.Kind.ENUM_TYPE_DEFINITION,
2628
- graphql.Kind.ENUM_TYPE_EXTENSION,
2629
- graphql.Kind.INTERFACE_TYPE_DEFINITION,
2630
- graphql.Kind.INTERFACE_TYPE_EXTENSION,
2631
- ];
2632
- const notConnectionTypesSelector = `:matches(${NON_OBJECT_TYPES})[name.value=/Connection$/] > .name`;
2633
- const hasEdgesField = (node) => node.fields.some(field => field.name.value === 'edges');
2634
- const hasPageInfoField = (node) => node.fields.some(field => field.name.value === 'pageInfo');
2635
- const rule$g = {
2636
- meta: {
2637
- type: 'problem',
2638
- docs: {
2639
- category: 'Schema',
2640
- description: [
2641
- 'Set of rules to follow Relay specification for Connection types.',
2642
- '',
2643
- '- Any type whose name ends in "Connection" is considered by spec to be a `Connection type`',
2644
- '- Connection type must be an Object type',
2645
- '- Connection type must contain a field `edges` that return a list type that wraps an edge type',
2646
- '- Connection type must contain a field `pageInfo` that return a non-null `PageInfo` Object type',
2647
- ].join('\n'),
2648
- url: 'https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/relay-connection-types.md',
2649
- isDisabledForAllConfig: true,
2650
- examples: [
2651
- {
2652
- title: 'Incorrect',
2653
- code: /* GraphQL */ `
2654
- type UserPayload { # should be an Object type with \`Connection\` suffix
2655
- edges: UserEdge! # should return a list type
2656
- pageInfo: PageInfo # should return a non-null \`PageInfo\` Object type
2657
- }
2658
- `,
2659
- },
2660
- {
2661
- title: 'Correct',
2662
- code: /* GraphQL */ `
2663
- type UserConnection {
2664
- edges: [UserEdge]
2665
- pageInfo: PageInfo!
2666
- }
2667
- `,
2668
- },
2669
- ],
2670
- },
2671
- messages: {
2672
- // Connection types
2673
- [MUST_BE_OBJECT_TYPE]: 'Connection type must be an Object type.',
2674
- [MUST_HAVE_CONNECTION_SUFFIX]: 'Connection type must have `Connection` suffix.',
2675
- [MUST_CONTAIN_FIELD_EDGES]: 'Connection type must contain a field `edges` that return a list type.',
2676
- [MUST_CONTAIN_FIELD_PAGE_INFO]: 'Connection type must contain a field `pageInfo` that return a non-null `PageInfo` Object type.',
2677
- [EDGES_FIELD_MUST_RETURN_LIST_TYPE]: '`edges` field must return a list type.',
2678
- [PAGE_INFO_FIELD_MUST_RETURN_NON_NULL_TYPE]: '`pageInfo` field must return a non-null `PageInfo` Object type.',
2679
- },
2680
- schema: [],
2681
- },
2682
- create(context) {
2683
- return {
2684
- [notConnectionTypesSelector](node) {
2685
- context.report({ node, messageId: MUST_BE_OBJECT_TYPE });
2686
- },
2687
- ':matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value!=/Connection$/]'(node) {
2688
- if (hasEdgesField(node) && hasPageInfoField(node)) {
2689
- context.report({ node: node.name, messageId: MUST_HAVE_CONNECTION_SUFFIX });
2690
- }
2691
- },
2692
- ':matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=/Connection$/]'(node) {
2693
- if (!hasEdgesField(node)) {
2694
- context.report({ node: node.name, messageId: MUST_CONTAIN_FIELD_EDGES });
2695
- }
2696
- if (!hasPageInfoField(node)) {
2697
- context.report({ node: node.name, messageId: MUST_CONTAIN_FIELD_PAGE_INFO });
2698
- }
2699
- },
2700
- ':matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=/Connection$/] > FieldDefinition[name.value=edges] > .gqlType'(node) {
2701
- const isListType = node.kind === graphql.Kind.LIST_TYPE || (node.kind === graphql.Kind.NON_NULL_TYPE && node.gqlType.kind === graphql.Kind.LIST_TYPE);
2702
- if (!isListType) {
2703
- context.report({ node, messageId: EDGES_FIELD_MUST_RETURN_LIST_TYPE });
2704
- }
2705
- },
2706
- ':matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=/Connection$/] > FieldDefinition[name.value=pageInfo] > .gqlType'(node) {
2707
- const isNonNullPageInfoType = node.kind === graphql.Kind.NON_NULL_TYPE &&
2708
- node.gqlType.kind === graphql.Kind.NAMED_TYPE &&
2709
- node.gqlType.name.value === 'PageInfo';
2710
- if (!isNonNullPageInfoType) {
2711
- context.report({ node, messageId: PAGE_INFO_FIELD_MUST_RETURN_NON_NULL_TYPE });
2712
- }
2713
- },
2714
- };
2715
- },
2716
- };
2717
-
2718
- const RULE_ID$8 = 'relay-edge-types';
2719
- const MESSAGE_MUST_BE_OBJECT_TYPE = 'MESSAGE_MUST_BE_OBJECT_TYPE';
2720
- const MESSAGE_MISSING_EDGE_SUFFIX = 'MESSAGE_MISSING_EDGE_SUFFIX';
2721
- const MESSAGE_LIST_TYPE_ONLY_EDGE_TYPE = 'MESSAGE_LIST_TYPE_ONLY_EDGE_TYPE';
2722
- const MESSAGE_SHOULD_IMPLEMENTS_NODE = 'MESSAGE_SHOULD_IMPLEMENTS_NODE';
2723
- let edgeTypesCache;
2724
- function getEdgeTypes(schema) {
2725
- // We don't want cache edgeTypes on test environment
2726
- // Otherwise edgeTypes will be same for all tests
2727
- if (process.env.NODE_ENV !== 'test' && edgeTypesCache) {
2728
- return edgeTypesCache;
2729
- }
2730
- const edgeTypes = new Set();
2731
- const visitor = {
2732
- ObjectTypeDefinition(node) {
2733
- const typeName = node.name.value;
2734
- const hasConnectionSuffix = typeName.endsWith('Connection');
2735
- if (!hasConnectionSuffix) {
2736
- return;
2737
- }
2738
- const edges = node.fields.find(field => field.name.value === 'edges');
2739
- if (edges) {
2740
- const edgesTypeName = getTypeName(edges);
2741
- const edgesType = schema.getType(edgesTypeName);
2742
- if (graphql.isObjectType(edgesType)) {
2743
- edgeTypes.add(edgesTypeName);
2744
- }
2745
- }
2746
- },
2747
- };
2748
- const astNode = utils.getDocumentNodeFromSchema(schema); // Transforms the schema into ASTNode
2749
- graphql.visit(astNode, visitor);
2750
- edgeTypesCache = edgeTypes;
2751
- return edgeTypesCache;
2752
- }
2753
- const rule$h = {
2754
- meta: {
2755
- type: 'problem',
2756
- docs: {
2757
- category: 'Schema',
2758
- description: [
2759
- 'Set of rules to follow Relay specification for Edge types.',
2760
- '',
2761
- "- A type that is returned in list form by a connection type's `edges` field is considered by this spec to be an Edge type",
2762
- '- Edge type must be an Object type',
2763
- '- Edge type must contain a field `node` that return either Scalar, Enum, Object, Interface, Union, or a non-null wrapper around one of those types. Notably, this field cannot return a list',
2764
- '- Edge type must contain a field `cursor` that return either String, Scalar, or a non-null wrapper around one of those types',
2765
- '- Edge type name must end in "Edge" _(optional)_',
2766
- "- Edge type's field `node` must implement `Node` interface _(optional)_",
2767
- '- A list type should only wrap an edge type _(optional)_',
2768
- ].join('\n'),
2769
- url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID$8}.md`,
2770
- isDisabledForAllConfig: true,
2771
- requiresSchema: true,
2772
- examples: [
2773
- {
2774
- title: 'Correct',
2775
- code: /* GraphQL */ `
2776
- type UserConnection {
2777
- edges: [UserEdge]
2778
- pageInfo: PageInfo!
2779
- }
2780
- `,
2781
- },
2782
- ],
2783
- },
2784
- messages: {
2785
- [MESSAGE_MUST_BE_OBJECT_TYPE]: 'Edge type must be an Object type.',
2786
- [MESSAGE_MISSING_EDGE_SUFFIX]: 'Edge type must have "Edge" suffix.',
2787
- [MESSAGE_LIST_TYPE_ONLY_EDGE_TYPE]: 'A list type should only wrap an edge type.',
2788
- [MESSAGE_SHOULD_IMPLEMENTS_NODE]: "Edge type's field `node` must implement `Node` interface.",
2789
- },
2790
- schema: {
2791
- type: 'array',
2792
- maxItems: 1,
2793
- items: {
2794
- type: 'object',
2795
- additionalProperties: false,
2796
- minProperties: 1,
2797
- properties: {
2798
- withEdgeSuffix: {
2799
- type: 'boolean',
2800
- default: true,
2801
- description: 'Edge type name must end in "Edge".',
2802
- },
2803
- shouldImplementNode: {
2804
- type: 'boolean',
2805
- default: true,
2806
- description: "Edge type's field `node` must implement `Node` interface.",
2807
- },
2808
- listTypeCanWrapOnlyEdgeType: {
2809
- type: 'boolean',
2810
- default: true,
2811
- description: 'A list type should only wrap an edge type.',
2812
- },
2813
- },
2814
- },
2815
- },
2816
- },
2817
- create(context) {
2818
- const schema = requireGraphQLSchemaFromContext(RULE_ID$8, context);
2819
- const edgeTypes = getEdgeTypes(schema);
2820
- const options = {
2821
- withEdgeSuffix: true,
2822
- shouldImplementNode: true,
2823
- listTypeCanWrapOnlyEdgeType: true,
2824
- ...context.options[0],
2825
- };
2826
- const isNamedOrNonNullNamed = (node) => node.kind === graphql.Kind.NAMED_TYPE || (node.kind === graphql.Kind.NON_NULL_TYPE && node.gqlType.kind === graphql.Kind.NAMED_TYPE);
2827
- const checkNodeField = (node) => {
2828
- const nodeField = node.fields.find(field => field.name.value === 'node');
2829
- const message = 'return either a Scalar, Enum, Object, Interface, Union, or a non-null wrapper around one of those types.';
2830
- if (!nodeField) {
2831
- context.report({
2832
- node: node.name,
2833
- message: `Edge type must contain a field \`node\` that ${message}`,
2834
- });
2835
- }
2836
- else if (!isNamedOrNonNullNamed(nodeField.gqlType)) {
2837
- context.report({ node: nodeField.name, message: `Field \`node\` must ${message}` });
2838
- }
2839
- else if (options.shouldImplementNode) {
2840
- const nodeReturnTypeName = getTypeName(nodeField.gqlType.rawNode());
2841
- const type = schema.getType(nodeReturnTypeName);
2842
- if (!graphql.isObjectType(type)) {
2843
- return;
2844
- }
2845
- const implementsNode = type.astNode.interfaces.some(n => n.name.value === 'Node');
2846
- if (!implementsNode) {
2847
- context.report({ node: node.name, messageId: MESSAGE_SHOULD_IMPLEMENTS_NODE });
2848
- }
2849
- }
2850
- };
2851
- const checkCursorField = (node) => {
2852
- const cursorField = node.fields.find(field => field.name.value === 'cursor');
2853
- const message = 'return either a String, Scalar, or a non-null wrapper wrapper around one of those types.';
2854
- if (!cursorField) {
2855
- context.report({
2856
- node: node.name,
2857
- message: `Edge type must contain a field \`cursor\` that ${message}`,
2858
- });
2859
- return;
2860
- }
2861
- const typeName = getTypeName(cursorField.rawNode());
2862
- if (!isNamedOrNonNullNamed(cursorField.gqlType) ||
2863
- (typeName !== 'String' && !graphql.isScalarType(schema.getType(typeName)))) {
2864
- context.report({ node: cursorField.name, message: `Field \`cursor\` must ${message}` });
2865
- }
2866
- };
2867
- const listeners = {
2868
- ':matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=/Connection$/] > FieldDefinition[name.value=edges] > .gqlType Name'(node) {
2869
- const type = schema.getType(node.value);
2870
- if (!graphql.isObjectType(type)) {
2871
- context.report({ node, messageId: MESSAGE_MUST_BE_OBJECT_TYPE });
2872
- }
2873
- },
2874
- ':matches(ObjectTypeDefinition, ObjectTypeExtension)'(node) {
2875
- const typeName = node.name.value;
2876
- if (edgeTypes.has(typeName)) {
2877
- checkNodeField(node);
2878
- checkCursorField(node);
2879
- if (options.withEdgeSuffix && !typeName.endsWith('Edge')) {
2880
- context.report({ node: node.name, messageId: MESSAGE_MISSING_EDGE_SUFFIX });
2881
- }
2882
- }
2883
- },
2884
- };
2885
- if (options.listTypeCanWrapOnlyEdgeType) {
2886
- listeners['FieldDefinition > .gqlType'] = (node) => {
2887
- if (node.kind === graphql.Kind.LIST_TYPE ||
2888
- (node.kind === graphql.Kind.NON_NULL_TYPE && node.gqlType.kind === graphql.Kind.LIST_TYPE)) {
2889
- const typeName = getTypeName(node.rawNode());
2890
- if (!edgeTypes.has(typeName)) {
2891
- context.report({ node, messageId: MESSAGE_LIST_TYPE_ONLY_EDGE_TYPE });
2892
- }
2893
- }
2894
- };
2895
- }
2896
- return listeners;
2897
- },
2898
- };
2899
-
2900
- const RULE_ID$9 = 'relay-page-info';
2901
- const MESSAGE_MUST_EXIST = 'MESSAGE_MUST_EXIST';
2902
- const MESSAGE_MUST_BE_OBJECT_TYPE$1 = 'MESSAGE_MUST_BE_OBJECT_TYPE';
2903
- const notPageInfoTypesSelector = `:matches(${NON_OBJECT_TYPES})[name.value=PageInfo] > .name`;
2904
- let hasPageInfoChecked = false;
2905
- const rule$i = {
2906
- meta: {
2907
- type: 'problem',
2908
- docs: {
2909
- category: 'Schema',
2910
- description: [
2911
- 'Set of rules to follow Relay specification for `PageInfo` object.',
2912
- '',
2913
- '- `PageInfo` must be an Object type',
2914
- '- `PageInfo` must contain fields `hasPreviousPage` and `hasNextPage`, that return non-null Boolean',
2915
- '- `PageInfo` must contain fields `startCursor` and `endCursor`, that return either String or Scalar, which can be null if there are no results',
2916
- ].join('\n'),
2917
- url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID$9}.md`,
2918
- examples: [
2919
- {
2920
- title: 'Correct',
2921
- code: /* GraphQL */ `
2922
- type PageInfo {
2923
- hasPreviousPage: Boolean!
2924
- hasNextPage: Boolean!
2925
- startCursor: String
2926
- endCursor: String
2927
- }
2928
- `,
2929
- },
2930
- ],
2931
- isDisabledForAllConfig: true,
2932
- requiresSchema: true,
2933
- },
2934
- messages: {
2935
- [MESSAGE_MUST_EXIST]: 'The server must provide a `PageInfo` object.',
2936
- [MESSAGE_MUST_BE_OBJECT_TYPE$1]: '`PageInfo` must be an Object type.',
2937
- },
2938
- schema: [],
2939
- },
2940
- create(context) {
2941
- const schema = requireGraphQLSchemaFromContext(RULE_ID$9, context);
2942
- if (process.env.NODE_ENV === 'test' || !hasPageInfoChecked) {
2943
- const pageInfoType = schema.getType('PageInfo');
2944
- if (!pageInfoType) {
2945
- context.report({
2946
- loc: REPORT_ON_FIRST_CHARACTER,
2947
- messageId: MESSAGE_MUST_EXIST,
2948
- });
2949
- }
2950
- hasPageInfoChecked = true;
2951
- }
2952
- return {
2953
- [notPageInfoTypesSelector](node) {
2954
- context.report({ node, messageId: MESSAGE_MUST_BE_OBJECT_TYPE$1 });
2955
- },
2956
- 'ObjectTypeDefinition[name.value=PageInfo]'(node) {
2957
- const fieldMap = Object.fromEntries(node.fields.map(field => [field.name.value, field]));
2958
- const checkField = (fieldName, typeName) => {
2959
- const field = fieldMap[fieldName];
2960
- let isAllowedType = false;
2961
- if (field) {
2962
- const type = field.gqlType;
2963
- if (typeName === 'Boolean') {
2964
- isAllowedType =
2965
- type.kind === graphql.Kind.NON_NULL_TYPE &&
2966
- type.gqlType.kind === graphql.Kind.NAMED_TYPE &&
2967
- type.gqlType.name.value === 'Boolean';
2968
- }
2969
- else if (type.kind === graphql.Kind.NAMED_TYPE) {
2970
- isAllowedType = type.name.value === 'String' || graphql.isScalarType(schema.getType(type.name.value));
2971
- }
2972
- }
2973
- if (!isAllowedType) {
2974
- const returnType = typeName === 'Boolean'
2975
- ? 'non-null Boolean'
2976
- : 'either String or Scalar, which can be null if there are no results';
2977
- context.report({
2978
- node: field ? field.name : node.name,
2979
- message: field
2980
- ? `Field \`${fieldName}\` must return ${returnType}.`
2981
- : `\`PageInfo\` must contain a field \`${fieldName}\`, that return ${returnType}.`,
2982
- });
2983
- }
2984
- };
2985
- checkField('hasPreviousPage', 'Boolean');
2986
- checkField('hasNextPage', 'Boolean');
2987
- checkField('startCursor', 'String');
2988
- checkField('endCursor', 'String');
2989
- },
2990
- };
2991
- },
2992
- };
2993
-
2994
- const valueFromNode = (...args) => {
2995
- return valueFromASTUntyped.valueFromASTUntyped(...args);
2996
- };
2997
- function getBaseType(type) {
2998
- if (graphql.isNonNullType(type) || graphql.isListType(type)) {
2999
- return getBaseType(type.ofType);
3000
- }
3001
- return type;
3002
- }
3003
- function convertToken(token, type) {
3004
- const { line, column, end, start, value } = token;
3005
- return {
3006
- type,
3007
- value,
3008
- /*
3009
- * ESLint has 0-based column number
3010
- * https://eslint.org/docs/developer-guide/working-with-rules#contextreport
3011
- */
3012
- loc: {
3013
- start: {
3014
- line,
3015
- column: column - 1,
3016
- },
3017
- end: {
3018
- line,
3019
- column: column - 1 + (end - start),
3020
- },
3021
- },
3022
- range: [start, end],
3023
- };
3024
- }
3025
- function getLexer(source) {
3026
- // GraphQL v14
3027
- const gqlLanguage = require('graphql/language');
3028
- if (gqlLanguage && gqlLanguage.createLexer) {
3029
- return gqlLanguage.createLexer(source, {});
3030
- }
3031
- // GraphQL v15
3032
- const { Lexer: LexerCls } = require('graphql');
3033
- if (LexerCls && typeof LexerCls === 'function') {
3034
- return new LexerCls(source);
3035
- }
3036
- throw new Error('Unsupported GraphQL version! Please make sure to use GraphQL v14 or newer!');
3037
- }
3038
- function extractTokens(filePath, code) {
3039
- const source = new graphql.Source(code, filePath);
3040
- const lexer = getLexer(source);
3041
- const tokens = [];
3042
- let token = lexer.advance();
3043
- while (token && token.kind !== graphql.TokenKind.EOF) {
3044
- const result = convertToken(token, token.kind);
3045
- tokens.push(result);
3046
- token = lexer.advance();
3047
- }
3048
- return tokens;
3049
- }
3050
- function extractComments(loc) {
3051
- if (!loc) {
3052
- return [];
3053
- }
3054
- const comments = [];
3055
- let token = loc.startToken;
3056
- while (token) {
3057
- if (token.kind === graphql.TokenKind.COMMENT) {
3058
- const comment = convertToken(token,
3059
- // `eslint-disable` directive works only with `Block` type comment
3060
- token.value.trimStart().startsWith('eslint') ? 'Block' : 'Line');
3061
- comments.push(comment);
3062
- }
3063
- token = token.next;
3064
- }
3065
- return comments;
3066
- }
3067
- function convertLocation(location) {
3068
- const { startToken, endToken, source, start, end } = location;
3069
- /*
3070
- * ESLint has 0-based column number
3071
- * https://eslint.org/docs/developer-guide/working-with-rules#contextreport
3072
- */
3073
- const loc = {
3074
- start: {
3075
- /*
3076
- * Kind.Document has startToken: { line: 0, column: 0 }, we set line as 1 and column as 0
3077
- */
3078
- line: startToken.line === 0 ? 1 : startToken.line,
3079
- column: startToken.column === 0 ? 0 : startToken.column - 1,
3080
- },
3081
- end: {
3082
- line: endToken.line,
3083
- column: endToken.column - 1,
3084
- },
3085
- source: source.body,
3086
- };
3087
- if (loc.start.column === loc.end.column) {
3088
- loc.end.column += end - start;
3089
- }
3090
- return loc;
3091
- }
3092
-
3093
- function convertToESTree(node, schema) {
3094
- const typeInfo = schema ? new graphql.TypeInfo(schema) : null;
3095
- const visitor = {
3096
- leave(node, key, parent) {
3097
- const leadingComments = 'description' in node && node.description
3098
- ? [
3099
- {
3100
- type: node.description.block ? 'Block' : 'Line',
3101
- value: node.description.value,
3102
- },
3103
- ]
3104
- : [];
3105
- const calculatedTypeInfo = typeInfo
3106
- ? {
3107
- argument: typeInfo.getArgument(),
3108
- defaultValue: typeInfo.getDefaultValue(),
3109
- directive: typeInfo.getDirective(),
3110
- enumValue: typeInfo.getEnumValue(),
3111
- fieldDef: typeInfo.getFieldDef(),
3112
- inputType: typeInfo.getInputType(),
3113
- parentInputType: typeInfo.getParentInputType(),
3114
- parentType: typeInfo.getParentType(),
3115
- gqlType: typeInfo.getType(),
3116
- }
3117
- : {};
3118
- const rawNode = () => {
3119
- if (parent && key !== undefined) {
3120
- return parent[key];
3121
- }
3122
- return node.kind === graphql.Kind.DOCUMENT
3123
- ? {
3124
- ...node,
3125
- definitions: node.definitions.map(definition => definition.rawNode()),
3126
- }
3127
- : node;
3128
- };
3129
- const commonFields = {
3130
- ...node,
3131
- type: node.kind,
3132
- loc: convertLocation(node.loc),
3133
- range: [node.loc.start, node.loc.end],
3134
- leadingComments,
3135
- // Use function to prevent RangeError: Maximum call stack size exceeded
3136
- typeInfo: () => calculatedTypeInfo,
3137
- rawNode,
3138
- };
3139
- return 'type' in node
3140
- ? {
3141
- ...commonFields,
3142
- gqlType: node.type,
3143
- }
3144
- : commonFields;
3145
- },
3146
- };
3147
- return graphql.visit(node, typeInfo ? graphql.visitWithTypeInfo(typeInfo, visitor) : visitor);
3148
- }
3149
-
3150
- // eslint-disable-next-line unicorn/better-regex
3151
- const DATE_REGEX = /^\d{2}\/\d{2}\/\d{4}$/;
3152
- const MESSAGE_REQUIRE_DATE = 'MESSAGE_REQUIRE_DATE';
3153
- const MESSAGE_INVALID_FORMAT = 'MESSAGE_INVALID_FORMAT';
3154
- const MESSAGE_INVALID_DATE = 'MESSAGE_INVALID_DATE';
3155
- const MESSAGE_CAN_BE_REMOVED = 'MESSAGE_CAN_BE_REMOVED';
3156
- const rule$j = {
3157
- meta: {
3158
- type: 'suggestion',
3159
- hasSuggestions: true,
3160
- docs: {
3161
- category: 'Schema',
3162
- description: 'Require deletion date on `@deprecated` directive. Suggest removing deprecated things after deprecated date.',
3163
- url: 'https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/require-deprecation-date.md',
3164
- examples: [
3165
- {
3166
- title: 'Incorrect',
3167
- code: /* GraphQL */ `
3168
- type User {
3169
- firstname: String @deprecated
3170
- firstName: String
3171
- }
3172
- `,
3173
- },
3174
- {
3175
- title: 'Incorrect',
3176
- code: /* GraphQL */ `
3177
- type User {
3178
- firstname: String @deprecated(reason: "Use 'firstName' instead")
3179
- firstName: String
3180
- }
3181
- `,
3182
- },
3183
- {
3184
- title: 'Correct',
3185
- code: /* GraphQL */ `
3186
- type User {
3187
- firstname: String @deprecated(reason: "Use 'firstName' instead", deletionDate: "25/12/2022")
3188
- firstName: String
3189
- }
3190
- `,
3191
- },
3192
- ],
3193
- },
3194
- messages: {
3195
- [MESSAGE_REQUIRE_DATE]: 'Directive "@deprecated" must have a deletion date',
3196
- [MESSAGE_INVALID_FORMAT]: 'Deletion date must be in format "DD/MM/YYYY"',
3197
- [MESSAGE_INVALID_DATE]: 'Invalid "{{ deletionDate }}" deletion date',
3198
- [MESSAGE_CAN_BE_REMOVED]: '"{{ nodeName }}" сan be removed',
3199
- },
3200
- schema: [
3201
- {
3202
- type: 'object',
3203
- additionalProperties: false,
3204
- properties: {
3205
- argumentName: {
3206
- type: 'string',
3207
- },
3208
- },
3209
- },
3210
- ],
3211
- },
3212
- create(context) {
3213
- return {
3214
- 'Directive[name.value=deprecated]'(node) {
3215
- var _a;
3216
- const argName = ((_a = context.options[0]) === null || _a === void 0 ? void 0 : _a.argumentName) || 'deletionDate';
3217
- const deletionDateNode = node.arguments.find(arg => arg.name.value === argName);
3218
- if (!deletionDateNode) {
3219
- context.report({
3220
- node: node.name,
3221
- messageId: MESSAGE_REQUIRE_DATE,
3222
- });
3223
- return;
3224
- }
3225
- const deletionDate = valueFromNode(deletionDateNode.value);
3226
- const isValidDate = DATE_REGEX.test(deletionDate);
3227
- if (!isValidDate) {
3228
- context.report({ node: deletionDateNode.value, messageId: MESSAGE_INVALID_FORMAT });
3229
- return;
3230
- }
3231
- let [day, month, year] = deletionDate.split('/');
3232
- day = day.padStart(2, '0');
3233
- month = month.padStart(2, '0');
3234
- const deletionDateInMS = Date.parse(`${year}-${month}-${day}`);
3235
- if (Number.isNaN(deletionDateInMS)) {
3236
- context.report({
3237
- node: deletionDateNode.value,
3238
- messageId: MESSAGE_INVALID_DATE,
3239
- data: {
3240
- deletionDate,
3241
- },
3242
- });
3243
- return;
3244
- }
3245
- const canRemove = Date.now() > deletionDateInMS;
3246
- if (canRemove) {
3247
- const { parent } = node;
3248
- const nodeName = parent.name.value;
3249
- context.report({
3250
- node: parent.name,
3251
- messageId: MESSAGE_CAN_BE_REMOVED,
3252
- data: { nodeName },
3253
- suggest: [
3254
- {
3255
- desc: `Remove \`${nodeName}\``,
3256
- fix: fixer => fixer.remove(parent),
3257
- },
3258
- ],
3259
- });
3260
- }
3261
- },
3262
- };
3263
- },
3264
- };
3265
-
3266
- const rule$k = {
3267
- meta: {
3268
- docs: {
3269
- description: 'Require all deprecation directives to specify a reason.',
3270
- category: 'Schema',
3271
- url: 'https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/require-deprecation-reason.md',
3272
- recommended: true,
3273
- examples: [
3274
- {
3275
- title: 'Incorrect',
3276
- code: /* GraphQL */ `
3277
- type MyType {
3278
- name: String @deprecated
3279
- }
3280
- `,
3281
- },
3282
- {
3283
- title: 'Incorrect',
3284
- code: /* GraphQL */ `
3285
- type MyType {
3286
- name: String @deprecated(reason: "")
3287
- }
3288
- `,
3289
- },
3290
- {
3291
- title: 'Correct',
3292
- code: /* GraphQL */ `
3293
- type MyType {
3294
- name: String @deprecated(reason: "no longer relevant, please use fullName field")
3295
- }
3296
- `,
3297
- },
3298
- ],
3299
- },
3300
- type: 'suggestion',
3301
- schema: [],
3302
- },
3303
- create(context) {
3304
- return {
3305
- 'Directive[name.value=deprecated]'(node) {
3306
- const reasonArgument = node.arguments.find(arg => arg.name.value === 'reason');
3307
- const value = reasonArgument && String(valueFromNode(reasonArgument.value)).trim();
3308
- if (!value) {
3309
- context.report({
3310
- node: node.name,
3311
- message: 'Directive "@deprecated" must have a reason!',
3312
- });
3313
- }
3314
- },
3315
- };
3316
- },
3317
- };
3318
-
3319
- const RULE_ID$a = 'require-description';
3320
- const ALLOWED_KINDS$1 = [
3321
- ...TYPES_KINDS,
3322
- graphql.Kind.DIRECTIVE_DEFINITION,
3323
- graphql.Kind.FIELD_DEFINITION,
3324
- graphql.Kind.INPUT_VALUE_DEFINITION,
3325
- graphql.Kind.ENUM_VALUE_DEFINITION,
3326
- graphql.Kind.OPERATION_DEFINITION,
3327
- ];
3328
- function getNodeName(node) {
3329
- const DisplayNodeNameMap = {
3330
- [graphql.Kind.OBJECT_TYPE_DEFINITION]: 'type',
3331
- [graphql.Kind.INTERFACE_TYPE_DEFINITION]: 'interface',
3332
- [graphql.Kind.ENUM_TYPE_DEFINITION]: 'enum',
3333
- [graphql.Kind.SCALAR_TYPE_DEFINITION]: 'scalar',
3334
- [graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION]: 'input',
3335
- [graphql.Kind.UNION_TYPE_DEFINITION]: 'union',
3336
- [graphql.Kind.DIRECTIVE_DEFINITION]: 'directive',
3337
- };
3338
- switch (node.kind) {
3339
- case graphql.Kind.OBJECT_TYPE_DEFINITION:
3340
- case graphql.Kind.INTERFACE_TYPE_DEFINITION:
3341
- case graphql.Kind.ENUM_TYPE_DEFINITION:
3342
- case graphql.Kind.SCALAR_TYPE_DEFINITION:
3343
- case graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION:
3344
- case graphql.Kind.UNION_TYPE_DEFINITION:
3345
- return `${DisplayNodeNameMap[node.kind]} ${node.name.value}`;
3346
- case graphql.Kind.DIRECTIVE_DEFINITION:
3347
- return `${DisplayNodeNameMap[node.kind]} @${node.name.value}`;
3348
- case graphql.Kind.FIELD_DEFINITION:
3349
- case graphql.Kind.INPUT_VALUE_DEFINITION:
3350
- case graphql.Kind.ENUM_VALUE_DEFINITION:
3351
- return `${node.parent.name.value}.${node.name.value}`;
3352
- case graphql.Kind.OPERATION_DEFINITION:
3353
- return node.name ? `${node.operation} ${node.name.value}` : node.operation;
3354
- }
3355
- }
3356
- const rule$l = {
3357
- meta: {
3358
- docs: {
3359
- category: 'Schema',
3360
- description: 'Enforce descriptions in type definitions and operations.',
3361
- url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID$a}.md`,
3362
- examples: [
3363
- {
3364
- title: 'Incorrect',
3365
- usage: [{ types: true, FieldDefinition: true }],
3366
- code: /* GraphQL */ `
3367
- type someTypeName {
3368
- name: String
3369
- }
3370
- `,
3371
- },
3372
- {
3373
- title: 'Correct',
3374
- usage: [{ types: true, FieldDefinition: true }],
3375
- code: /* GraphQL */ `
3376
- """
3377
- Some type description
3378
- """
3379
- type someTypeName {
3380
- """
3381
- Name description
3382
- """
3383
- name: String
3384
- }
3385
- `,
3386
- },
3387
- {
3388
- title: 'Correct',
3389
- usage: [{ OperationDefinition: true }],
3390
- code: /* GraphQL */ `
3391
- # Create a new user
3392
- mutation createUser {
3393
- # ...
3394
- }
3395
- `,
3396
- },
3397
- ],
3398
- configOptions: [
3399
- {
3400
- types: true,
3401
- [graphql.Kind.DIRECTIVE_DEFINITION]: true,
3402
- },
3403
- ],
3404
- recommended: true,
3405
- },
3406
- type: 'suggestion',
3407
- messages: {
3408
- [RULE_ID$a]: 'Description is required for `{{ nodeName }}`.',
3409
- },
3410
- schema: {
3411
- type: 'array',
3412
- minItems: 1,
3413
- maxItems: 1,
3414
- items: {
3415
- type: 'object',
3416
- additionalProperties: false,
3417
- minProperties: 1,
3418
- properties: {
3419
- types: {
3420
- type: 'boolean',
3421
- description: `Includes:\n\n${TYPES_KINDS.map(kind => `- \`${kind}\``).join('\n')}`,
3422
- },
3423
- ...Object.fromEntries([...ALLOWED_KINDS$1].sort().map(kind => {
3424
- let description = `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`;
3425
- if (kind === graphql.Kind.OPERATION_DEFINITION) {
3426
- description += '\n\n> You must use only comment syntax `#` and not description syntax `"""` or `"`.';
3427
- }
3428
- return [kind, { type: 'boolean', description }];
3429
- })),
3430
- },
3431
- },
3432
- },
3433
- },
3434
- create(context) {
3435
- const { types, ...restOptions } = context.options[0] || {};
3436
- const kinds = new Set(types ? TYPES_KINDS : []);
3437
- for (const [kind, isEnabled] of Object.entries(restOptions)) {
3438
- if (isEnabled) {
3439
- kinds.add(kind);
3440
- }
3441
- else {
3442
- kinds.delete(kind);
3443
- }
3444
- }
3445
- const selector = [...kinds].join(',');
3446
- return {
3447
- [selector](node) {
3448
- var _a;
3449
- let description = '';
3450
- const isOperation = node.kind === graphql.Kind.OPERATION_DEFINITION;
3451
- if (isOperation) {
3452
- const rawNode = node.rawNode();
3453
- const { prev, line } = rawNode.loc.startToken;
3454
- if (prev.kind === graphql.TokenKind.COMMENT) {
3455
- const value = prev.value.trim();
3456
- const linesBefore = line - prev.line;
3457
- if (!value.startsWith('eslint') && linesBefore === 1) {
3458
- description = value;
3459
- }
3460
- }
3461
- }
3462
- else {
3463
- description = ((_a = node.description) === null || _a === void 0 ? void 0 : _a.value.trim()) || '';
3464
- }
3465
- if (description.length === 0) {
3466
- context.report({
3467
- loc: isOperation ? getLocation(node.loc.start, node.operation) : node.name.loc,
3468
- messageId: RULE_ID$a,
3469
- data: {
3470
- nodeName: getNodeName(node),
3471
- },
3472
- });
3473
- }
3474
- },
3475
- };
3476
- },
3477
- };
3478
-
3479
- const RULE_ID$b = 'require-field-of-type-query-in-mutation-result';
3480
- const rule$m = {
3481
- meta: {
3482
- type: 'suggestion',
3483
- docs: {
3484
- category: 'Schema',
3485
- description: 'Allow the client in one round-trip not only to call mutation but also to get a wagon of data to update their application.\n> Currently, no errors are reported for result type `union`, `interface` and `scalar`.',
3486
- url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID$b}.md`,
3487
- requiresSchema: true,
3488
- examples: [
3489
- {
3490
- title: 'Incorrect',
3491
- code: /* GraphQL */ `
3492
- type User { ... }
3493
-
3494
- type Mutation {
3495
- createUser: User!
3496
- }
3497
- `,
3498
- },
3499
- {
3500
- title: 'Correct',
3501
- code: /* GraphQL */ `
3502
- type User { ... }
3503
-
3504
- type Query { ... }
3505
-
3506
- type CreateUserPayload {
3507
- user: User!
3508
- query: Query!
3509
- }
3510
-
3511
- type Mutation {
3512
- createUser: CreateUserPayload!
3513
- }
3514
- `,
3515
- },
3516
- ],
3517
- },
3518
- schema: [],
3519
- },
3520
- create(context) {
3521
- const schema = requireGraphQLSchemaFromContext(RULE_ID$b, context);
3522
- const mutationType = schema.getMutationType();
3523
- const queryType = schema.getQueryType();
3524
- if (!mutationType || !queryType) {
3525
- return {};
3526
- }
3527
- const selector = `:matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=${mutationType.name}] > FieldDefinition > .gqlType Name`;
3528
- return {
3529
- [selector](node) {
3530
- const typeName = node.value;
3531
- const graphQLType = schema.getType(typeName);
3532
- if (graphql.isObjectType(graphQLType)) {
3533
- const { fields } = graphQLType.astNode;
3534
- const hasQueryType = fields.some(field => getTypeName(field) === queryType.name);
3535
- if (!hasQueryType) {
3536
- context.report({
3537
- node,
3538
- message: `Mutation result type "${graphQLType.name}" must contain field of type "${queryType.name}"`,
3539
- });
3540
- }
3541
- }
3542
- },
3543
- };
3544
- },
3545
- };
3546
-
3547
- const RULE_ID$c = 'require-id-when-available';
3548
- const DEFAULT_ID_FIELD_NAME = 'id';
3549
- const rule$n = {
3550
- meta: {
3551
- type: 'suggestion',
3552
- // eslint-disable-next-line eslint-plugin/require-meta-has-suggestions
3553
- hasSuggestions: true,
3554
- docs: {
3555
- category: 'Operations',
3556
- description: 'Enforce selecting specific fields when they are available on the GraphQL type.',
3557
- url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID$c}.md`,
3558
- requiresSchema: true,
3559
- requiresSiblings: true,
3560
- examples: [
3561
- {
3562
- title: 'Incorrect',
3563
- code: /* GraphQL */ `
3564
- # In your schema
3565
- type User {
3566
- id: ID!
3567
- name: String!
3568
- }
3569
-
3570
- # Query
3571
- query {
3572
- user {
3573
- name
3574
- }
3575
- }
3576
- `,
3577
- },
3578
- {
3579
- title: 'Correct',
3580
- code: /* GraphQL */ `
3581
- # In your schema
3582
- type User {
3583
- id: ID!
3584
- name: String!
3585
- }
3586
-
3587
- # Query
3588
- query {
3589
- user {
3590
- id
3591
- name
3592
- }
3593
- }
3594
-
3595
- # Selecting \`id\` with an alias is also valid
3596
- query {
3597
- user {
3598
- id: name
3599
- }
3600
- }
3601
- `,
3602
- },
3603
- ],
3604
- recommended: true,
3605
- },
3606
- messages: {
3607
- [RULE_ID$c]: "Field{{ pluralSuffix }} {{ fieldName }} must be selected when it's available on a type.\nInclude it in your selection set{{ addition }}.",
3608
- },
3609
- schema: {
3610
- definitions: {
3611
- asString: {
3612
- type: 'string',
3613
- },
3614
- asArray: ARRAY_DEFAULT_OPTIONS,
3615
- },
3616
- type: 'array',
3617
- maxItems: 1,
3618
- items: {
3619
- type: 'object',
3620
- additionalProperties: false,
3621
- properties: {
3622
- fieldName: {
3623
- oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asArray' }],
3624
- default: DEFAULT_ID_FIELD_NAME,
3625
- },
3626
- },
3627
- },
3628
- },
3629
- },
3630
- create(context) {
3631
- const schema = requireGraphQLSchemaFromContext(RULE_ID$c, context);
3632
- const siblings = requireSiblingsOperations(RULE_ID$c, context);
3633
- const { fieldName = DEFAULT_ID_FIELD_NAME } = context.options[0] || {};
3634
- const idNames = utils.asArray(fieldName);
3635
- // Check selections only in OperationDefinition,
3636
- // skip selections of OperationDefinition and InlineFragment
3637
- const selector = 'OperationDefinition SelectionSet[parent.kind!=/(^OperationDefinition|InlineFragment)$/]';
3638
- const typeInfo = new graphql.TypeInfo(schema);
3639
- function checkFragments(node) {
3640
- for (const selection of node.selections) {
3641
- if (selection.kind !== graphql.Kind.FRAGMENT_SPREAD) {
3642
- continue;
3643
- }
3644
- const [foundSpread] = siblings.getFragment(selection.name.value);
3645
- if (!foundSpread) {
3646
- continue;
3647
- }
3648
- const checkedFragmentSpreads = new Set();
3649
- const visitor = graphql.visitWithTypeInfo(typeInfo, {
3650
- SelectionSet(node, key, parent) {
3651
- if (parent.kind === graphql.Kind.FRAGMENT_DEFINITION) {
3652
- checkedFragmentSpreads.add(parent.name.value);
3653
- }
3654
- else if (parent.kind !== graphql.Kind.INLINE_FRAGMENT) {
3655
- checkSelections(node, typeInfo.getType(), selection.loc.start, parent, checkedFragmentSpreads);
3656
- }
3657
- },
3658
- });
3659
- graphql.visit(foundSpread.document, visitor);
3660
- }
3661
- }
3662
- function checkSelections(node, type,
3663
- // Fragment can be placed in separate file
3664
- // Provide actual fragment spread location instead of location in fragment
3665
- loc,
3666
- // Can't access to node.parent in GraphQL AST.Node, so pass as argument
3667
- parent, checkedFragmentSpreads = new Set()) {
3668
- const rawType = getBaseType(type);
3669
- const isObjectType = rawType instanceof graphql.GraphQLObjectType;
3670
- const isInterfaceType = rawType instanceof graphql.GraphQLInterfaceType;
3671
- if (!isObjectType && !isInterfaceType) {
3672
- return;
3673
- }
3674
- const fields = rawType.getFields();
3675
- const hasIdFieldInType = idNames.some(name => fields[name]);
3676
- if (!hasIdFieldInType) {
3677
- return;
3678
- }
3679
- function hasIdField({ selections }) {
3680
- return selections.some(selection => {
3681
- if (selection.kind === graphql.Kind.FIELD) {
3682
- if (selection.alias && idNames.includes(selection.alias.value)) {
3683
- return true;
3684
- }
3685
- return idNames.includes(selection.name.value);
3686
- }
3687
- if (selection.kind === graphql.Kind.INLINE_FRAGMENT) {
3688
- return hasIdField(selection.selectionSet);
3689
- }
3690
- if (selection.kind === graphql.Kind.FRAGMENT_SPREAD) {
3691
- const [foundSpread] = siblings.getFragment(selection.name.value);
3692
- if (foundSpread) {
3693
- const fragmentSpread = foundSpread.document;
3694
- checkedFragmentSpreads.add(fragmentSpread.name.value);
3695
- return hasIdField(fragmentSpread.selectionSet);
3696
- }
3697
- }
3698
- return false;
3699
- });
3700
- }
3701
- const hasId = hasIdField(node);
3702
- checkFragments(node);
3703
- if (hasId) {
3704
- return;
3705
- }
3706
- const pluralSuffix = idNames.length > 1 ? 's' : '';
3707
- const fieldName = englishJoinWords(idNames.map(name => `\`${(parent.alias || parent.name).value}.${name}\``));
3708
- const addition = checkedFragmentSpreads.size === 0
3709
- ? ''
3710
- : ` or add to used fragment${checkedFragmentSpreads.size > 1 ? 's' : ''} ${englishJoinWords([...checkedFragmentSpreads].map(name => `\`${name}\``))}`;
3711
- const problem = {
3712
- loc,
3713
- messageId: RULE_ID$c,
3714
- data: {
3715
- pluralSuffix,
3716
- fieldName,
3717
- addition,
3718
- },
3719
- };
3720
- // Don't provide suggestions for selections in fragments as fragment can be in a separate file
3721
- if ('type' in node) {
3722
- problem.suggest = idNames.map(idName => ({
3723
- desc: `Add \`${idName}\` selection`,
3724
- fix: fixer => fixer.insertTextBefore(node.selections[0], `${idName} `),
3725
- }));
3726
- }
3727
- context.report(problem);
3728
- }
3729
- return {
3730
- [selector](node) {
3731
- const typeInfo = node.typeInfo();
3732
- if (typeInfo.gqlType) {
3733
- checkSelections(node, typeInfo.gqlType, node.loc.start, node.parent);
3734
- }
3735
- },
3736
- };
3737
- },
3738
- };
3739
-
3740
- const RULE_ID$d = 'selection-set-depth';
3741
- const rule$o = {
3742
- meta: {
3743
- type: 'suggestion',
3744
- // eslint-disable-next-line eslint-plugin/require-meta-has-suggestions -- optional since we can't provide fixes for fragments located in separate files
3745
- hasSuggestions: true,
3746
- docs: {
3747
- category: 'Operations',
3748
- description: 'Limit the complexity of the GraphQL operations solely by their depth. Based on [graphql-depth-limit](https://npmjs.com/package/graphql-depth-limit).',
3749
- url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID$d}.md`,
3750
- requiresSiblings: true,
3751
- examples: [
3752
- {
3753
- title: 'Incorrect',
3754
- usage: [{ maxDepth: 1 }],
3755
- code: `
3756
- query deep2 {
3757
- viewer { # Level 0
3758
- albums { # Level 1
3759
- title # Level 2
3760
- }
3761
- }
3762
- }
3763
- `,
3764
- },
3765
- {
3766
- title: 'Correct',
3767
- usage: [{ maxDepth: 4 }],
3768
- code: `
3769
- query deep2 {
3770
- viewer { # Level 0
3771
- albums { # Level 1
3772
- title # Level 2
3773
- }
3774
- }
3775
- }
3776
- `,
3777
- },
3778
- {
3779
- title: 'Correct (ignored field)',
3780
- usage: [{ maxDepth: 1, ignore: ['albums'] }],
3781
- code: `
3782
- query deep2 {
3783
- viewer { # Level 0
3784
- albums { # Level 1
3785
- title # Level 2
3786
- }
3787
- }
3788
- }
3789
- `,
3790
- },
3791
- ],
3792
- recommended: true,
3793
- configOptions: [{ maxDepth: 7 }],
3794
- },
3795
- schema: {
3796
- type: 'array',
3797
- minItems: 1,
3798
- maxItems: 1,
3799
- items: {
3800
- type: 'object',
3801
- additionalProperties: false,
3802
- required: ['maxDepth'],
3803
- properties: {
3804
- maxDepth: {
3805
- type: 'number',
3806
- },
3807
- ignore: ARRAY_DEFAULT_OPTIONS,
3808
- },
3809
- },
3810
- },
3811
- },
3812
- create(context) {
3813
- let siblings = null;
3814
- try {
3815
- siblings = requireSiblingsOperations(RULE_ID$d, context);
3816
- }
3817
- catch (_a) {
3818
- logger.warn(`Rule "${RULE_ID$d}" works best with siblings operations loaded. For more info: https://bit.ly/graphql-eslint-operations`);
3819
- }
3820
- const { maxDepth, ignore = [] } = context.options[0];
3821
- const checkFn = depthLimit(maxDepth, { ignore });
3822
- return {
3823
- 'OperationDefinition, FragmentDefinition'(node) {
3824
- try {
3825
- const rawNode = node.rawNode();
3826
- const fragmentsInUse = siblings ? siblings.getFragmentsInUse(rawNode) : [];
3827
- const document = {
3828
- kind: graphql.Kind.DOCUMENT,
3829
- definitions: [rawNode, ...fragmentsInUse],
3830
- };
3831
- checkFn({
3832
- getDocument: () => document,
3833
- reportError(error) {
3834
- const { line, column } = error.locations[0];
3835
- const ancestors = context.getAncestors();
3836
- const token = ancestors[0].tokens.find(token => token.loc.start.line === line && token.loc.start.column === column - 1);
3837
- context.report({
3838
- loc: {
3839
- line,
3840
- column: column - 1,
3841
- },
3842
- message: error.message,
3843
- // Don't provide suggestions for fragment that can be in a separate file
3844
- ...(token && {
3845
- suggest: [
3846
- {
3847
- desc: 'Remove selections',
3848
- fix(fixer) {
3849
- const sourceCode = context.getSourceCode();
3850
- const foundNode = sourceCode.getNodeByRangeIndex(token.range[0]);
3851
- const parentNode = foundNode.parent.parent;
3852
- return fixer.remove(foundNode.kind === 'Name' ? parentNode.parent : parentNode);
3853
- },
3854
- },
3855
- ],
3856
- }),
3857
- });
3858
- },
3859
- });
3860
- }
3861
- catch (e) {
3862
- logger.warn(`Rule "${RULE_ID$d}" check failed due to a missing siblings operations. For more info: https://bit.ly/graphql-eslint-operations`, e);
3863
- }
3864
- },
3865
- };
3866
- },
3867
- };
3868
-
3869
- const RULE_ID$e = 'strict-id-in-types';
3870
- const rule$p = {
3871
- meta: {
3872
- type: 'suggestion',
3873
- docs: {
3874
- description: 'Requires output types to have one unique identifier unless they do not have a logical one. Exceptions can be used to ignore output types that do not have unique identifiers.',
3875
- category: 'Schema',
3876
- recommended: true,
3877
- url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID$e}.md`,
3878
- requiresSchema: true,
3879
- examples: [
3880
- {
3881
- title: 'Incorrect',
3882
- usage: [
3883
- {
3884
- acceptedIdNames: ['id', '_id'],
3885
- acceptedIdTypes: ['ID'],
3886
- exceptions: { suffixes: ['Payload'] },
3887
- },
3888
- ],
3889
- code: /* GraphQL */ `
3890
- # Incorrect field name
3891
- type InvalidFieldName {
3892
- key: ID!
3893
- }
3894
-
3895
- # Incorrect field type
3896
- type InvalidFieldType {
3897
- id: String!
3898
- }
3899
-
3900
- # Incorrect exception suffix
3901
- type InvalidSuffixResult {
3902
- data: String!
3903
- }
3904
-
3905
- # Too many unique identifiers. Must only contain one.
3906
- type InvalidFieldName {
3907
- id: ID!
3908
- _id: ID!
3909
- }
3910
- `,
3911
- },
3912
- {
3913
- title: 'Correct',
3914
- usage: [
3915
- {
3916
- acceptedIdNames: ['id', '_id'],
3917
- acceptedIdTypes: ['ID'],
3918
- exceptions: { types: ['Error'], suffixes: ['Payload'] },
3919
- },
3920
- ],
3921
- code: /* GraphQL */ `
3922
- type User {
3923
- id: ID!
3924
- }
3925
-
3926
- type Post {
3927
- _id: ID!
3928
- }
3929
-
3930
- type CreateUserPayload {
3931
- data: String!
3932
- }
3933
-
3934
- type Error {
3935
- message: String!
3936
- }
3937
- `,
3938
- },
3939
- ],
3940
- },
3941
- schema: {
3942
- type: 'array',
3943
- maxItems: 1,
3944
- items: {
3945
- type: 'object',
3946
- additionalProperties: false,
3947
- properties: {
3948
- acceptedIdNames: {
3949
- ...ARRAY_DEFAULT_OPTIONS,
3950
- default: ['id'],
3951
- },
3952
- acceptedIdTypes: {
3953
- ...ARRAY_DEFAULT_OPTIONS,
3954
- default: ['ID'],
3955
- },
3956
- exceptions: {
3957
- type: 'object',
3958
- properties: {
3959
- types: {
3960
- ...ARRAY_DEFAULT_OPTIONS,
3961
- description: 'This is used to exclude types with names that match one of the specified values.',
3962
- },
3963
- suffixes: {
3964
- ...ARRAY_DEFAULT_OPTIONS,
3965
- description: 'This is used to exclude types with names with suffixes that match one of the specified values.',
3966
- },
3967
- },
3968
- },
3969
- },
3970
- },
3971
- },
3972
- },
3973
- create(context) {
3974
- const options = {
3975
- acceptedIdNames: ['id'],
3976
- acceptedIdTypes: ['ID'],
3977
- exceptions: {},
3978
- ...context.options[0],
3979
- };
3980
- const schema = requireGraphQLSchemaFromContext(RULE_ID$e, context);
3981
- const rootTypeNames = [schema.getQueryType(), schema.getMutationType(), schema.getSubscriptionType()]
3982
- .filter(Boolean)
3983
- .map(type => type.name);
3984
- const selector = `ObjectTypeDefinition[name.value!=/^(${rootTypeNames.join('|')})$/]`;
3985
- return {
3986
- [selector](node) {
3987
- var _a, _b;
3988
- const typeName = node.name.value;
3989
- const shouldIgnoreNode = ((_a = options.exceptions.types) === null || _a === void 0 ? void 0 : _a.includes(typeName)) ||
3990
- ((_b = options.exceptions.suffixes) === null || _b === void 0 ? void 0 : _b.some(suffix => typeName.endsWith(suffix)));
3991
- if (shouldIgnoreNode) {
3992
- return;
3993
- }
3994
- const validIds = node.fields.filter(field => {
3995
- const fieldNode = field.rawNode();
3996
- const isValidIdName = options.acceptedIdNames.includes(fieldNode.name.value);
3997
- // To be a valid type, it must be non-null and one of the accepted types.
3998
- let isValidIdType = false;
3999
- if (fieldNode.type.kind === graphql.Kind.NON_NULL_TYPE && fieldNode.type.type.kind === graphql.Kind.NAMED_TYPE) {
4000
- isValidIdType = options.acceptedIdTypes.includes(fieldNode.type.type.name.value);
4001
- }
4002
- return isValidIdName && isValidIdType;
4003
- });
4004
- // Usually, there should be only one unique identifier field per type.
4005
- // Some clients allow multiple fields to be used. If more people need this,
4006
- // we can extend this rule later.
4007
- if (validIds.length !== 1) {
4008
- const pluralNamesSuffix = options.acceptedIdNames.length > 1 ? 's' : '';
4009
- const pluralTypesSuffix = options.acceptedIdTypes.length > 1 ? 's' : '';
4010
- context.report({
4011
- node: node.name,
4012
- message: `${typeName} must have exactly one non-nullable unique identifier. Accepted name${pluralNamesSuffix}: ${englishJoinWords(options.acceptedIdNames)}. Accepted type${pluralTypesSuffix}: ${englishJoinWords(options.acceptedIdTypes)}.`,
4013
- });
4014
- }
4015
- },
4016
- };
4017
- },
4018
- };
4019
-
4020
- const RULE_ID$f = 'unique-fragment-name';
4021
- const checkNode = (context, node, ruleId) => {
4022
- const documentName = node.name.value;
4023
- const siblings = requireSiblingsOperations(ruleId, context);
4024
- const siblingDocuments = node.kind === graphql.Kind.FRAGMENT_DEFINITION ? siblings.getFragment(documentName) : siblings.getOperation(documentName);
4025
- const filepath = context.getFilename();
4026
- const conflictingDocuments = siblingDocuments.filter(f => {
4027
- var _a;
4028
- const isSameName = ((_a = f.document.name) === null || _a === void 0 ? void 0 : _a.value) === documentName;
4029
- const isSamePath = normalizePath(f.filePath) === normalizePath(filepath);
4030
- return isSameName && !isSamePath;
4031
- });
4032
- if (conflictingDocuments.length > 0) {
4033
- context.report({
4034
- messageId: ruleId,
4035
- data: {
4036
- documentName,
4037
- summary: conflictingDocuments
4038
- .map(f => `\t${path.relative(process.cwd(), getOnDiskFilepath(f.filePath))}`)
4039
- .join('\n'),
4040
- },
4041
- node: node.name,
4042
- });
4043
- }
4044
- };
4045
- const rule$q = {
4046
- meta: {
4047
- type: 'suggestion',
4048
- docs: {
4049
- category: 'Operations',
4050
- description: 'Enforce unique fragment names across your project.',
4051
- url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID$f}.md`,
4052
- requiresSiblings: true,
4053
- examples: [
4054
- {
4055
- title: 'Incorrect',
4056
- code: /* GraphQL */ `
4057
- # user.fragment.graphql
4058
- fragment UserFields on User {
4059
- id
4060
- name
4061
- fullName
4062
- }
4063
-
4064
- # user-fields.graphql
4065
- fragment UserFields on User {
4066
- id
4067
- }
4068
- `,
4069
- },
4070
- {
4071
- title: 'Correct',
4072
- code: /* GraphQL */ `
4073
- # user.fragment.graphql
4074
- fragment AllUserFields on User {
4075
- id
4076
- name
4077
- fullName
4078
- }
4079
-
4080
- # user-fields.graphql
4081
- fragment UserFields on User {
4082
- id
4083
- }
4084
- `,
4085
- },
4086
- ],
4087
- },
4088
- messages: {
4089
- [RULE_ID$f]: 'Fragment named "{{ documentName }}" already defined in:\n{{ summary }}',
4090
- },
4091
- schema: [],
4092
- },
4093
- create(context) {
4094
- return {
4095
- FragmentDefinition(node) {
4096
- checkNode(context, node, RULE_ID$f);
4097
- },
4098
- };
4099
- },
4100
- };
4101
-
4102
- const RULE_ID$g = 'unique-operation-name';
4103
- const rule$r = {
4104
- meta: {
4105
- type: 'suggestion',
4106
- docs: {
4107
- category: 'Operations',
4108
- description: 'Enforce unique operation names across your project.',
4109
- url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID$g}.md`,
4110
- requiresSiblings: true,
4111
- examples: [
4112
- {
4113
- title: 'Incorrect',
4114
- code: /* GraphQL */ `
4115
- # foo.query.graphql
4116
- query user {
4117
- user {
4118
- id
4119
- }
4120
- }
4121
-
4122
- # bar.query.graphql
4123
- query user {
4124
- me {
4125
- id
4126
- }
4127
- }
4128
- `,
4129
- },
4130
- {
4131
- title: 'Correct',
4132
- code: /* GraphQL */ `
4133
- # foo.query.graphql
4134
- query user {
4135
- user {
4136
- id
4137
- }
4138
- }
4139
-
4140
- # bar.query.graphql
4141
- query me {
4142
- me {
4143
- id
4144
- }
4145
- }
4146
- `,
4147
- },
4148
- ],
4149
- },
4150
- messages: {
4151
- [RULE_ID$g]: 'Operation named "{{ documentName }}" already defined in:\n{{ summary }}',
4152
- },
4153
- schema: [],
4154
- },
4155
- create(context) {
4156
- return {
4157
- 'OperationDefinition[name!=undefined]'(node) {
4158
- checkNode(context, node, RULE_ID$g);
4159
- },
4160
- };
4161
- },
4162
- };
4163
-
4164
- /*
4165
- * 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
4166
- */
4167
- const rules = {
4168
- ...GRAPHQL_JS_VALIDATIONS,
4169
- alphabetize: rule,
4170
- 'description-style': rule$1,
4171
- 'input-name': rule$2,
4172
- 'match-document-filename': rule$3,
4173
- 'naming-convention': rule$4,
4174
- 'no-anonymous-operations': rule$5,
4175
- 'no-case-insensitive-enum-values-duplicates': rule$6,
4176
- 'no-deprecated': rule$7,
4177
- 'no-duplicate-fields': rule$8,
4178
- 'no-hashtag-description': rule$9,
4179
- 'no-root-type': rule$a,
4180
- 'no-scalar-result-type-on-mutation': rule$b,
4181
- 'no-typename-prefix': rule$c,
4182
- 'no-unreachable-types': rule$d,
4183
- 'no-unused-fields': rule$e,
4184
- 'relay-arguments': rule$f,
4185
- 'relay-connection-types': rule$g,
4186
- 'relay-edge-types': rule$h,
4187
- 'relay-page-info': rule$i,
4188
- 'require-deprecation-date': rule$j,
4189
- 'require-deprecation-reason': rule$k,
4190
- 'require-description': rule$l,
4191
- 'require-field-of-type-query-in-mutation-result': rule$m,
4192
- 'require-id-when-available': rule$n,
4193
- 'selection-set-depth': rule$o,
4194
- 'strict-id-in-types': rule$p,
4195
- 'unique-fragment-name': rule$q,
4196
- 'unique-operation-name': rule$r,
4197
- };
4198
-
4199
- const schemaCache = new Map();
4200
- const debug = debugFactory('graphql-eslint:schema');
4201
- function getSchema(projectForFile, options = {}) {
4202
- const schemaKey = utils.asArray(projectForFile.schema).sort().join(',');
4203
- if (!schemaKey) {
4204
- return null;
4205
- }
4206
- if (schemaCache.has(schemaKey)) {
4207
- return schemaCache.get(schemaKey);
4208
- }
4209
- let schema;
4210
- try {
4211
- debug('Loading schema from %o', projectForFile.schema);
4212
- schema = projectForFile.loadSchemaSync(projectForFile.schema, 'GraphQLSchema', options.schemaOptions);
4213
- if (debug.enabled) {
4214
- debug('Schema loaded: %o', schema instanceof graphql.GraphQLSchema);
4215
- const schemaPaths = fastGlob.sync(projectForFile.schema, {
4216
- absolute: true,
4217
- });
4218
- debug('Schema pointers %O', schemaPaths);
4219
- }
4220
- }
4221
- catch (error) {
4222
- error.message = chalk.red(`Error while loading schema: ${error.message}`);
4223
- schema = error;
4224
- }
4225
- schemaCache.set(schemaKey, schema);
4226
- return schema;
4227
- }
4228
-
4229
- const debug$1 = debugFactory('graphql-eslint:operations');
4230
- const handleVirtualPath = (documents) => {
4231
- const filepathMap = Object.create(null);
4232
- return documents.map(source => {
4233
- var _a;
4234
- const { location } = source;
4235
- if (['.gql', '.graphql'].some(extension => location.endsWith(extension))) {
4236
- return source;
4237
- }
4238
- (_a = filepathMap[location]) !== null && _a !== void 0 ? _a : (filepathMap[location] = -1);
4239
- const index = (filepathMap[location] += 1);
4240
- return {
4241
- ...source,
4242
- location: path.resolve(location, `${index}_document.graphql`),
4243
- };
4244
- });
4245
- };
4246
- const operationsCache = new Map();
4247
- const siblingOperationsCache = new Map();
4248
- const getSiblings = (projectForFile) => {
4249
- const documentsKey = utils.asArray(projectForFile.documents).sort().join(',');
4250
- if (!documentsKey) {
4251
- return [];
4252
- }
4253
- let siblings = operationsCache.get(documentsKey);
4254
- if (!siblings) {
4255
- debug$1('Loading operations from %o', projectForFile.documents);
4256
- const documents = projectForFile.loadDocumentsSync(projectForFile.documents, {
4257
- skipGraphQLImport: true,
4258
- });
4259
- if (debug$1.enabled) {
4260
- debug$1('Loaded %d operations', documents.length);
4261
- const operationsPaths = fastGlob.sync(projectForFile.documents, {
4262
- absolute: true,
4263
- });
4264
- debug$1('Operations pointers %O', operationsPaths);
4265
- }
4266
- siblings = handleVirtualPath(documents);
4267
- operationsCache.set(documentsKey, siblings);
4268
- }
4269
- return siblings;
4270
- };
4271
- function getSiblingOperations(projectForFile) {
4272
- const siblings = getSiblings(projectForFile);
4273
- if (siblings.length === 0) {
4274
- let printed = false;
4275
- const noopWarn = () => {
4276
- if (!printed) {
4277
- logger.warn('getSiblingOperations was called without any operations. Make sure to set "parserOptions.operations" to make this feature available!');
4278
- printed = true;
4279
- }
4280
- return [];
4281
- };
4282
- return {
4283
- available: false,
4284
- getFragment: noopWarn,
4285
- getFragments: noopWarn,
4286
- getFragmentByType: noopWarn,
4287
- getFragmentsInUse: noopWarn,
4288
- getOperation: noopWarn,
4289
- getOperations: noopWarn,
4290
- getOperationByType: noopWarn,
4291
- };
4292
- }
4293
- // Since the siblings array is cached, we can use it as cache key.
4294
- // We should get the same array reference each time we get
4295
- // to this point for the same graphql project
4296
- if (siblingOperationsCache.has(siblings)) {
4297
- return siblingOperationsCache.get(siblings);
4298
- }
4299
- let fragmentsCache = null;
4300
- const getFragments = () => {
4301
- if (fragmentsCache === null) {
4302
- const result = [];
4303
- for (const source of siblings) {
4304
- for (const definition of source.document.definitions) {
4305
- if (definition.kind === graphql.Kind.FRAGMENT_DEFINITION) {
4306
- result.push({
4307
- filePath: source.location,
4308
- document: definition,
4309
- });
4310
- }
4311
- }
4312
- }
4313
- fragmentsCache = result;
4314
- }
4315
- return fragmentsCache;
4316
- };
4317
- let cachedOperations = null;
4318
- const getOperations = () => {
4319
- if (cachedOperations === null) {
4320
- const result = [];
4321
- for (const source of siblings) {
4322
- for (const definition of source.document.definitions) {
4323
- if (definition.kind === graphql.Kind.OPERATION_DEFINITION) {
4324
- result.push({
4325
- filePath: source.location,
4326
- document: definition,
4327
- });
4328
- }
4329
- }
4330
- }
4331
- cachedOperations = result;
4332
- }
4333
- return cachedOperations;
4334
- };
4335
- const getFragment = (name) => getFragments().filter(f => { var _a; return ((_a = f.document.name) === null || _a === void 0 ? void 0 : _a.value) === name; });
4336
- const collectFragments = (selectable, recursive, collected = new Map()) => {
4337
- graphql.visit(selectable, {
4338
- FragmentSpread(spread) {
4339
- const fragmentName = spread.name.value;
4340
- const [fragment] = getFragment(fragmentName);
4341
- if (!fragment) {
4342
- logger.warn(`Unable to locate fragment named "${fragmentName}", please make sure it's loaded using "parserOptions.operations"`);
4343
- return;
4344
- }
4345
- if (!collected.has(fragmentName)) {
4346
- collected.set(fragmentName, fragment.document);
4347
- if (recursive) {
4348
- collectFragments(fragment.document, recursive, collected);
4349
- }
4350
- }
4351
- },
4352
- });
4353
- return collected;
4354
- };
4355
- const siblingOperations = {
4356
- available: true,
4357
- getFragment,
4358
- getFragments,
4359
- getFragmentByType: typeName => getFragments().filter(f => { var _a, _b; return ((_b = (_a = f.document.typeCondition) === null || _a === void 0 ? void 0 : _a.name) === null || _b === void 0 ? void 0 : _b.value) === typeName; }),
4360
- getFragmentsInUse: (selectable, recursive = true) => Array.from(collectFragments(selectable, recursive).values()),
4361
- getOperation: name => getOperations().filter(o => { var _a; return ((_a = o.document.name) === null || _a === void 0 ? void 0 : _a.value) === name; }),
4362
- getOperations,
4363
- getOperationByType: type => getOperations().filter(o => o.document.operation === type),
4364
- };
4365
- siblingOperationsCache.set(siblings, siblingOperations);
4366
- return siblingOperations;
4367
- }
4368
-
4369
- const debug$2 = debugFactory('graphql-eslint:graphql-config');
4370
- let graphQLConfig;
4371
- function loadGraphQLConfig(options) {
4372
- // We don't want cache config on test environment
4373
- // Otherwise schema and documents will be same for all tests
4374
- if (process.env.NODE_ENV !== 'test' && graphQLConfig) {
4375
- return graphQLConfig;
4376
- }
4377
- const onDiskConfig = options.skipGraphQLConfig
4378
- ? null
4379
- : graphqlConfig.loadConfigSync({
4380
- // load config relative to the file being linted
4381
- rootDir: options.filePath ? path.dirname(options.filePath) : undefined,
4382
- throwOnEmpty: false,
4383
- throwOnMissing: false,
4384
- extensions: [addCodeFileLoaderExtension],
4385
- });
4386
- debug$2('options.skipGraphQLConfig: %o', options.skipGraphQLConfig);
4387
- if (onDiskConfig) {
4388
- debug$2('Graphql-config path %o', onDiskConfig.filepath);
4389
- }
4390
- const configOptions = options.projects
4391
- ? { projects: options.projects }
4392
- : {
4393
- schema: (options.schema || ''),
4394
- documents: options.documents || options.operations,
4395
- extensions: options.extensions,
4396
- include: options.include,
4397
- exclude: options.exclude,
4398
- };
4399
- graphQLConfig =
4400
- onDiskConfig ||
4401
- new graphqlConfig.GraphQLConfig({
4402
- config: configOptions,
4403
- filepath: 'virtual-config',
4404
- }, [addCodeFileLoaderExtension]);
4405
- return graphQLConfig;
4406
- }
4407
- const addCodeFileLoaderExtension = api => {
4408
- api.loaders.schema.register(new codeFileLoader.CodeFileLoader());
4409
- api.loaders.documents.register(new codeFileLoader.CodeFileLoader());
4410
- return { name: 'graphql-eslint-loaders' };
4411
- };
4412
-
4413
- const debug$3 = debugFactory('graphql-eslint:parser');
4414
- debug$3('cwd %o', process.cwd());
4415
- function parseForESLint(code, options = {}) {
4416
- try {
4417
- const filePath = options.filePath || '';
4418
- const realFilepath = filePath && getOnDiskFilepath(filePath);
4419
- const gqlConfig = loadGraphQLConfig(options);
4420
- const projectForFile = realFilepath ? gqlConfig.getProjectForFile(realFilepath) : gqlConfig.getDefault();
4421
- const schema = getSchema(projectForFile, options);
4422
- const siblingOperations = getSiblingOperations(projectForFile);
4423
- const { document } = utils.parseGraphQLSDL(filePath, code, {
4424
- ...options.graphQLParserOptions,
4425
- noLocation: false,
4426
- });
4427
- const comments = extractComments(document.loc);
4428
- const tokens = extractTokens(filePath, code);
4429
- const rootTree = convertToESTree(document, schema instanceof graphql.GraphQLSchema ? schema : null);
4430
- return {
4431
- services: {
4432
- schema,
4433
- siblingOperations,
4434
- },
4435
- ast: {
4436
- comments,
4437
- tokens,
4438
- loc: rootTree.loc,
4439
- range: rootTree.range,
4440
- type: 'Program',
4441
- sourceType: 'script',
4442
- body: [rootTree],
4443
- },
4444
- };
4445
- }
4446
- catch (error) {
4447
- error.message = `[graphql-eslint] ${error.message}`;
4448
- // In case of GraphQL parser error, we report it to ESLint as a parser error that matches the requirements
4449
- // of ESLint. This will make sure to display it correctly in IDEs and lint results.
4450
- if (error instanceof graphql.GraphQLError) {
4451
- const eslintError = {
4452
- index: error.positions[0],
4453
- lineNumber: error.locations[0].line,
4454
- column: error.locations[0].column - 1,
4455
- message: error.message,
4456
- };
4457
- throw eslintError;
4458
- }
4459
- throw error;
4460
- }
4461
- }
4462
-
4463
- /* eslint-env jest */
4464
- function indentCode(code, indent = 4) {
4465
- return code.replace(/^/gm, ' '.repeat(indent));
4466
- }
4467
- // A simple version of `SourceCodeFixer.applyFixes`
4468
- // https://github.com/eslint/eslint/issues/14936#issuecomment-906746754
4469
- function applyFix(code, { range, text }) {
4470
- return [code.slice(0, range[0]), text, code.slice(range[1])].join('');
4471
- }
4472
- class GraphQLRuleTester extends eslint.RuleTester {
4473
- constructor(parserOptions = {}) {
4474
- const config = {
4475
- parser: require.resolve('@graphql-eslint/eslint-plugin'),
4476
- parserOptions: {
4477
- ...parserOptions,
4478
- skipGraphQLConfig: true,
4479
- },
4480
- };
4481
- super(config);
4482
- this.config = config;
4483
- }
4484
- fromMockFile(path$1) {
4485
- return fs.readFileSync(path.resolve(__dirname, `../tests/mocks/${path$1}`), 'utf-8');
4486
- }
4487
- runGraphQLTests(ruleId, rule, tests) {
4488
- const ruleTests = eslint.Linter.version.startsWith('8')
4489
- ? tests
4490
- : {
4491
- valid: tests.valid.map(test => {
4492
- if (typeof test === 'string') {
4493
- return test;
4494
- }
4495
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
4496
- const { name, ...testCaseOptions } = test;
4497
- return testCaseOptions;
4498
- }),
4499
- invalid: tests.invalid.map(test => {
4500
- // ESLint 7 throws an error on CI - Unexpected top-level property "name"
4501
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
4502
- const { name, ...testCaseOptions } = test;
4503
- return testCaseOptions;
4504
- }),
4505
- };
4506
- super.run(ruleId, rule, ruleTests);
4507
- const linter = new eslint.Linter();
4508
- linter.defineRule(ruleId, rule);
4509
- const hasOnlyTest = [...tests.valid, ...tests.invalid].some(t => typeof t !== 'string' && t.only);
4510
- // for (const [index, testCase] of tests.valid.entries()) {
4511
- // const { name, code, filename, only }: RuleTester.ValidTestCase =
4512
- // typeof testCase === 'string' ? { code: testCase } : testCase;
4513
- //
4514
- // if (hasOnlyTest && !only) {
4515
- // continue;
4516
- // }
4517
- //
4518
- // const verifyConfig = getVerifyConfig(ruleId, this.config, testCase);
4519
- // defineParser(linter, verifyConfig.parser);
4520
- //
4521
- // const messages = linter.verify(code, verifyConfig, { filename });
4522
- // const codeFrame = printCode(code, { line: 0, column: 0 });
4523
- //
4524
- // it(name || `Valid #${index + 1}\n${codeFrame}`, () => {
4525
- // expect(messages).toEqual([]);
4526
- // });
4527
- // }
4528
- for (const [idx, testCase] of tests.invalid.entries()) {
4529
- const { only, filename, options, name } = testCase;
4530
- if (hasOnlyTest && !only) {
4531
- continue;
4532
- }
4533
- const code = removeTrailingBlankLines(testCase.code);
4534
- const verifyConfig = getVerifyConfig(ruleId, this.config, testCase);
4535
- defineParser(linter, verifyConfig.parser);
4536
- const messages = linter.verify(code, verifyConfig, filename);
4537
- if (messages.length === 0) {
4538
- throw new Error('Invalid case should have at least one error.');
4539
- }
4540
- const codeFrame = indentCode(printCode(code, { line: 0, column: 0 }));
4541
- const messageForSnapshot = ['#### ⌨️ Code', codeFrame];
4542
- if (options) {
4543
- const opts = JSON.stringify(options, null, 2).slice(1, -1);
4544
- messageForSnapshot.push('#### ⚙️ Options', indentCode(removeTrailingBlankLines(opts), 2));
4545
- }
4546
- for (const [index, message] of messages.entries()) {
4547
- if (message.fatal) {
4548
- throw new Error(message.message);
4549
- }
4550
- const codeWithMessage = printCode(code, message, 1);
4551
- messageForSnapshot.push(printWithIndex('#### ❌ Error', index, messages.length), indentCode(codeWithMessage));
4552
- const { suggestions } = message;
4553
- // Don't print suggestions in snapshots for too big codes
4554
- if (suggestions && (code.match(/\n/g) || '').length < 1000) {
4555
- for (const [i, suggestion] of message.suggestions.entries()) {
4556
- const title = printWithIndex('#### 💡 Suggestion', i, suggestions.length, suggestion.desc);
4557
- const output = applyFix(code, suggestion.fix);
4558
- const codeFrame = printCode(output, { line: 0, column: 0 });
4559
- messageForSnapshot.push(title, indentCode(codeFrame, 2));
4560
- }
4561
- }
4562
- }
4563
- if (rule.meta.fixable) {
4564
- const { fixed, output } = linter.verifyAndFix(code, verifyConfig, filename);
4565
- if (fixed) {
4566
- messageForSnapshot.push('#### 🔧 Autofix output', indentCode(printCode(output)));
4567
- }
4568
- }
4569
- it(name || `Invalid #${idx + 1}`, () => {
4570
- expect(messageForSnapshot.join('\n\n')).toMatchSnapshot();
4571
- });
4572
- }
4573
- }
4574
- }
4575
- function removeTrailingBlankLines(text) {
4576
- return text.replace(/^\s*\n/, '').trimEnd();
4577
- }
4578
- function printWithIndex(title, index, total, description) {
4579
- if (total > 1) {
4580
- title += ` ${index + 1}/${total}`;
4581
- }
4582
- if (description) {
4583
- title += `: ${description}`;
4584
- }
4585
- return title;
4586
- }
4587
- function getVerifyConfig(ruleId, testerConfig, testCase) {
4588
- const { parser = testerConfig.parser, parserOptions, options } = testCase;
4589
- return {
4590
- ...testerConfig,
4591
- parser,
4592
- parserOptions: {
4593
- ...testerConfig.parserOptions,
4594
- ...parserOptions,
4595
- },
4596
- rules: {
4597
- [ruleId]: Array.isArray(options) ? ['error', ...options] : 'error',
4598
- },
4599
- };
4600
- }
4601
- const parsers = new WeakMap();
4602
- function defineParser(linter, parser) {
4603
- if (!parser) {
4604
- return;
4605
- }
4606
- if (!parsers.has(linter)) {
4607
- parsers.set(linter, new Set());
4608
- }
4609
- const defined = parsers.get(linter);
4610
- if (!defined.has(parser)) {
4611
- defined.add(parser);
4612
- linter.defineParser(parser, require(parser));
4613
- }
4614
- }
4615
- function printCode(code, result = {}, linesOffset = Number.POSITIVE_INFINITY) {
4616
- const { line, column, endLine, endColumn, message } = result;
4617
- const location = {};
4618
- if (typeof line === 'number' && typeof column === 'number') {
4619
- location.start = {
4620
- line,
4621
- column,
4622
- };
4623
- }
4624
- if (typeof endLine === 'number' && typeof endColumn === 'number') {
4625
- location.end = {
4626
- line: endLine,
4627
- column: endColumn,
4628
- };
4629
- }
4630
- return codeFrame.codeFrameColumns(code, location, {
4631
- linesAbove: linesOffset,
4632
- linesBelow: linesOffset,
4633
- message,
4634
- });
4635
- }
4636
-
4637
- const processors = { graphql: processor };
4638
- const configs = Object.fromEntries([
4639
- // Configs to extend from `configs` directory
4640
- 'schema-recommended',
4641
- 'schema-all',
4642
- 'operations-recommended',
4643
- 'operations-all',
4644
- 'relay',
4645
- ].map(configName => [configName, { extends: `./configs/${configName}.json` }]));
4646
-
4647
- exports.GraphQLRuleTester = GraphQLRuleTester;
4648
- exports.configs = configs;
4649
- exports.parseForESLint = parseForESLint;
4650
- exports.processors = processors;
4651
- exports.requireGraphQLSchemaFromContext = requireGraphQLSchemaFromContext;
4652
- exports.requireSiblingsOperations = requireSiblingsOperations;
4653
- exports.rules = rules;