@graphql-eslint/eslint-plugin 3.14.0-alpha-20221221142641-4e1a924 → 3.14.0-alpha-20221222124206-b82954b

Sign up to get free protection for your applications and to get access to all the features.
Files changed (231) hide show
  1. package/README.md +309 -0
  2. package/cjs/cache.js +30 -0
  3. package/cjs/configs/base.js +7 -0
  4. package/cjs/configs/index.js +16 -0
  5. package/cjs/configs/operations-all.js +31 -0
  6. package/cjs/configs/operations-recommended.js +56 -0
  7. package/cjs/configs/relay.js +12 -0
  8. package/cjs/configs/schema-all.js +23 -0
  9. package/cjs/configs/schema-recommended.js +52 -0
  10. package/cjs/documents.js +149 -0
  11. package/cjs/estree-converter/converter.js +62 -0
  12. package/cjs/estree-converter/index.js +6 -0
  13. package/cjs/estree-converter/types.js +2 -0
  14. package/cjs/estree-converter/utils.js +109 -0
  15. package/cjs/graphql-config.js +55 -0
  16. package/cjs/index.js +17 -0
  17. package/cjs/parser.js +61 -0
  18. package/cjs/processor.js +78 -0
  19. package/cjs/rules/alphabetize.js +348 -0
  20. package/cjs/rules/description-style.js +78 -0
  21. package/cjs/rules/graphql-js-validation.js +499 -0
  22. package/cjs/rules/index.js +68 -0
  23. package/cjs/rules/input-name.js +136 -0
  24. package/cjs/rules/lone-executable-definition.js +88 -0
  25. package/cjs/rules/match-document-filename.js +235 -0
  26. package/cjs/rules/naming-convention.js +310 -0
  27. package/cjs/rules/no-anonymous-operations.js +67 -0
  28. package/cjs/rules/no-case-insensitive-enum-values-duplicates.js +61 -0
  29. package/cjs/rules/no-deprecated.js +124 -0
  30. package/cjs/rules/no-duplicate-fields.js +112 -0
  31. package/cjs/rules/no-hashtag-description.js +89 -0
  32. package/cjs/rules/no-root-type.js +86 -0
  33. package/cjs/rules/no-scalar-result-type-on-mutation.js +66 -0
  34. package/cjs/rules/no-typename-prefix.js +65 -0
  35. package/cjs/rules/no-unreachable-types.js +158 -0
  36. package/cjs/rules/no-unused-fields.js +130 -0
  37. package/cjs/rules/relay-arguments.js +121 -0
  38. package/cjs/rules/relay-connection-types.js +107 -0
  39. package/cjs/rules/relay-edge-types.js +189 -0
  40. package/cjs/rules/relay-page-info.js +100 -0
  41. package/cjs/rules/require-deprecation-date.js +123 -0
  42. package/cjs/rules/require-deprecation-reason.js +56 -0
  43. package/cjs/rules/require-description.js +193 -0
  44. package/cjs/rules/require-field-of-type-query-in-mutation-result.js +72 -0
  45. package/cjs/rules/require-id-when-available.js +199 -0
  46. package/cjs/rules/selection-set-depth.js +135 -0
  47. package/cjs/rules/strict-id-in-types.js +162 -0
  48. package/cjs/rules/unique-fragment-name.js +90 -0
  49. package/cjs/rules/unique-operation-name.js +65 -0
  50. package/cjs/schema.js +42 -0
  51. package/cjs/testkit.js +183 -0
  52. package/cjs/types.js +2 -0
  53. package/cjs/utils.js +96 -0
  54. package/docs/README.md +82 -0
  55. package/docs/custom-rules.md +184 -0
  56. package/docs/deprecated-rules.md +24 -0
  57. package/docs/parser-options.md +95 -0
  58. package/docs/parser.md +67 -0
  59. package/docs/rules/alphabetize.md +194 -0
  60. package/docs/rules/description-style.md +57 -0
  61. package/docs/rules/executable-definitions.md +20 -0
  62. package/docs/rules/fields-on-correct-type.md +23 -0
  63. package/docs/rules/fragments-on-composite-type.md +20 -0
  64. package/docs/rules/input-name.md +80 -0
  65. package/docs/rules/known-argument-names.md +23 -0
  66. package/docs/rules/known-directives.md +48 -0
  67. package/docs/rules/known-fragment-names.md +72 -0
  68. package/docs/rules/known-type-names.md +24 -0
  69. package/docs/rules/lone-anonymous-operation.md +20 -0
  70. package/docs/rules/lone-executable-definition.md +59 -0
  71. package/docs/rules/lone-schema-definition.md +19 -0
  72. package/docs/rules/match-document-filename.md +181 -0
  73. package/docs/rules/naming-convention.md +320 -0
  74. package/docs/rules/no-anonymous-operations.md +43 -0
  75. package/docs/rules/no-case-insensitive-enum-values-duplicates.md +46 -0
  76. package/docs/rules/no-deprecated.md +88 -0
  77. package/docs/rules/no-duplicate-fields.md +69 -0
  78. package/docs/rules/no-fragment-cycles.md +19 -0
  79. package/docs/rules/no-hashtag-description.md +62 -0
  80. package/docs/rules/no-root-type.md +55 -0
  81. package/docs/rules/no-scalar-result-type-on-mutation.md +39 -0
  82. package/docs/rules/no-typename-prefix.md +42 -0
  83. package/docs/rules/no-undefined-variables.md +20 -0
  84. package/docs/rules/no-unreachable-types.md +52 -0
  85. package/docs/rules/no-unused-fields.md +64 -0
  86. package/docs/rules/no-unused-fragments.md +20 -0
  87. package/docs/rules/no-unused-variables.md +20 -0
  88. package/docs/rules/one-field-subscriptions.md +19 -0
  89. package/docs/rules/overlapping-fields-can-be-merged.md +20 -0
  90. package/docs/rules/possible-fragment-spread.md +21 -0
  91. package/docs/rules/possible-type-extension.md +19 -0
  92. package/docs/rules/provided-required-arguments.md +21 -0
  93. package/docs/rules/relay-arguments.md +59 -0
  94. package/docs/rules/relay-connection-types.md +43 -0
  95. package/docs/rules/relay-edge-types.md +60 -0
  96. package/docs/rules/relay-page-info.md +34 -0
  97. package/docs/rules/require-deprecation-date.md +59 -0
  98. package/docs/rules/require-deprecation-reason.md +49 -0
  99. package/docs/rules/require-description.md +147 -0
  100. package/docs/rules/require-field-of-type-query-in-mutation-result.md +50 -0
  101. package/docs/rules/require-id-when-available.md +91 -0
  102. package/docs/rules/scalar-leafs.md +23 -0
  103. package/docs/rules/selection-set-depth.md +86 -0
  104. package/docs/rules/strict-id-in-types.md +129 -0
  105. package/docs/rules/unique-argument-names.md +19 -0
  106. package/docs/rules/unique-directive-names-per-location.md +21 -0
  107. package/docs/rules/unique-directive-names.md +19 -0
  108. package/docs/rules/unique-enum-value-names.md +16 -0
  109. package/docs/rules/unique-field-definition-names.md +19 -0
  110. package/docs/rules/unique-fragment-name.md +52 -0
  111. package/docs/rules/unique-input-field-names.md +19 -0
  112. package/docs/rules/unique-operation-name.md +56 -0
  113. package/docs/rules/unique-operation-types.md +19 -0
  114. package/docs/rules/unique-type-names.md +19 -0
  115. package/docs/rules/unique-variable-names.md +19 -0
  116. package/docs/rules/value-literals-of-correct-type.md +22 -0
  117. package/docs/rules/variables-are-input-types.md +20 -0
  118. package/docs/rules/variables-in-allowed-position.md +19 -0
  119. package/package.json +8 -11
  120. package/{cache.d.ts → typings/cache.d.cts} +0 -0
  121. package/typings/cache.d.ts +10 -0
  122. package/typings/configs/base.d.cts +5 -0
  123. package/typings/configs/base.d.ts +5 -0
  124. package/typings/configs/index.d.cts +139 -0
  125. package/typings/configs/index.d.ts +139 -0
  126. package/typings/configs/operations-all.d.cts +20 -0
  127. package/typings/configs/operations-all.d.ts +20 -0
  128. package/typings/configs/operations-recommended.d.cts +50 -0
  129. package/typings/configs/operations-recommended.d.ts +50 -0
  130. package/typings/configs/relay.d.cts +10 -0
  131. package/typings/configs/relay.d.ts +10 -0
  132. package/typings/configs/schema-all.d.cts +15 -0
  133. package/typings/configs/schema-all.d.ts +15 -0
  134. package/typings/configs/schema-recommended.d.cts +47 -0
  135. package/typings/configs/schema-recommended.d.ts +47 -0
  136. package/{documents.d.ts → typings/documents.d.cts} +0 -0
  137. package/typings/documents.d.ts +21 -0
  138. package/{estree-converter/converter.d.ts → typings/estree-converter/converter.d.cts} +0 -0
  139. package/typings/estree-converter/converter.d.ts +3 -0
  140. package/{estree-converter/index.d.ts → typings/estree-converter/index.d.cts} +0 -0
  141. package/typings/estree-converter/index.d.ts +3 -0
  142. package/{estree-converter/types.d.ts → typings/estree-converter/types.d.cts} +0 -0
  143. package/typings/estree-converter/types.d.ts +40 -0
  144. package/{estree-converter/utils.d.ts → typings/estree-converter/utils.d.cts} +0 -0
  145. package/typings/estree-converter/utils.d.ts +13 -0
  146. package/{graphql-config.d.ts → typings/graphql-config.d.cts} +0 -0
  147. package/typings/graphql-config.d.ts +4 -0
  148. package/typings/index.d.cts +9 -0
  149. package/{index.d.ts → typings/index.d.ts} +1 -5
  150. package/{parser.d.ts → typings/parser.d.cts} +0 -0
  151. package/typings/parser.d.ts +2 -0
  152. package/{processor.d.ts → typings/processor.d.cts} +0 -0
  153. package/typings/processor.d.ts +6 -0
  154. package/{rules/alphabetize.d.ts → typings/rules/alphabetize.d.cts} +0 -0
  155. package/typings/rules/alphabetize.d.ts +76 -0
  156. package/{rules/description-style.d.ts → typings/rules/description-style.d.cts} +0 -0
  157. package/typings/rules/description-style.d.ts +20 -0
  158. package/{rules/graphql-js-validation.d.ts → typings/rules/graphql-js-validation.d.cts} +0 -0
  159. package/typings/rules/graphql-js-validation.d.ts +2 -0
  160. package/{rules/index.d.ts → typings/rules/index.d.cts} +0 -0
  161. package/typings/rules/index.d.ts +104 -0
  162. package/{rules/input-name.d.ts → typings/rules/input-name.d.cts} +0 -0
  163. package/typings/rules/input-name.d.ts +35 -0
  164. package/{rules/lone-executable-definition.d.ts → typings/rules/lone-executable-definition.d.cts} +0 -0
  165. package/typings/rules/lone-executable-definition.d.ts +26 -0
  166. package/{rules/match-document-filename.d.ts → typings/rules/match-document-filename.d.cts} +0 -0
  167. package/typings/rules/match-document-filename.d.ts +72 -0
  168. package/{rules/naming-convention.d.ts → typings/rules/naming-convention.d.cts} +0 -0
  169. package/typings/rules/naming-convention.d.ts +83 -0
  170. package/{rules/no-anonymous-operations.d.ts → typings/rules/no-anonymous-operations.d.cts} +0 -0
  171. package/{rules/no-case-insensitive-enum-values-duplicates.d.ts → typings/rules/no-anonymous-operations.d.ts} +0 -0
  172. package/{rules/no-hashtag-description.d.ts → typings/rules/no-case-insensitive-enum-values-duplicates.d.cts} +0 -0
  173. package/{rules/no-scalar-result-type-on-mutation.d.ts → typings/rules/no-case-insensitive-enum-values-duplicates.d.ts} +0 -0
  174. package/{rules/no-deprecated.d.ts → typings/rules/no-deprecated.d.cts} +0 -0
  175. package/typings/rules/no-deprecated.d.ts +2 -0
  176. package/{rules/no-duplicate-fields.d.ts → typings/rules/no-duplicate-fields.d.cts} +0 -0
  177. package/{rules/relay-page-info.d.ts → typings/rules/no-duplicate-fields.d.ts} +0 -0
  178. package/{rules/no-typename-prefix.d.ts → typings/rules/no-hashtag-description.d.cts} +0 -0
  179. package/{rules/no-unreachable-types.d.ts → typings/rules/no-hashtag-description.d.ts} +0 -0
  180. package/{rules/no-root-type.d.ts → typings/rules/no-root-type.d.cts} +0 -0
  181. package/typings/rules/no-root-type.d.ts +25 -0
  182. package/{rules/no-unused-fields.d.ts → typings/rules/no-scalar-result-type-on-mutation.d.cts} +0 -0
  183. package/{rules/unique-operation-name.d.ts → typings/rules/no-scalar-result-type-on-mutation.d.ts} +0 -0
  184. package/typings/rules/no-typename-prefix.d.cts +2 -0
  185. package/typings/rules/no-typename-prefix.d.ts +2 -0
  186. package/typings/rules/no-unreachable-types.d.cts +2 -0
  187. package/typings/rules/no-unreachable-types.d.ts +2 -0
  188. package/typings/rules/no-unused-fields.d.cts +2 -0
  189. package/typings/rules/no-unused-fields.d.ts +2 -0
  190. package/{rules/relay-arguments.d.ts → typings/rules/relay-arguments.d.cts} +0 -0
  191. package/typings/rules/relay-arguments.d.ts +21 -0
  192. package/{rules/relay-connection-types.d.ts → typings/rules/relay-connection-types.d.cts} +0 -0
  193. package/typings/rules/relay-connection-types.d.ts +4 -0
  194. package/{rules/relay-edge-types.d.ts → typings/rules/relay-edge-types.d.cts} +0 -0
  195. package/typings/rules/relay-edge-types.d.ts +31 -0
  196. package/{rules/require-deprecation-reason.d.ts → typings/rules/relay-page-info.d.cts} +0 -0
  197. package/{rules/require-field-of-type-query-in-mutation-result.d.ts → typings/rules/relay-page-info.d.ts} +0 -0
  198. package/{rules/require-deprecation-date.d.ts → typings/rules/require-deprecation-date.d.cts} +0 -0
  199. package/typings/rules/require-deprecation-date.d.ts +18 -0
  200. package/typings/rules/require-deprecation-reason.d.cts +2 -0
  201. package/typings/rules/require-deprecation-reason.d.ts +2 -0
  202. package/{rules/require-description.d.ts → typings/rules/require-description.d.cts} +0 -0
  203. package/typings/rules/require-description.d.ts +14 -0
  204. package/typings/rules/require-field-of-type-query-in-mutation-result.d.cts +2 -0
  205. package/typings/rules/require-field-of-type-query-in-mutation-result.d.ts +2 -0
  206. package/{rules/require-id-when-available.d.ts → typings/rules/require-id-when-available.d.cts} +0 -0
  207. package/typings/rules/require-id-when-available.d.ts +36 -0
  208. package/{rules/selection-set-depth.d.ts → typings/rules/selection-set-depth.d.cts} +0 -0
  209. package/typings/rules/selection-set-depth.d.ts +28 -0
  210. package/{rules/strict-id-in-types.d.ts → typings/rules/strict-id-in-types.d.cts} +0 -0
  211. package/typings/rules/strict-id-in-types.d.ts +57 -0
  212. package/{rules/unique-fragment-name.d.ts → typings/rules/unique-fragment-name.d.cts} +0 -0
  213. package/typings/rules/unique-fragment-name.d.ts +5 -0
  214. package/typings/rules/unique-operation-name.d.cts +2 -0
  215. package/typings/rules/unique-operation-name.d.ts +2 -0
  216. package/{schema.d.ts → typings/schema.d.cts} +0 -0
  217. package/typings/schema.d.ts +3 -0
  218. package/{testkit.d.ts → typings/testkit.d.cts} +0 -0
  219. package/typings/testkit.d.ts +27 -0
  220. package/{types.d.ts → typings/types.d.cts} +0 -0
  221. package/typings/types.d.ts +81 -0
  222. package/{utils.d.ts → typings/utils.d.cts} +0 -0
  223. package/typings/utils.d.ts +34 -0
  224. package/configs/base.json +0 -4
  225. package/configs/operations-all.json +0 -25
  226. package/configs/operations-recommended.json +0 -50
  227. package/configs/relay.json +0 -9
  228. package/configs/schema-all.json +0 -17
  229. package/configs/schema-recommended.json +0 -49
  230. package/index.js +0 -4995
  231. package/index.mjs +0 -4983
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rule = void 0;
4
+ const graphql_1 = require("graphql");
5
+ const utils_1 = require("../utils");
6
+ const RULE_ID = 'relay-arguments';
7
+ const MISSING_ARGUMENTS = 'MISSING_ARGUMENTS';
8
+ const schema = {
9
+ type: 'array',
10
+ maxItems: 1,
11
+ items: {
12
+ type: 'object',
13
+ additionalProperties: false,
14
+ minProperties: 1,
15
+ properties: {
16
+ includeBoth: {
17
+ type: 'boolean',
18
+ default: true,
19
+ description: 'Enforce including both forward and backward pagination arguments',
20
+ },
21
+ },
22
+ },
23
+ };
24
+ exports.rule = {
25
+ meta: {
26
+ type: 'problem',
27
+ docs: {
28
+ category: 'Schema',
29
+ description: [
30
+ 'Set of rules to follow Relay specification for Arguments.',
31
+ '',
32
+ '- A field that returns a Connection type must include forward pagination arguments (`first` and `after`), backward pagination arguments (`last` and `before`), or both',
33
+ '',
34
+ 'Forward pagination arguments',
35
+ '',
36
+ '- `first` takes a non-negative integer',
37
+ '- `after` takes the Cursor type',
38
+ '',
39
+ 'Backward pagination arguments',
40
+ '',
41
+ '- `last` takes a non-negative integer',
42
+ '- `before` takes the Cursor type',
43
+ ].join('\n'),
44
+ url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
45
+ examples: [
46
+ {
47
+ title: 'Incorrect',
48
+ code: /* GraphQL */ `
49
+ type User {
50
+ posts: PostConnection
51
+ }
52
+ `,
53
+ },
54
+ {
55
+ title: 'Correct',
56
+ code: /* GraphQL */ `
57
+ type User {
58
+ posts(after: String, first: Int, before: String, last: Int): PostConnection
59
+ }
60
+ `,
61
+ },
62
+ ],
63
+ isDisabledForAllConfig: true,
64
+ },
65
+ messages: {
66
+ [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.',
67
+ },
68
+ schema,
69
+ },
70
+ create(context) {
71
+ const schema = (0, utils_1.requireGraphQLSchemaFromContext)(RULE_ID, context);
72
+ const { includeBoth = true } = context.options[0] || {};
73
+ return {
74
+ 'FieldDefinition > .gqlType Name[value=/Connection$/]'(node) {
75
+ let fieldNode = node.parent;
76
+ while (fieldNode.kind !== graphql_1.Kind.FIELD_DEFINITION) {
77
+ fieldNode = fieldNode.parent;
78
+ }
79
+ const args = Object.fromEntries(fieldNode.arguments.map(argument => [argument.name.value, argument]));
80
+ const hasForwardPagination = Boolean(args.first && args.after);
81
+ const hasBackwardPagination = Boolean(args.last && args.before);
82
+ if (!hasForwardPagination && !hasBackwardPagination) {
83
+ context.report({
84
+ node: fieldNode.name,
85
+ messageId: MISSING_ARGUMENTS,
86
+ });
87
+ return;
88
+ }
89
+ function checkField(typeName, argumentName) {
90
+ const argument = args[argumentName];
91
+ const hasArgument = Boolean(argument);
92
+ let type = argument;
93
+ if (hasArgument && type.gqlType.kind === graphql_1.Kind.NON_NULL_TYPE) {
94
+ type = type.gqlType;
95
+ }
96
+ const isAllowedNonNullType = hasArgument &&
97
+ type.gqlType.kind === graphql_1.Kind.NAMED_TYPE &&
98
+ (type.gqlType.name.value === typeName ||
99
+ (typeName === 'String' && (0, graphql_1.isScalarType)(schema.getType(type.gqlType.name.value))));
100
+ if (!isAllowedNonNullType) {
101
+ const returnType = typeName === 'String' ? 'String or Scalar' : typeName;
102
+ context.report({
103
+ node: (argument || fieldNode).name,
104
+ message: hasArgument
105
+ ? `Argument \`${argumentName}\` must return ${returnType}.`
106
+ : `Field \`${fieldNode.name.value}\` must contain an argument \`${argumentName}\`, that return ${returnType}.`,
107
+ });
108
+ }
109
+ }
110
+ if (includeBoth || args.first || args.after) {
111
+ checkField('Int', 'first');
112
+ checkField('String', 'after');
113
+ }
114
+ if (includeBoth || args.last || args.before) {
115
+ checkField('Int', 'last');
116
+ checkField('String', 'before');
117
+ }
118
+ },
119
+ };
120
+ },
121
+ };
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rule = exports.NON_OBJECT_TYPES = void 0;
4
+ const graphql_1 = require("graphql");
5
+ const MUST_BE_OBJECT_TYPE = 'MUST_BE_OBJECT_TYPE';
6
+ const MUST_CONTAIN_FIELD_EDGES = 'MUST_CONTAIN_FIELD_EDGES';
7
+ const MUST_CONTAIN_FIELD_PAGE_INFO = 'MUST_CONTAIN_FIELD_PAGE_INFO';
8
+ const MUST_HAVE_CONNECTION_SUFFIX = 'MUST_HAVE_CONNECTION_SUFFIX';
9
+ const EDGES_FIELD_MUST_RETURN_LIST_TYPE = 'EDGES_FIELD_MUST_RETURN_LIST_TYPE';
10
+ const PAGE_INFO_FIELD_MUST_RETURN_NON_NULL_TYPE = 'PAGE_INFO_FIELD_MUST_RETURN_NON_NULL_TYPE';
11
+ exports.NON_OBJECT_TYPES = [
12
+ graphql_1.Kind.SCALAR_TYPE_DEFINITION,
13
+ graphql_1.Kind.UNION_TYPE_DEFINITION,
14
+ graphql_1.Kind.UNION_TYPE_EXTENSION,
15
+ graphql_1.Kind.INPUT_OBJECT_TYPE_DEFINITION,
16
+ graphql_1.Kind.INPUT_OBJECT_TYPE_EXTENSION,
17
+ graphql_1.Kind.ENUM_TYPE_DEFINITION,
18
+ graphql_1.Kind.ENUM_TYPE_EXTENSION,
19
+ graphql_1.Kind.INTERFACE_TYPE_DEFINITION,
20
+ graphql_1.Kind.INTERFACE_TYPE_EXTENSION,
21
+ ];
22
+ const notConnectionTypesSelector = `:matches(${exports.NON_OBJECT_TYPES})[name.value=/Connection$/] > .name`;
23
+ const hasEdgesField = (node) => node.fields.some(field => field.name.value === 'edges');
24
+ const hasPageInfoField = (node) => node.fields.some(field => field.name.value === 'pageInfo');
25
+ exports.rule = {
26
+ meta: {
27
+ type: 'problem',
28
+ docs: {
29
+ category: 'Schema',
30
+ description: [
31
+ 'Set of rules to follow Relay specification for Connection types.',
32
+ '',
33
+ '- Any type whose name ends in "Connection" is considered by spec to be a `Connection type`',
34
+ '- Connection type must be an Object type',
35
+ '- Connection type must contain a field `edges` that return a list type that wraps an edge type',
36
+ '- Connection type must contain a field `pageInfo` that return a non-null `PageInfo` Object type',
37
+ ].join('\n'),
38
+ url: 'https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/relay-connection-types.md',
39
+ isDisabledForAllConfig: true,
40
+ examples: [
41
+ {
42
+ title: 'Incorrect',
43
+ code: /* GraphQL */ `
44
+ type UserPayload { # should be an Object type with \`Connection\` suffix
45
+ edges: UserEdge! # should return a list type
46
+ pageInfo: PageInfo # should return a non-null \`PageInfo\` Object type
47
+ }
48
+ `,
49
+ },
50
+ {
51
+ title: 'Correct',
52
+ code: /* GraphQL */ `
53
+ type UserConnection {
54
+ edges: [UserEdge]
55
+ pageInfo: PageInfo!
56
+ }
57
+ `,
58
+ },
59
+ ],
60
+ },
61
+ messages: {
62
+ // Connection types
63
+ [MUST_BE_OBJECT_TYPE]: 'Connection type must be an Object type.',
64
+ [MUST_HAVE_CONNECTION_SUFFIX]: 'Connection type must have `Connection` suffix.',
65
+ [MUST_CONTAIN_FIELD_EDGES]: 'Connection type must contain a field `edges` that return a list type.',
66
+ [MUST_CONTAIN_FIELD_PAGE_INFO]: 'Connection type must contain a field `pageInfo` that return a non-null `PageInfo` Object type.',
67
+ [EDGES_FIELD_MUST_RETURN_LIST_TYPE]: '`edges` field must return a list type.',
68
+ [PAGE_INFO_FIELD_MUST_RETURN_NON_NULL_TYPE]: '`pageInfo` field must return a non-null `PageInfo` Object type.',
69
+ },
70
+ schema: [],
71
+ },
72
+ create(context) {
73
+ return {
74
+ [notConnectionTypesSelector](node) {
75
+ context.report({ node, messageId: MUST_BE_OBJECT_TYPE });
76
+ },
77
+ ':matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value!=/Connection$/]'(node) {
78
+ if (hasEdgesField(node) && hasPageInfoField(node)) {
79
+ context.report({ node: node.name, messageId: MUST_HAVE_CONNECTION_SUFFIX });
80
+ }
81
+ },
82
+ ':matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=/Connection$/]'(node) {
83
+ if (!hasEdgesField(node)) {
84
+ context.report({ node: node.name, messageId: MUST_CONTAIN_FIELD_EDGES });
85
+ }
86
+ if (!hasPageInfoField(node)) {
87
+ context.report({ node: node.name, messageId: MUST_CONTAIN_FIELD_PAGE_INFO });
88
+ }
89
+ },
90
+ ':matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=/Connection$/] > FieldDefinition[name.value=edges] > .gqlType'(node) {
91
+ const isListType = node.kind === graphql_1.Kind.LIST_TYPE ||
92
+ (node.kind === graphql_1.Kind.NON_NULL_TYPE && node.gqlType.kind === graphql_1.Kind.LIST_TYPE);
93
+ if (!isListType) {
94
+ context.report({ node, messageId: EDGES_FIELD_MUST_RETURN_LIST_TYPE });
95
+ }
96
+ },
97
+ ':matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=/Connection$/] > FieldDefinition[name.value=pageInfo] > .gqlType'(node) {
98
+ const isNonNullPageInfoType = node.kind === graphql_1.Kind.NON_NULL_TYPE &&
99
+ node.gqlType.kind === graphql_1.Kind.NAMED_TYPE &&
100
+ node.gqlType.name.value === 'PageInfo';
101
+ if (!isNonNullPageInfoType) {
102
+ context.report({ node, messageId: PAGE_INFO_FIELD_MUST_RETURN_NON_NULL_TYPE });
103
+ }
104
+ },
105
+ };
106
+ },
107
+ };
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rule = void 0;
4
+ const graphql_1 = require("graphql");
5
+ const utils_1 = require("@graphql-tools/utils");
6
+ const utils_2 = require("../utils");
7
+ const RULE_ID = 'relay-edge-types';
8
+ const MESSAGE_MUST_BE_OBJECT_TYPE = 'MESSAGE_MUST_BE_OBJECT_TYPE';
9
+ const MESSAGE_MISSING_EDGE_SUFFIX = 'MESSAGE_MISSING_EDGE_SUFFIX';
10
+ const MESSAGE_LIST_TYPE_ONLY_EDGE_TYPE = 'MESSAGE_LIST_TYPE_ONLY_EDGE_TYPE';
11
+ const MESSAGE_SHOULD_IMPLEMENTS_NODE = 'MESSAGE_SHOULD_IMPLEMENTS_NODE';
12
+ let edgeTypesCache;
13
+ function getEdgeTypes(schema) {
14
+ // We don't want cache edgeTypes on test environment
15
+ // Otherwise edgeTypes will be same for all tests
16
+ if (process.env.NODE_ENV !== 'test' && edgeTypesCache) {
17
+ return edgeTypesCache;
18
+ }
19
+ const edgeTypes = new Set();
20
+ const visitor = {
21
+ ObjectTypeDefinition(node) {
22
+ const typeName = node.name.value;
23
+ const hasConnectionSuffix = typeName.endsWith('Connection');
24
+ if (!hasConnectionSuffix) {
25
+ return;
26
+ }
27
+ const edges = node.fields.find(field => field.name.value === 'edges');
28
+ if (edges) {
29
+ const edgesTypeName = (0, utils_2.getTypeName)(edges);
30
+ const edgesType = schema.getType(edgesTypeName);
31
+ if ((0, graphql_1.isObjectType)(edgesType)) {
32
+ edgeTypes.add(edgesTypeName);
33
+ }
34
+ }
35
+ },
36
+ };
37
+ const astNode = (0, utils_1.getDocumentNodeFromSchema)(schema); // Transforms the schema into ASTNode
38
+ (0, graphql_1.visit)(astNode, visitor);
39
+ edgeTypesCache = edgeTypes;
40
+ return edgeTypesCache;
41
+ }
42
+ const schema = {
43
+ type: 'array',
44
+ maxItems: 1,
45
+ items: {
46
+ type: 'object',
47
+ additionalProperties: false,
48
+ minProperties: 1,
49
+ properties: {
50
+ withEdgeSuffix: {
51
+ type: 'boolean',
52
+ default: true,
53
+ description: 'Edge type name must end in "Edge".',
54
+ },
55
+ shouldImplementNode: {
56
+ type: 'boolean',
57
+ default: true,
58
+ description: "Edge type's field `node` must implement `Node` interface.",
59
+ },
60
+ listTypeCanWrapOnlyEdgeType: {
61
+ type: 'boolean',
62
+ default: true,
63
+ description: 'A list type should only wrap an edge type.',
64
+ },
65
+ },
66
+ },
67
+ };
68
+ exports.rule = {
69
+ meta: {
70
+ type: 'problem',
71
+ docs: {
72
+ category: 'Schema',
73
+ description: [
74
+ 'Set of rules to follow Relay specification for Edge types.',
75
+ '',
76
+ "- 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",
77
+ '- Edge type must be an Object type',
78
+ '- 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',
79
+ '- Edge type must contain a field `cursor` that return either String, Scalar, or a non-null wrapper around one of those types',
80
+ '- Edge type name must end in "Edge" _(optional)_',
81
+ "- Edge type's field `node` must implement `Node` interface _(optional)_",
82
+ '- A list type should only wrap an edge type _(optional)_',
83
+ ].join('\n'),
84
+ url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
85
+ isDisabledForAllConfig: true,
86
+ requiresSchema: true,
87
+ examples: [
88
+ {
89
+ title: 'Correct',
90
+ code: /* GraphQL */ `
91
+ type UserConnection {
92
+ edges: [UserEdge]
93
+ pageInfo: PageInfo!
94
+ }
95
+ `,
96
+ },
97
+ ],
98
+ },
99
+ messages: {
100
+ [MESSAGE_MUST_BE_OBJECT_TYPE]: 'Edge type must be an Object type.',
101
+ [MESSAGE_MISSING_EDGE_SUFFIX]: 'Edge type must have "Edge" suffix.',
102
+ [MESSAGE_LIST_TYPE_ONLY_EDGE_TYPE]: 'A list type should only wrap an edge type.',
103
+ [MESSAGE_SHOULD_IMPLEMENTS_NODE]: "Edge type's field `node` must implement `Node` interface.",
104
+ },
105
+ schema,
106
+ },
107
+ create(context) {
108
+ const schema = (0, utils_2.requireGraphQLSchemaFromContext)(RULE_ID, context);
109
+ const edgeTypes = getEdgeTypes(schema);
110
+ const options = {
111
+ withEdgeSuffix: true,
112
+ shouldImplementNode: true,
113
+ listTypeCanWrapOnlyEdgeType: true,
114
+ ...context.options[0],
115
+ };
116
+ const isNamedOrNonNullNamed = (node) => node.kind === graphql_1.Kind.NAMED_TYPE ||
117
+ (node.kind === graphql_1.Kind.NON_NULL_TYPE && node.gqlType.kind === graphql_1.Kind.NAMED_TYPE);
118
+ const checkNodeField = (node) => {
119
+ const nodeField = node.fields.find(field => field.name.value === 'node');
120
+ const message = 'return either a Scalar, Enum, Object, Interface, Union, or a non-null wrapper around one of those types.';
121
+ if (!nodeField) {
122
+ context.report({
123
+ node: node.name,
124
+ message: `Edge type must contain a field \`node\` that ${message}`,
125
+ });
126
+ }
127
+ else if (!isNamedOrNonNullNamed(nodeField.gqlType)) {
128
+ context.report({ node: nodeField.name, message: `Field \`node\` must ${message}` });
129
+ }
130
+ else if (options.shouldImplementNode) {
131
+ const nodeReturnTypeName = (0, utils_2.getTypeName)(nodeField.gqlType.rawNode());
132
+ const type = schema.getType(nodeReturnTypeName);
133
+ if (!(0, graphql_1.isObjectType)(type)) {
134
+ return;
135
+ }
136
+ const implementsNode = type.astNode.interfaces.some(n => n.name.value === 'Node');
137
+ if (!implementsNode) {
138
+ context.report({ node: node.name, messageId: MESSAGE_SHOULD_IMPLEMENTS_NODE });
139
+ }
140
+ }
141
+ };
142
+ const checkCursorField = (node) => {
143
+ const cursorField = node.fields.find(field => field.name.value === 'cursor');
144
+ const message = 'return either a String, Scalar, or a non-null wrapper wrapper around one of those types.';
145
+ if (!cursorField) {
146
+ context.report({
147
+ node: node.name,
148
+ message: `Edge type must contain a field \`cursor\` that ${message}`,
149
+ });
150
+ return;
151
+ }
152
+ const typeName = (0, utils_2.getTypeName)(cursorField.rawNode());
153
+ if (!isNamedOrNonNullNamed(cursorField.gqlType) ||
154
+ (typeName !== 'String' && !(0, graphql_1.isScalarType)(schema.getType(typeName)))) {
155
+ context.report({ node: cursorField.name, message: `Field \`cursor\` must ${message}` });
156
+ }
157
+ };
158
+ const listeners = {
159
+ ':matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=/Connection$/] > FieldDefinition[name.value=edges] > .gqlType Name'(node) {
160
+ const type = schema.getType(node.value);
161
+ if (!(0, graphql_1.isObjectType)(type)) {
162
+ context.report({ node, messageId: MESSAGE_MUST_BE_OBJECT_TYPE });
163
+ }
164
+ },
165
+ ':matches(ObjectTypeDefinition, ObjectTypeExtension)'(node) {
166
+ const typeName = node.name.value;
167
+ if (edgeTypes.has(typeName)) {
168
+ checkNodeField(node);
169
+ checkCursorField(node);
170
+ if (options.withEdgeSuffix && !typeName.endsWith('Edge')) {
171
+ context.report({ node: node.name, messageId: MESSAGE_MISSING_EDGE_SUFFIX });
172
+ }
173
+ }
174
+ },
175
+ };
176
+ if (options.listTypeCanWrapOnlyEdgeType) {
177
+ listeners['FieldDefinition > .gqlType'] = (node) => {
178
+ if (node.kind === graphql_1.Kind.LIST_TYPE ||
179
+ (node.kind === graphql_1.Kind.NON_NULL_TYPE && node.gqlType.kind === graphql_1.Kind.LIST_TYPE)) {
180
+ const typeName = (0, utils_2.getTypeName)(node.rawNode());
181
+ if (!edgeTypes.has(typeName)) {
182
+ context.report({ node, messageId: MESSAGE_LIST_TYPE_ONLY_EDGE_TYPE });
183
+ }
184
+ }
185
+ };
186
+ }
187
+ return listeners;
188
+ },
189
+ };
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rule = void 0;
4
+ const graphql_1 = require("graphql");
5
+ const relay_connection_types_1 = require("./relay-connection-types");
6
+ const utils_1 = require("../utils");
7
+ const RULE_ID = 'relay-page-info';
8
+ const MESSAGE_MUST_EXIST = 'MESSAGE_MUST_EXIST';
9
+ const MESSAGE_MUST_BE_OBJECT_TYPE = 'MESSAGE_MUST_BE_OBJECT_TYPE';
10
+ const notPageInfoTypesSelector = `:matches(${relay_connection_types_1.NON_OBJECT_TYPES})[name.value=PageInfo] > .name`;
11
+ let hasPageInfoChecked = false;
12
+ exports.rule = {
13
+ meta: {
14
+ type: 'problem',
15
+ docs: {
16
+ category: 'Schema',
17
+ description: [
18
+ 'Set of rules to follow Relay specification for `PageInfo` object.',
19
+ '',
20
+ '- `PageInfo` must be an Object type',
21
+ '- `PageInfo` must contain fields `hasPreviousPage` and `hasNextPage`, that return non-null Boolean',
22
+ '- `PageInfo` must contain fields `startCursor` and `endCursor`, that return either String or Scalar, which can be null if there are no results',
23
+ ].join('\n'),
24
+ url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
25
+ examples: [
26
+ {
27
+ title: 'Correct',
28
+ code: /* GraphQL */ `
29
+ type PageInfo {
30
+ hasPreviousPage: Boolean!
31
+ hasNextPage: Boolean!
32
+ startCursor: String
33
+ endCursor: String
34
+ }
35
+ `,
36
+ },
37
+ ],
38
+ isDisabledForAllConfig: true,
39
+ requiresSchema: true,
40
+ },
41
+ messages: {
42
+ [MESSAGE_MUST_EXIST]: 'The server must provide a `PageInfo` object.',
43
+ [MESSAGE_MUST_BE_OBJECT_TYPE]: '`PageInfo` must be an Object type.',
44
+ },
45
+ schema: [],
46
+ },
47
+ create(context) {
48
+ const schema = (0, utils_1.requireGraphQLSchemaFromContext)(RULE_ID, context);
49
+ if (process.env.NODE_ENV === 'test' || !hasPageInfoChecked) {
50
+ const pageInfoType = schema.getType('PageInfo');
51
+ if (!pageInfoType) {
52
+ context.report({
53
+ loc: utils_1.REPORT_ON_FIRST_CHARACTER,
54
+ messageId: MESSAGE_MUST_EXIST,
55
+ });
56
+ }
57
+ hasPageInfoChecked = true;
58
+ }
59
+ return {
60
+ [notPageInfoTypesSelector](node) {
61
+ context.report({ node, messageId: MESSAGE_MUST_BE_OBJECT_TYPE });
62
+ },
63
+ 'ObjectTypeDefinition[name.value=PageInfo]'(node) {
64
+ const fieldMap = Object.fromEntries(node.fields.map(field => [field.name.value, field]));
65
+ const checkField = (fieldName, typeName) => {
66
+ const field = fieldMap[fieldName];
67
+ let isAllowedType = false;
68
+ if (field) {
69
+ const type = field.gqlType;
70
+ if (typeName === 'Boolean') {
71
+ isAllowedType =
72
+ type.kind === graphql_1.Kind.NON_NULL_TYPE &&
73
+ type.gqlType.kind === graphql_1.Kind.NAMED_TYPE &&
74
+ type.gqlType.name.value === 'Boolean';
75
+ }
76
+ else if (type.kind === graphql_1.Kind.NAMED_TYPE) {
77
+ isAllowedType =
78
+ type.name.value === 'String' || (0, graphql_1.isScalarType)(schema.getType(type.name.value));
79
+ }
80
+ }
81
+ if (!isAllowedType) {
82
+ const returnType = typeName === 'Boolean'
83
+ ? 'non-null Boolean'
84
+ : 'either String or Scalar, which can be null if there are no results';
85
+ context.report({
86
+ node: field ? field.name : node.name,
87
+ message: field
88
+ ? `Field \`${fieldName}\` must return ${returnType}.`
89
+ : `\`PageInfo\` must contain a field \`${fieldName}\`, that return ${returnType}.`,
90
+ });
91
+ }
92
+ };
93
+ checkField('hasPreviousPage', 'Boolean');
94
+ checkField('hasNextPage', 'Boolean');
95
+ checkField('startCursor', 'String');
96
+ checkField('endCursor', 'String');
97
+ },
98
+ };
99
+ },
100
+ };
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rule = void 0;
4
+ const estree_converter_1 = require("../estree-converter");
5
+ // eslint-disable-next-line unicorn/better-regex
6
+ const DATE_REGEX = /^\d{2}\/\d{2}\/\d{4}$/;
7
+ const MESSAGE_REQUIRE_DATE = 'MESSAGE_REQUIRE_DATE';
8
+ const MESSAGE_INVALID_FORMAT = 'MESSAGE_INVALID_FORMAT';
9
+ const MESSAGE_INVALID_DATE = 'MESSAGE_INVALID_DATE';
10
+ const MESSAGE_CAN_BE_REMOVED = 'MESSAGE_CAN_BE_REMOVED';
11
+ const schema = {
12
+ type: 'array',
13
+ maxItems: 1,
14
+ items: {
15
+ type: 'object',
16
+ additionalProperties: false,
17
+ properties: {
18
+ argumentName: {
19
+ type: 'string',
20
+ },
21
+ },
22
+ },
23
+ };
24
+ exports.rule = {
25
+ meta: {
26
+ type: 'suggestion',
27
+ hasSuggestions: true,
28
+ docs: {
29
+ category: 'Schema',
30
+ description: 'Require deletion date on `@deprecated` directive. Suggest removing deprecated things after deprecated date.',
31
+ url: 'https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/require-deprecation-date.md',
32
+ examples: [
33
+ {
34
+ title: 'Incorrect',
35
+ code: /* GraphQL */ `
36
+ type User {
37
+ firstname: String @deprecated
38
+ firstName: String
39
+ }
40
+ `,
41
+ },
42
+ {
43
+ title: 'Incorrect',
44
+ code: /* GraphQL */ `
45
+ type User {
46
+ firstname: String @deprecated(reason: "Use 'firstName' instead")
47
+ firstName: String
48
+ }
49
+ `,
50
+ },
51
+ {
52
+ title: 'Correct',
53
+ code: /* GraphQL */ `
54
+ type User {
55
+ firstname: String
56
+ @deprecated(reason: "Use 'firstName' instead", deletionDate: "25/12/2022")
57
+ firstName: String
58
+ }
59
+ `,
60
+ },
61
+ ],
62
+ },
63
+ messages: {
64
+ [MESSAGE_REQUIRE_DATE]: 'Directive "@deprecated" must have a deletion date',
65
+ [MESSAGE_INVALID_FORMAT]: 'Deletion date must be in format "DD/MM/YYYY"',
66
+ [MESSAGE_INVALID_DATE]: 'Invalid "{{ deletionDate }}" deletion date',
67
+ [MESSAGE_CAN_BE_REMOVED]: '"{{ nodeName }}" сan be removed',
68
+ },
69
+ schema,
70
+ },
71
+ create(context) {
72
+ return {
73
+ 'Directive[name.value=deprecated]'(node) {
74
+ var _a;
75
+ const argName = ((_a = context.options[0]) === null || _a === void 0 ? void 0 : _a.argumentName) || 'deletionDate';
76
+ const deletionDateNode = node.arguments.find(arg => arg.name.value === argName);
77
+ if (!deletionDateNode) {
78
+ context.report({
79
+ node: node.name,
80
+ messageId: MESSAGE_REQUIRE_DATE,
81
+ });
82
+ return;
83
+ }
84
+ const deletionDate = (0, estree_converter_1.valueFromNode)(deletionDateNode.value);
85
+ const isValidDate = DATE_REGEX.test(deletionDate);
86
+ if (!isValidDate) {
87
+ context.report({ node: deletionDateNode.value, messageId: MESSAGE_INVALID_FORMAT });
88
+ return;
89
+ }
90
+ let [day, month, year] = deletionDate.split('/');
91
+ day = day.padStart(2, '0');
92
+ month = month.padStart(2, '0');
93
+ const deletionDateInMS = Date.parse(`${year}-${month}-${day}`);
94
+ if (Number.isNaN(deletionDateInMS)) {
95
+ context.report({
96
+ node: deletionDateNode.value,
97
+ messageId: MESSAGE_INVALID_DATE,
98
+ data: {
99
+ deletionDate,
100
+ },
101
+ });
102
+ return;
103
+ }
104
+ const canRemove = Date.now() > deletionDateInMS;
105
+ if (canRemove) {
106
+ const { parent } = node;
107
+ const nodeName = parent.name.value;
108
+ context.report({
109
+ node: parent.name,
110
+ messageId: MESSAGE_CAN_BE_REMOVED,
111
+ data: { nodeName },
112
+ suggest: [
113
+ {
114
+ desc: `Remove \`${nodeName}\``,
115
+ fix: fixer => fixer.remove(parent),
116
+ },
117
+ ],
118
+ });
119
+ }
120
+ },
121
+ };
122
+ },
123
+ };