@graphql-eslint/eslint-plugin 3.14.0-alpha-20221222211539-5e993f5 → 3.14.0-alpha-20221223011223-bd3e820

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/cjs/configs/index.js +10 -10
  2. package/cjs/documents.js +5 -5
  3. package/cjs/estree-converter/converter.js +2 -2
  4. package/cjs/estree-converter/index.js +3 -3
  5. package/cjs/estree-converter/utils.js +2 -2
  6. package/cjs/flat-configs.js +36 -0
  7. package/cjs/index.js +16 -14
  8. package/cjs/parser.js +13 -13
  9. package/cjs/processor.js +2 -2
  10. package/cjs/rules/alphabetize.js +7 -7
  11. package/cjs/rules/graphql-js-validation.js +9 -9
  12. package/cjs/rules/index.js +66 -66
  13. package/cjs/rules/lone-executable-definition.js +4 -4
  14. package/cjs/rules/match-document-filename.js +4 -4
  15. package/cjs/rules/naming-convention.js +6 -6
  16. package/cjs/rules/no-anonymous-operations.js +2 -2
  17. package/cjs/rules/no-deprecated.js +2 -2
  18. package/cjs/rules/no-one-place-fragments.js +3 -4
  19. package/cjs/rules/no-root-type.js +3 -3
  20. package/cjs/rules/no-scalar-result-type-on-mutation.js +2 -2
  21. package/cjs/rules/no-unreachable-types.js +3 -3
  22. package/cjs/rules/no-unused-fields.js +3 -3
  23. package/cjs/rules/relay-arguments.js +2 -2
  24. package/cjs/rules/relay-edge-types.js +6 -6
  25. package/cjs/rules/relay-page-info.js +5 -5
  26. package/cjs/rules/require-deprecation-date.js +2 -2
  27. package/cjs/rules/require-deprecation-reason.js +2 -2
  28. package/cjs/rules/require-description.js +8 -8
  29. package/cjs/rules/require-field-of-type-query-in-mutation-result.js +3 -3
  30. package/cjs/rules/require-id-when-available.js +8 -8
  31. package/cjs/rules/selection-set-depth.js +5 -5
  32. package/cjs/rules/strict-id-in-types.js +7 -7
  33. package/cjs/rules/unique-fragment-name.js +4 -4
  34. package/cjs/rules/unique-operation-name.js +2 -2
  35. package/cjs/schema.js +2 -2
  36. package/esm/cache.js +25 -0
  37. package/esm/configs/base.js +4 -0
  38. package/esm/configs/index.js +12 -0
  39. package/esm/configs/operations-all.js +29 -0
  40. package/esm/configs/operations-recommended.js +53 -0
  41. package/esm/configs/relay.js +9 -0
  42. package/esm/configs/schema-all.js +22 -0
  43. package/esm/configs/schema-recommended.js +49 -0
  44. package/esm/documents.js +144 -0
  45. package/esm/estree-converter/converter.js +58 -0
  46. package/esm/estree-converter/index.js +3 -0
  47. package/esm/estree-converter/types.js +1 -0
  48. package/esm/estree-converter/utils.js +102 -0
  49. package/esm/flat-configs.js +33 -0
  50. package/esm/graphql-config.js +49 -0
  51. package/esm/index.js +9 -0
  52. package/esm/package.json +1 -0
  53. package/esm/parser.js +56 -0
  54. package/esm/processor.js +75 -0
  55. package/esm/rules/alphabetize.js +344 -0
  56. package/esm/rules/description-style.js +75 -0
  57. package/esm/rules/graphql-js-validation.js +498 -0
  58. package/esm/rules/index.js +71 -0
  59. package/esm/rules/input-name.js +133 -0
  60. package/esm/rules/lone-executable-definition.js +85 -0
  61. package/esm/rules/match-document-filename.js +232 -0
  62. package/esm/rules/naming-convention.js +307 -0
  63. package/esm/rules/no-anonymous-operations.js +64 -0
  64. package/esm/rules/no-case-insensitive-enum-values-duplicates.js +58 -0
  65. package/esm/rules/no-deprecated.js +121 -0
  66. package/esm/rules/no-duplicate-fields.js +109 -0
  67. package/esm/rules/no-hashtag-description.js +86 -0
  68. package/esm/rules/no-one-place-fragments.js +80 -0
  69. package/esm/rules/no-root-type.js +83 -0
  70. package/esm/rules/no-scalar-result-type-on-mutation.js +63 -0
  71. package/esm/rules/no-typename-prefix.js +62 -0
  72. package/esm/rules/no-unreachable-types.js +154 -0
  73. package/esm/rules/no-unused-fields.js +127 -0
  74. package/esm/rules/relay-arguments.js +118 -0
  75. package/esm/rules/relay-connection-types.js +104 -0
  76. package/esm/rules/relay-edge-types.js +186 -0
  77. package/esm/rules/relay-page-info.js +97 -0
  78. package/esm/rules/require-deprecation-date.js +120 -0
  79. package/esm/rules/require-deprecation-reason.js +53 -0
  80. package/esm/rules/require-description.js +190 -0
  81. package/esm/rules/require-field-of-type-query-in-mutation-result.js +69 -0
  82. package/esm/rules/require-id-when-available.js +196 -0
  83. package/esm/rules/require-nullable-fields-with-oneof.js +58 -0
  84. package/esm/rules/require-type-pattern-with-oneof.js +57 -0
  85. package/esm/rules/selection-set-depth.js +131 -0
  86. package/esm/rules/strict-id-in-types.js +159 -0
  87. package/esm/rules/unique-fragment-name.js +86 -0
  88. package/esm/rules/unique-operation-name.js +62 -0
  89. package/esm/schema.js +37 -0
  90. package/esm/testkit.js +181 -0
  91. package/esm/types.js +1 -0
  92. package/esm/utils.js +83 -0
  93. package/package.json +10 -1
  94. package/typings/estree-converter/converter.d.cts +1 -1
  95. package/typings/estree-converter/converter.d.ts +1 -1
  96. package/typings/estree-converter/index.d.cts +3 -3
  97. package/typings/estree-converter/index.d.ts +3 -3
  98. package/typings/estree-converter/types.d.cts +3 -3
  99. package/typings/estree-converter/types.d.ts +3 -3
  100. package/typings/estree-converter/utils.d.cts +2 -2
  101. package/typings/estree-converter/utils.d.ts +2 -2
  102. package/typings/flat-configs.d.cts +248 -0
  103. package/typings/flat-configs.d.ts +248 -0
  104. package/typings/graphql-config.d.cts +1 -1
  105. package/typings/graphql-config.d.ts +1 -1
  106. package/typings/index.d.cts +8 -7
  107. package/typings/index.d.ts +8 -7
  108. package/typings/parser.d.cts +1 -1
  109. package/typings/parser.d.ts +1 -1
  110. package/typings/rules/alphabetize.d.cts +1 -1
  111. package/typings/rules/alphabetize.d.ts +1 -1
  112. package/typings/rules/description-style.d.cts +1 -1
  113. package/typings/rules/description-style.d.ts +1 -1
  114. package/typings/rules/graphql-js-validation.d.cts +1 -1
  115. package/typings/rules/graphql-js-validation.d.ts +1 -1
  116. package/typings/rules/index.d.cts +45 -45
  117. package/typings/rules/index.d.ts +45 -45
  118. package/typings/rules/input-name.d.cts +1 -1
  119. package/typings/rules/input-name.d.ts +1 -1
  120. package/typings/rules/lone-executable-definition.d.cts +1 -1
  121. package/typings/rules/lone-executable-definition.d.ts +1 -1
  122. package/typings/rules/match-document-filename.d.cts +2 -2
  123. package/typings/rules/match-document-filename.d.ts +2 -2
  124. package/typings/rules/naming-convention.d.cts +1 -1
  125. package/typings/rules/naming-convention.d.ts +1 -1
  126. package/typings/rules/no-anonymous-operations.d.cts +1 -1
  127. package/typings/rules/no-anonymous-operations.d.ts +1 -1
  128. package/typings/rules/no-case-insensitive-enum-values-duplicates.d.cts +1 -1
  129. package/typings/rules/no-case-insensitive-enum-values-duplicates.d.ts +1 -1
  130. package/typings/rules/no-deprecated.d.cts +1 -1
  131. package/typings/rules/no-deprecated.d.ts +1 -1
  132. package/typings/rules/no-duplicate-fields.d.cts +1 -1
  133. package/typings/rules/no-duplicate-fields.d.ts +1 -1
  134. package/typings/rules/no-hashtag-description.d.cts +1 -1
  135. package/typings/rules/no-hashtag-description.d.ts +1 -1
  136. package/typings/rules/no-one-place-fragments.d.cts +1 -1
  137. package/typings/rules/no-one-place-fragments.d.ts +1 -1
  138. package/typings/rules/no-root-type.d.cts +1 -1
  139. package/typings/rules/no-root-type.d.ts +1 -1
  140. package/typings/rules/no-scalar-result-type-on-mutation.d.cts +1 -1
  141. package/typings/rules/no-scalar-result-type-on-mutation.d.ts +1 -1
  142. package/typings/rules/no-typename-prefix.d.cts +1 -1
  143. package/typings/rules/no-typename-prefix.d.ts +1 -1
  144. package/typings/rules/no-unreachable-types.d.cts +1 -1
  145. package/typings/rules/no-unreachable-types.d.ts +1 -1
  146. package/typings/rules/no-unused-fields.d.cts +1 -1
  147. package/typings/rules/no-unused-fields.d.ts +1 -1
  148. package/typings/rules/relay-arguments.d.cts +1 -1
  149. package/typings/rules/relay-arguments.d.ts +1 -1
  150. package/typings/rules/relay-connection-types.d.cts +1 -1
  151. package/typings/rules/relay-connection-types.d.ts +1 -1
  152. package/typings/rules/relay-edge-types.d.cts +1 -1
  153. package/typings/rules/relay-edge-types.d.ts +1 -1
  154. package/typings/rules/relay-page-info.d.cts +1 -1
  155. package/typings/rules/relay-page-info.d.ts +1 -1
  156. package/typings/rules/require-deprecation-date.d.cts +1 -1
  157. package/typings/rules/require-deprecation-date.d.ts +1 -1
  158. package/typings/rules/require-deprecation-reason.d.cts +1 -1
  159. package/typings/rules/require-deprecation-reason.d.ts +1 -1
  160. package/typings/rules/require-description.d.cts +1 -1
  161. package/typings/rules/require-description.d.ts +1 -1
  162. package/typings/rules/require-field-of-type-query-in-mutation-result.d.cts +1 -1
  163. package/typings/rules/require-field-of-type-query-in-mutation-result.d.ts +1 -1
  164. package/typings/rules/require-id-when-available.d.cts +1 -1
  165. package/typings/rules/require-id-when-available.d.ts +1 -1
  166. package/typings/rules/require-nullable-fields-with-oneof.d.cts +1 -1
  167. package/typings/rules/require-nullable-fields-with-oneof.d.ts +1 -1
  168. package/typings/rules/require-type-pattern-with-oneof.d.cts +1 -1
  169. package/typings/rules/require-type-pattern-with-oneof.d.ts +1 -1
  170. package/typings/rules/selection-set-depth.d.cts +1 -1
  171. package/typings/rules/selection-set-depth.d.ts +1 -1
  172. package/typings/rules/strict-id-in-types.d.cts +1 -1
  173. package/typings/rules/strict-id-in-types.d.ts +1 -1
  174. package/typings/rules/unique-fragment-name.d.cts +2 -2
  175. package/typings/rules/unique-fragment-name.d.ts +2 -2
  176. package/typings/rules/unique-operation-name.d.cts +1 -1
  177. package/typings/rules/unique-operation-name.d.ts +1 -1
  178. package/typings/schema.d.cts +1 -1
  179. package/typings/schema.d.ts +1 -1
  180. package/typings/testkit.d.cts +3 -3
  181. package/typings/testkit.d.ts +3 -3
  182. package/typings/types.d.cts +2 -2
  183. package/typings/types.d.ts +2 -2
  184. package/typings/utils.d.cts +2 -2
  185. package/typings/utils.d.ts +2 -2
@@ -0,0 +1,307 @@
1
+ import { Kind } from 'graphql';
2
+ import { TYPES_KINDS, convertCase, ARRAY_DEFAULT_OPTIONS } from '../utils.js';
3
+ const KindToDisplayName = {
4
+ // types
5
+ [Kind.OBJECT_TYPE_DEFINITION]: 'Type',
6
+ [Kind.INTERFACE_TYPE_DEFINITION]: 'Interface',
7
+ [Kind.ENUM_TYPE_DEFINITION]: 'Enumerator',
8
+ [Kind.SCALAR_TYPE_DEFINITION]: 'Scalar',
9
+ [Kind.INPUT_OBJECT_TYPE_DEFINITION]: 'Input type',
10
+ [Kind.UNION_TYPE_DEFINITION]: 'Union',
11
+ // fields
12
+ [Kind.FIELD_DEFINITION]: 'Field',
13
+ [Kind.INPUT_VALUE_DEFINITION]: 'Input property',
14
+ [Kind.ARGUMENT]: 'Argument',
15
+ [Kind.DIRECTIVE_DEFINITION]: 'Directive',
16
+ // rest
17
+ [Kind.ENUM_VALUE_DEFINITION]: 'Enumeration value',
18
+ [Kind.OPERATION_DEFINITION]: 'Operation',
19
+ [Kind.FRAGMENT_DEFINITION]: 'Fragment',
20
+ [Kind.VARIABLE_DEFINITION]: 'Variable',
21
+ };
22
+ const StyleToRegex = {
23
+ camelCase: /^[a-z][\dA-Za-z]*$/,
24
+ PascalCase: /^[A-Z][\dA-Za-z]*$/,
25
+ snake_case: /^[a-z][\d_a-z]*[\da-z]*$/,
26
+ UPPER_CASE: /^[A-Z][\dA-Z_]*[\dA-Z]*$/,
27
+ };
28
+ const ALLOWED_KINDS = Object.keys(KindToDisplayName).sort();
29
+ const ALLOWED_STYLES = Object.keys(StyleToRegex);
30
+ const schemaOption = {
31
+ oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
32
+ };
33
+ const schema = {
34
+ definitions: {
35
+ asString: {
36
+ enum: ALLOWED_STYLES,
37
+ description: `One of: ${ALLOWED_STYLES.map(t => `\`${t}\``).join(', ')}`,
38
+ },
39
+ asObject: {
40
+ type: 'object',
41
+ additionalProperties: false,
42
+ properties: {
43
+ style: { enum: ALLOWED_STYLES },
44
+ prefix: { type: 'string' },
45
+ suffix: { type: 'string' },
46
+ forbiddenPrefixes: ARRAY_DEFAULT_OPTIONS,
47
+ forbiddenSuffixes: ARRAY_DEFAULT_OPTIONS,
48
+ ignorePattern: {
49
+ type: 'string',
50
+ description: 'Option to skip validation of some words, e.g. acronyms',
51
+ },
52
+ },
53
+ },
54
+ },
55
+ type: 'array',
56
+ maxItems: 1,
57
+ items: {
58
+ type: 'object',
59
+ additionalProperties: false,
60
+ properties: {
61
+ types: {
62
+ ...schemaOption,
63
+ description: `Includes:\n${TYPES_KINDS.map(kind => `- \`${kind}\``).join('\n')}`,
64
+ },
65
+ ...Object.fromEntries(ALLOWED_KINDS.map(kind => [
66
+ kind,
67
+ {
68
+ ...schemaOption,
69
+ description: `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`,
70
+ },
71
+ ])),
72
+ allowLeadingUnderscore: {
73
+ type: 'boolean',
74
+ default: false,
75
+ },
76
+ allowTrailingUnderscore: {
77
+ type: 'boolean',
78
+ default: false,
79
+ },
80
+ },
81
+ patternProperties: {
82
+ [`^(${ALLOWED_KINDS.join('|')})(.+)?$`]: schemaOption,
83
+ },
84
+ description: [
85
+ "> It's possible to use a [`selector`](https://eslint.org/docs/developer-guide/selectors) that starts with allowed `ASTNode` names which are described below.",
86
+ '>',
87
+ '> Paste or drop code into the editor in [ASTExplorer](https://astexplorer.net) and inspect the generated AST to compose your selector.',
88
+ '>',
89
+ '> Example: pattern property `FieldDefinition[parent.name.value=Query]` will match only fields for type `Query`.',
90
+ ].join('\n'),
91
+ },
92
+ };
93
+ export const rule = {
94
+ meta: {
95
+ type: 'suggestion',
96
+ docs: {
97
+ description: 'Require names to follow specified conventions.',
98
+ category: ['Schema', 'Operations'],
99
+ recommended: true,
100
+ url: 'https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/naming-convention.md',
101
+ examples: [
102
+ {
103
+ title: 'Incorrect',
104
+ usage: [{ types: 'PascalCase', FieldDefinition: 'camelCase' }],
105
+ code: /* GraphQL */ `
106
+ type user {
107
+ first_name: String!
108
+ }
109
+ `,
110
+ },
111
+ {
112
+ title: 'Incorrect',
113
+ usage: [{ FragmentDefinition: { style: 'PascalCase', forbiddenSuffixes: ['Fragment'] } }],
114
+ code: /* GraphQL */ `
115
+ fragment UserFragment on User {
116
+ # ...
117
+ }
118
+ `,
119
+ },
120
+ {
121
+ title: 'Incorrect',
122
+ usage: [{ 'FieldDefinition[parent.name.value=Query]': { forbiddenPrefixes: ['get'] } }],
123
+ code: /* GraphQL */ `
124
+ type Query {
125
+ getUsers: [User!]!
126
+ }
127
+ `,
128
+ },
129
+ {
130
+ title: 'Correct',
131
+ usage: [{ types: 'PascalCase', FieldDefinition: 'camelCase' }],
132
+ code: /* GraphQL */ `
133
+ type User {
134
+ firstName: String
135
+ }
136
+ `,
137
+ },
138
+ {
139
+ title: 'Correct',
140
+ usage: [{ FragmentDefinition: { style: 'PascalCase', forbiddenSuffixes: ['Fragment'] } }],
141
+ code: /* GraphQL */ `
142
+ fragment UserFields on User {
143
+ # ...
144
+ }
145
+ `,
146
+ },
147
+ {
148
+ title: 'Correct',
149
+ usage: [{ 'FieldDefinition[parent.name.value=Query]': { forbiddenPrefixes: ['get'] } }],
150
+ code: /* GraphQL */ `
151
+ type Query {
152
+ users: [User!]!
153
+ }
154
+ `,
155
+ },
156
+ {
157
+ title: 'Correct',
158
+ usage: [{ FieldDefinition: { style: 'camelCase', ignorePattern: '^(EAN13|UPC|UK)' } }],
159
+ code: /* GraphQL */ `
160
+ type Product {
161
+ EAN13: String
162
+ UPC: String
163
+ UKFlag: String
164
+ }
165
+ `,
166
+ },
167
+ ],
168
+ configOptions: {
169
+ schema: [
170
+ {
171
+ types: 'PascalCase',
172
+ FieldDefinition: 'camelCase',
173
+ InputValueDefinition: 'camelCase',
174
+ Argument: 'camelCase',
175
+ DirectiveDefinition: 'camelCase',
176
+ EnumValueDefinition: 'UPPER_CASE',
177
+ 'FieldDefinition[parent.name.value=Query]': {
178
+ forbiddenPrefixes: ['query', 'get'],
179
+ forbiddenSuffixes: ['Query'],
180
+ },
181
+ 'FieldDefinition[parent.name.value=Mutation]': {
182
+ forbiddenPrefixes: ['mutation'],
183
+ forbiddenSuffixes: ['Mutation'],
184
+ },
185
+ 'FieldDefinition[parent.name.value=Subscription]': {
186
+ forbiddenPrefixes: ['subscription'],
187
+ forbiddenSuffixes: ['Subscription'],
188
+ },
189
+ },
190
+ ],
191
+ operations: [
192
+ {
193
+ VariableDefinition: 'camelCase',
194
+ OperationDefinition: {
195
+ style: 'PascalCase',
196
+ forbiddenPrefixes: ['Query', 'Mutation', 'Subscription', 'Get'],
197
+ forbiddenSuffixes: ['Query', 'Mutation', 'Subscription'],
198
+ },
199
+ FragmentDefinition: {
200
+ style: 'PascalCase',
201
+ forbiddenPrefixes: ['Fragment'],
202
+ forbiddenSuffixes: ['Fragment'],
203
+ },
204
+ },
205
+ ],
206
+ },
207
+ },
208
+ hasSuggestions: true,
209
+ schema,
210
+ },
211
+ create(context) {
212
+ const options = context.options[0] || {};
213
+ const { allowLeadingUnderscore, allowTrailingUnderscore, types, ...restOptions } = options;
214
+ function normalisePropertyOption(kind) {
215
+ const style = restOptions[kind] || types;
216
+ return typeof style === 'object' ? style : { style };
217
+ }
218
+ function report(node, message, suggestedName) {
219
+ context.report({
220
+ node,
221
+ message,
222
+ suggest: [
223
+ {
224
+ desc: `Rename to \`${suggestedName}\``,
225
+ fix: fixer => fixer.replaceText(node, suggestedName),
226
+ },
227
+ ],
228
+ });
229
+ }
230
+ const checkNode = (selector) => (n) => {
231
+ const { name: node } = n.kind === Kind.VARIABLE_DEFINITION ? n.variable : n;
232
+ if (!node) {
233
+ return;
234
+ }
235
+ const { prefix, suffix, forbiddenPrefixes, forbiddenSuffixes, style, ignorePattern } = normalisePropertyOption(selector);
236
+ const nodeType = KindToDisplayName[n.kind] || n.kind;
237
+ const nodeName = node.value;
238
+ const error = getError();
239
+ if (error) {
240
+ const { errorMessage, renameToName } = error;
241
+ const [leadingUnderscores] = nodeName.match(/^_*/);
242
+ const [trailingUnderscores] = nodeName.match(/_*$/);
243
+ const suggestedName = leadingUnderscores + renameToName + trailingUnderscores;
244
+ report(node, `${nodeType} "${nodeName}" should ${errorMessage}`, suggestedName);
245
+ }
246
+ function getError() {
247
+ const name = nodeName.replace(/(^_+)|(_+$)/g, '');
248
+ if (ignorePattern && new RegExp(ignorePattern, 'u').test(name)) {
249
+ return;
250
+ }
251
+ if (prefix && !name.startsWith(prefix)) {
252
+ return {
253
+ errorMessage: `have "${prefix}" prefix`,
254
+ renameToName: prefix + name,
255
+ };
256
+ }
257
+ if (suffix && !name.endsWith(suffix)) {
258
+ return {
259
+ errorMessage: `have "${suffix}" suffix`,
260
+ renameToName: name + suffix,
261
+ };
262
+ }
263
+ const forbiddenPrefix = forbiddenPrefixes === null || forbiddenPrefixes === void 0 ? void 0 : forbiddenPrefixes.find(prefix => name.startsWith(prefix));
264
+ if (forbiddenPrefix) {
265
+ return {
266
+ errorMessage: `not have "${forbiddenPrefix}" prefix`,
267
+ renameToName: name.replace(new RegExp(`^${forbiddenPrefix}`), ''),
268
+ };
269
+ }
270
+ const forbiddenSuffix = forbiddenSuffixes === null || forbiddenSuffixes === void 0 ? void 0 : forbiddenSuffixes.find(suffix => name.endsWith(suffix));
271
+ if (forbiddenSuffix) {
272
+ return {
273
+ errorMessage: `not have "${forbiddenSuffix}" suffix`,
274
+ renameToName: name.replace(new RegExp(`${forbiddenSuffix}$`), ''),
275
+ };
276
+ }
277
+ // Style is optional
278
+ if (!style) {
279
+ return;
280
+ }
281
+ const caseRegex = StyleToRegex[style];
282
+ if (!caseRegex.test(name)) {
283
+ return {
284
+ errorMessage: `be in ${style} format`,
285
+ renameToName: convertCase(style, name),
286
+ };
287
+ }
288
+ }
289
+ };
290
+ const checkUnderscore = (isLeading) => (node) => {
291
+ const suggestedName = node.value.replace(isLeading ? /^_+/ : /_+$/, '');
292
+ report(node, `${isLeading ? 'Leading' : 'Trailing'} underscores are not allowed`, suggestedName);
293
+ };
294
+ const listeners = {};
295
+ if (!allowLeadingUnderscore) {
296
+ listeners['Name[value=/^_/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore(true);
297
+ }
298
+ if (!allowTrailingUnderscore) {
299
+ listeners['Name[value=/_$/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore(false);
300
+ }
301
+ const selectors = new Set([types && TYPES_KINDS, Object.keys(restOptions)].flat().filter(Boolean));
302
+ for (const selector of selectors) {
303
+ listeners[selector] = checkNode(selector);
304
+ }
305
+ return listeners;
306
+ },
307
+ };
@@ -0,0 +1,64 @@
1
+ import { Kind } from 'graphql';
2
+ import { getLocation } from '../utils.js';
3
+ const RULE_ID = 'no-anonymous-operations';
4
+ export const rule = {
5
+ meta: {
6
+ type: 'suggestion',
7
+ hasSuggestions: true,
8
+ docs: {
9
+ category: 'Operations',
10
+ description: 'Require name for your GraphQL operations. This is useful since most GraphQL client libraries are using the operation name for caching purposes.',
11
+ recommended: true,
12
+ url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
13
+ examples: [
14
+ {
15
+ title: 'Incorrect',
16
+ code: /* GraphQL */ `
17
+ query {
18
+ # ...
19
+ }
20
+ `,
21
+ },
22
+ {
23
+ title: 'Correct',
24
+ code: /* GraphQL */ `
25
+ query user {
26
+ # ...
27
+ }
28
+ `,
29
+ },
30
+ ],
31
+ },
32
+ messages: {
33
+ [RULE_ID]: 'Anonymous GraphQL operations are forbidden. Make sure to name your {{ operation }}!',
34
+ },
35
+ schema: [],
36
+ },
37
+ create(context) {
38
+ return {
39
+ 'OperationDefinition[name=undefined]'(node) {
40
+ const [firstSelection] = node.selectionSet.selections;
41
+ const suggestedName = firstSelection.kind === Kind.FIELD
42
+ ? (firstSelection.alias || firstSelection.name).value
43
+ : node.operation;
44
+ context.report({
45
+ loc: getLocation(node.loc.start, node.operation),
46
+ messageId: RULE_ID,
47
+ data: {
48
+ operation: node.operation,
49
+ },
50
+ suggest: [
51
+ {
52
+ desc: `Rename to \`${suggestedName}\``,
53
+ fix(fixer) {
54
+ const sourceCode = context.getSourceCode();
55
+ const hasQueryKeyword = sourceCode.getText({ range: [node.range[0], node.range[0] + 1] }) !== '{';
56
+ return fixer.insertTextAfterRange([node.range[0], node.range[0] + (hasQueryKeyword ? node.operation.length : 0)], `${hasQueryKeyword ? '' : 'query'} ${suggestedName}${hasQueryKeyword ? '' : ' '}`);
57
+ },
58
+ },
59
+ ],
60
+ });
61
+ },
62
+ };
63
+ },
64
+ };
@@ -0,0 +1,58 @@
1
+ import { Kind } from 'graphql';
2
+ export const rule = {
3
+ meta: {
4
+ type: 'suggestion',
5
+ hasSuggestions: true,
6
+ docs: {
7
+ url: 'https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/no-case-insensitive-enum-values-duplicates.md',
8
+ category: 'Schema',
9
+ recommended: true,
10
+ description: 'Disallow case-insensitive enum values duplicates.',
11
+ examples: [
12
+ {
13
+ title: 'Incorrect',
14
+ code: /* GraphQL */ `
15
+ enum MyEnum {
16
+ Value
17
+ VALUE
18
+ ValuE
19
+ }
20
+ `,
21
+ },
22
+ {
23
+ title: 'Correct',
24
+ code: /* GraphQL */ `
25
+ enum MyEnum {
26
+ Value1
27
+ Value2
28
+ Value3
29
+ }
30
+ `,
31
+ },
32
+ ],
33
+ },
34
+ schema: [],
35
+ },
36
+ create(context) {
37
+ const selector = [Kind.ENUM_TYPE_DEFINITION, Kind.ENUM_TYPE_EXTENSION].join(',');
38
+ return {
39
+ [selector](node) {
40
+ const duplicates = node.values.filter((item, index, array) => array.findIndex(v => v.name.value.toLowerCase() === item.name.value.toLowerCase()) !==
41
+ index);
42
+ for (const duplicate of duplicates) {
43
+ const enumName = duplicate.name.value;
44
+ context.report({
45
+ node: duplicate.name,
46
+ message: `Case-insensitive enum values duplicates are not allowed! Found: \`${enumName}\`.`,
47
+ suggest: [
48
+ {
49
+ desc: `Remove \`${enumName}\` enum value`,
50
+ fix: fixer => fixer.remove(duplicate),
51
+ },
52
+ ],
53
+ });
54
+ }
55
+ },
56
+ };
57
+ },
58
+ };
@@ -0,0 +1,121 @@
1
+ import { Kind } from 'graphql';
2
+ import { requireGraphQLSchemaFromContext } from '../utils.js';
3
+ const RULE_ID = 'no-deprecated';
4
+ export const rule = {
5
+ meta: {
6
+ type: 'suggestion',
7
+ hasSuggestions: true,
8
+ docs: {
9
+ category: 'Operations',
10
+ description: 'Enforce that deprecated fields or enum values are not in use by operations.',
11
+ url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
12
+ requiresSchema: true,
13
+ examples: [
14
+ {
15
+ title: 'Incorrect (field)',
16
+ code: /* GraphQL */ `
17
+ # In your schema
18
+ type User {
19
+ id: ID!
20
+ name: String! @deprecated(reason: "old field, please use fullName instead")
21
+ fullName: String!
22
+ }
23
+
24
+ # Query
25
+ query user {
26
+ user {
27
+ name # This is deprecated, so you'll get an error
28
+ }
29
+ }
30
+ `,
31
+ },
32
+ {
33
+ title: 'Incorrect (enum value)',
34
+ code: /* GraphQL */ `
35
+ # In your schema
36
+ type Mutation {
37
+ changeSomething(type: SomeType): Boolean!
38
+ }
39
+
40
+ enum SomeType {
41
+ NEW
42
+ OLD @deprecated(reason: "old field, please use NEW instead")
43
+ }
44
+
45
+ # Mutation
46
+ mutation {
47
+ changeSomething(
48
+ type: OLD # This is deprecated, so you'll get an error
49
+ ) {
50
+ ...
51
+ }
52
+ }
53
+ `,
54
+ },
55
+ {
56
+ title: 'Correct',
57
+ code: /* GraphQL */ `
58
+ # In your schema
59
+ type User {
60
+ id: ID!
61
+ name: String! @deprecated(reason: "old field, please use fullName instead")
62
+ fullName: String!
63
+ }
64
+
65
+ # Query
66
+ query user {
67
+ user {
68
+ id
69
+ fullName
70
+ }
71
+ }
72
+ `,
73
+ },
74
+ ],
75
+ recommended: true,
76
+ },
77
+ messages: {
78
+ [RULE_ID]: 'This {{ type }} is marked as deprecated in your GraphQL schema (reason: {{ reason }})',
79
+ },
80
+ schema: [],
81
+ },
82
+ create(context) {
83
+ requireGraphQLSchemaFromContext(RULE_ID, context);
84
+ function report(node, reason) {
85
+ const nodeName = node.kind === Kind.ENUM ? node.value : node.name.value;
86
+ const nodeType = node.kind === Kind.ENUM ? 'enum value' : 'field';
87
+ context.report({
88
+ node,
89
+ messageId: RULE_ID,
90
+ data: {
91
+ type: nodeType,
92
+ reason,
93
+ },
94
+ suggest: [
95
+ {
96
+ desc: `Remove \`${nodeName}\` ${nodeType}`,
97
+ fix: fixer => fixer.remove(node),
98
+ },
99
+ ],
100
+ });
101
+ }
102
+ return {
103
+ EnumValue(node) {
104
+ var _a;
105
+ const typeInfo = node.typeInfo();
106
+ const reason = (_a = typeInfo.enumValue) === null || _a === void 0 ? void 0 : _a.deprecationReason;
107
+ if (reason) {
108
+ report(node, reason);
109
+ }
110
+ },
111
+ Field(node) {
112
+ var _a;
113
+ const typeInfo = node.typeInfo();
114
+ const reason = (_a = typeInfo.fieldDef) === null || _a === void 0 ? void 0 : _a.deprecationReason;
115
+ if (reason) {
116
+ report(node, reason);
117
+ }
118
+ },
119
+ };
120
+ },
121
+ };
@@ -0,0 +1,109 @@
1
+ import { Kind } from 'graphql';
2
+ const RULE_ID = 'no-duplicate-fields';
3
+ export const rule = {
4
+ meta: {
5
+ type: 'suggestion',
6
+ hasSuggestions: true,
7
+ docs: {
8
+ description: 'Checks for duplicate fields in selection set, variables in operation definition, or in arguments set of a field.',
9
+ category: 'Operations',
10
+ url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
11
+ recommended: true,
12
+ examples: [
13
+ {
14
+ title: 'Incorrect',
15
+ code: /* GraphQL */ `
16
+ query {
17
+ user {
18
+ name
19
+ email
20
+ name # duplicate field
21
+ }
22
+ }
23
+ `,
24
+ },
25
+ {
26
+ title: 'Incorrect',
27
+ code: /* GraphQL */ `
28
+ query {
29
+ users(
30
+ first: 100
31
+ skip: 50
32
+ after: "cji629tngfgou0b73kt7vi5jo"
33
+ first: 100 # duplicate argument
34
+ ) {
35
+ id
36
+ }
37
+ }
38
+ `,
39
+ },
40
+ {
41
+ title: 'Incorrect',
42
+ code: /* GraphQL */ `
43
+ query (
44
+ $first: Int!
45
+ $first: Int! # duplicate variable
46
+ ) {
47
+ users(first: $first, skip: 50) {
48
+ id
49
+ }
50
+ }
51
+ `,
52
+ },
53
+ ],
54
+ },
55
+ messages: {
56
+ [RULE_ID]: '{{ type }} `{{ fieldName }}` defined multiple times.',
57
+ },
58
+ schema: [],
59
+ },
60
+ create(context) {
61
+ function checkNode(usedFields, node) {
62
+ const fieldName = node.value;
63
+ if (usedFields.has(fieldName)) {
64
+ const { parent } = node;
65
+ context.report({
66
+ node,
67
+ messageId: RULE_ID,
68
+ data: {
69
+ type: parent.type,
70
+ fieldName,
71
+ },
72
+ suggest: [
73
+ {
74
+ desc: `Remove \`${fieldName}\` ${parent.type.toLowerCase()}`,
75
+ fix(fixer) {
76
+ return fixer.remove((parent.type === Kind.VARIABLE ? parent.parent : parent));
77
+ },
78
+ },
79
+ ],
80
+ });
81
+ }
82
+ else {
83
+ usedFields.add(fieldName);
84
+ }
85
+ }
86
+ return {
87
+ OperationDefinition(node) {
88
+ const set = new Set();
89
+ for (const varDef of node.variableDefinitions) {
90
+ checkNode(set, varDef.variable.name);
91
+ }
92
+ },
93
+ Field(node) {
94
+ const set = new Set();
95
+ for (const arg of node.arguments) {
96
+ checkNode(set, arg.name);
97
+ }
98
+ },
99
+ SelectionSet(node) {
100
+ const set = new Set();
101
+ for (const selection of node.selections) {
102
+ if (selection.kind === Kind.FIELD) {
103
+ checkNode(set, selection.alias || selection.name);
104
+ }
105
+ }
106
+ },
107
+ };
108
+ },
109
+ };