@graphql-eslint/eslint-plugin 2.1.0-alpha-b4cd82d.0 → 2.3.0-alpha-6ba4002.0

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 (58) hide show
  1. package/README.md +38 -18
  2. package/configs/all.d.ts +61 -0
  3. package/configs/index.d.ts +107 -0
  4. package/configs/recommended.d.ts +44 -0
  5. package/docs/README.md +64 -60
  6. package/docs/rules/avoid-scalar-result-type-on-mutation.md +30 -0
  7. package/docs/rules/avoid-typename-prefix.md +2 -0
  8. package/docs/rules/executable-definitions.md +3 -1
  9. package/docs/rules/fields-on-correct-type.md +3 -1
  10. package/docs/rules/fragments-on-composite-type.md +3 -1
  11. package/docs/rules/known-argument-names.md +3 -1
  12. package/docs/rules/known-directives.md +3 -1
  13. package/docs/rules/known-fragment-names.md +3 -1
  14. package/docs/rules/known-type-names.md +3 -1
  15. package/docs/rules/lone-anonymous-operation.md +3 -1
  16. package/docs/rules/lone-schema-definition.md +3 -1
  17. package/docs/rules/match-document-filename.md +1 -1
  18. package/docs/rules/naming-convention.md +2 -0
  19. package/docs/rules/no-anonymous-operations.md +2 -0
  20. package/docs/rules/no-case-insensitive-enum-values-duplicates.md +5 -1
  21. package/docs/rules/no-fragment-cycles.md +3 -1
  22. package/docs/rules/no-operation-name-suffix.md +4 -0
  23. package/docs/rules/no-undefined-variables.md +3 -1
  24. package/docs/rules/no-unreachable-types.md +2 -0
  25. package/docs/rules/no-unused-fields.md +2 -0
  26. package/docs/rules/no-unused-fragments.md +3 -1
  27. package/docs/rules/no-unused-variables.md +3 -1
  28. package/docs/rules/one-field-subscriptions.md +3 -1
  29. package/docs/rules/overlapping-fields-can-be-merged.md +3 -1
  30. package/docs/rules/possible-fragment-spread.md +3 -1
  31. package/docs/rules/possible-type-extension.md +3 -1
  32. package/docs/rules/provided-required-arguments.md +3 -1
  33. package/docs/rules/require-deprecation-date.md +53 -0
  34. package/docs/rules/require-deprecation-reason.md +2 -0
  35. package/docs/rules/require-field-of-type-query-in-mutation-result.md +42 -0
  36. package/docs/rules/scalar-leafs.md +3 -1
  37. package/docs/rules/strict-id-in-types.md +2 -0
  38. package/docs/rules/unique-argument-names.md +3 -1
  39. package/docs/rules/unique-directive-names-per-location.md +3 -1
  40. package/docs/rules/unique-directive-names.md +3 -1
  41. package/docs/rules/unique-enum-value-names.md +3 -1
  42. package/docs/rules/unique-field-definition-names.md +3 -1
  43. package/docs/rules/unique-input-field-names.md +3 -1
  44. package/docs/rules/unique-operation-types.md +3 -1
  45. package/docs/rules/unique-type-names.md +3 -1
  46. package/docs/rules/unique-variable-names.md +3 -1
  47. package/docs/rules/value-literals-of-correct-type.md +3 -1
  48. package/docs/rules/variables-are-input-types.md +3 -1
  49. package/docs/rules/variables-in-allowed-position.md +3 -1
  50. package/index.d.ts +1 -0
  51. package/index.js +389 -74
  52. package/index.mjs +390 -76
  53. package/package.json +1 -1
  54. package/rules/avoid-scalar-result-type-on-mutation.d.ts +3 -0
  55. package/rules/index.d.ts +5 -0
  56. package/rules/require-deprecation-date.d.ts +5 -0
  57. package/rules/require-field-of-type-query-in-mutation-result.d.ts +3 -0
  58. package/utils.d.ts +9 -4
package/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { Source, validate, Kind, TokenKind, isNonNullType, isListType, visit, visitWithTypeInfo, GraphQLObjectType, GraphQLInterfaceType, TypeInfo, isInterfaceType, GraphQLError } from 'graphql';
1
+ import { Kind, Source, validate, isScalarType, TokenKind, isNonNullType, isListType, isObjectType as isObjectType$1, visit, visitWithTypeInfo, GraphQLObjectType, GraphQLInterfaceType, TypeInfo, isInterfaceType, GraphQLError } from 'graphql';
2
2
  import { validateSDL } from 'graphql/validation/validate';
3
3
  import { processImport, parseImportLine } from '@graphql-tools/import';
4
4
  import { statSync, existsSync, readFileSync } from 'fs';
@@ -10,6 +10,86 @@ import { parseCode } from '@graphql-tools/graphql-tag-pluck';
10
10
  import { loadConfigSync, GraphQLConfig } from 'graphql-config';
11
11
  import { CodeFileLoader } from '@graphql-tools/code-file-loader';
12
12
 
13
+ /*
14
+ * 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
15
+ */
16
+ const recommendedConfig = {
17
+ parser: '@graphql-eslint/eslint-plugin',
18
+ plugins: ['@graphql-eslint'],
19
+ rules: {
20
+ '@graphql-eslint/avoid-typename-prefix': 'error',
21
+ '@graphql-eslint/executable-definitions': 'error',
22
+ '@graphql-eslint/fields-on-correct-type': 'error',
23
+ '@graphql-eslint/fragments-on-composite-type': 'error',
24
+ '@graphql-eslint/known-argument-names': 'error',
25
+ '@graphql-eslint/known-directives': 'error',
26
+ '@graphql-eslint/known-fragment-names': 'error',
27
+ '@graphql-eslint/known-type-names': 'error',
28
+ '@graphql-eslint/lone-anonymous-operation': 'error',
29
+ '@graphql-eslint/lone-schema-definition': 'error',
30
+ '@graphql-eslint/naming-convention': 'error',
31
+ '@graphql-eslint/no-anonymous-operations': 'error',
32
+ '@graphql-eslint/no-case-insensitive-enum-values-duplicates': 'error',
33
+ '@graphql-eslint/no-fragment-cycles': 'error',
34
+ '@graphql-eslint/no-operation-name-suffix': 'error',
35
+ '@graphql-eslint/no-undefined-variables': 'error',
36
+ '@graphql-eslint/no-unused-fragments': 'error',
37
+ '@graphql-eslint/no-unused-variables': 'error',
38
+ '@graphql-eslint/one-field-subscriptions': 'error',
39
+ '@graphql-eslint/overlapping-fields-can-be-merged': 'error',
40
+ '@graphql-eslint/possible-fragment-spread': 'error',
41
+ '@graphql-eslint/possible-type-extension': 'error',
42
+ '@graphql-eslint/provided-required-arguments': 'error',
43
+ '@graphql-eslint/require-deprecation-reason': 'error',
44
+ '@graphql-eslint/scalar-leafs': 'error',
45
+ '@graphql-eslint/strict-id-in-types': 'error',
46
+ '@graphql-eslint/unique-argument-names': 'error',
47
+ '@graphql-eslint/unique-directive-names': 'error',
48
+ '@graphql-eslint/unique-directive-names-per-location': 'error',
49
+ '@graphql-eslint/unique-enum-value-names': 'error',
50
+ '@graphql-eslint/unique-field-definition-names': 'error',
51
+ '@graphql-eslint/unique-input-field-names': 'error',
52
+ '@graphql-eslint/unique-operation-types': 'error',
53
+ '@graphql-eslint/unique-type-names': 'error',
54
+ '@graphql-eslint/unique-variable-names': 'error',
55
+ '@graphql-eslint/value-literals-of-correct-type': 'error',
56
+ '@graphql-eslint/variables-are-input-types': 'error',
57
+ '@graphql-eslint/variables-in-allowed-position': 'error',
58
+ },
59
+ };
60
+
61
+ /*
62
+ * 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
63
+ */
64
+ const allConfig = {
65
+ ...recommendedConfig,
66
+ rules: {
67
+ ...recommendedConfig.rules,
68
+ '@graphql-eslint/avoid-duplicate-fields': 'error',
69
+ '@graphql-eslint/avoid-operation-name-prefix': 'error',
70
+ '@graphql-eslint/avoid-scalar-result-type-on-mutation': 'error',
71
+ '@graphql-eslint/description-style': 'error',
72
+ '@graphql-eslint/input-name': 'error',
73
+ '@graphql-eslint/match-document-filename': 'error',
74
+ '@graphql-eslint/no-deprecated': 'error',
75
+ '@graphql-eslint/no-hashtag-description': 'error',
76
+ '@graphql-eslint/no-unreachable-types': 'error',
77
+ '@graphql-eslint/no-unused-fields': 'error',
78
+ '@graphql-eslint/require-deprecation-date': 'error',
79
+ '@graphql-eslint/require-description': 'error',
80
+ '@graphql-eslint/require-field-of-type-query-in-mutation-result': 'error',
81
+ '@graphql-eslint/require-id-when-available': 'error',
82
+ '@graphql-eslint/selection-set-depth': 'error',
83
+ '@graphql-eslint/unique-fragment-name': 'error',
84
+ '@graphql-eslint/unique-operation-name': 'error',
85
+ },
86
+ };
87
+
88
+ const configs = {
89
+ all: allConfig,
90
+ recommended: recommendedConfig,
91
+ };
92
+
13
93
  function requireSiblingsOperations(ruleName, context) {
14
94
  if (!context.parserServices) {
15
95
  throw new Error(`Rule '${ruleName}' requires 'parserOptions.operations' to be set and loaded. See http://bit.ly/graphql-eslint-operations for more info`);
@@ -95,6 +175,7 @@ const getOnDiskFilepath = (filepath) => {
95
175
  }
96
176
  return filepath;
97
177
  };
178
+ const getTypeName = node => ('type' in node ? getTypeName(node.type) : node.name.value);
98
179
  // Small workaround for the bug in older versions of @graphql-tools/load
99
180
  // Can be removed after graphql-config bumps to a new version
100
181
  const loaderCache = new Proxy(Object.create(null), {
@@ -112,7 +193,7 @@ const loaderCache = new Proxy(Object.create(null), {
112
193
  return true;
113
194
  },
114
195
  });
115
- const isObjectType = (node) => ['ObjectTypeDefinition', 'ObjectTypeExtension'].includes(node.type);
196
+ const isObjectType = (node) => [Kind.OBJECT_TYPE_DEFINITION, Kind.OBJECT_TYPE_EXTENSION].includes(node.type);
116
197
  const isQueryType = (node) => isObjectType(node) && node.name.value === 'Query';
117
198
  const isMutationType = (node) => isObjectType(node) && node.name.value === 'Mutation';
118
199
  var CaseStyle;
@@ -123,16 +204,18 @@ var CaseStyle;
123
204
  CaseStyle["upperCase"] = "UPPER_CASE";
124
205
  CaseStyle["kebabCase"] = "kebab-case";
125
206
  })(CaseStyle || (CaseStyle = {}));
207
+ const pascalCase = (str) => lowerCase(str)
208
+ .split(' ')
209
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
210
+ .join('');
211
+ const camelCase = (str) => {
212
+ const result = pascalCase(str);
213
+ return result.charAt(0).toLowerCase() + result.slice(1);
214
+ };
126
215
  const convertCase = (style, str) => {
127
- const pascalCase = (str) => lowerCase(str)
128
- .split(' ')
129
- .map(word => word.charAt(0).toUpperCase() + word.slice(1))
130
- .join('');
131
216
  switch (style) {
132
- case CaseStyle.camelCase: {
133
- const result = pascalCase(str);
134
- return result.charAt(0).toLowerCase() + result.slice(1);
135
- }
217
+ case CaseStyle.camelCase:
218
+ return camelCase(str);
136
219
  case CaseStyle.pascalCase:
137
220
  return pascalCase(str);
138
221
  case CaseStyle.snakeCase:
@@ -194,9 +277,10 @@ const validationToRule = (name, ruleName, docs, getDocumentNode) => {
194
277
  docs: {
195
278
  ...docs,
196
279
  category: 'Validation',
280
+ recommended: true,
197
281
  requiresSchema,
198
282
  url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${name}.md`,
199
- description: `${docs.description}\n\n> This rule is a wrapper around a \`graphql-js\` validation function. [You can find it's source code here](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/${ruleName}Rule.ts).`,
283
+ description: `${docs.description}\n\n> This rule is a wrapper around a \`graphql-js\` validation function. [You can find its source code here](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/${ruleName}Rule.ts).`,
200
284
  },
201
285
  },
202
286
  create(context) {
@@ -220,6 +304,14 @@ const validationToRule = (name, ruleName, docs, getDocumentNode) => {
220
304
  },
221
305
  };
222
306
  };
307
+ const importFiles = (context) => {
308
+ const code = context.getSourceCode().text;
309
+ if (!isGraphQLImportFile(code)) {
310
+ return null;
311
+ }
312
+ // Import documents because file contains '#import' comments
313
+ return processImport(context.getFilename());
314
+ };
223
315
  const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-definitions', 'ExecutableDefinitions', {
224
316
  description: `A GraphQL document is only valid for execution if all definitions are either operation or fragment definitions.`,
225
317
  }), validationToRule('fields-on-correct-type', 'FieldsOnCorrectType', {
@@ -296,14 +388,7 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
296
388
  \``,
297
389
  },
298
390
  ],
299
- }, context => {
300
- const code = context.getSourceCode().text;
301
- if (!isGraphQLImportFile(code)) {
302
- return null;
303
- }
304
- // Import documents because file contains '#import' comments
305
- return processImport(context.getFilename());
306
- }), validationToRule('known-type-names', 'KnownTypeNames', {
391
+ }, importFiles), validationToRule('known-type-names', 'KnownTypeNames', {
307
392
  description: `A GraphQL document is only valid if referenced types (specifically variable definitions and fragment conditions) are defined by the type schema.`,
308
393
  }), validationToRule('lone-anonymous-operation', 'LoneAnonymousOperation', {
309
394
  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.`,
@@ -345,7 +430,7 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
345
430
  return getParentNode(context.getFilename());
346
431
  }), validationToRule('no-unused-variables', 'NoUnusedVariables', {
347
432
  description: `A GraphQL operation is only valid if all variables defined by an operation are used, either directly or within a spread fragment.`,
348
- }), validationToRule('overlapping-fields-can-be-merged', 'OverlappingFieldsCanBeMerged', {
433
+ }, importFiles), validationToRule('overlapping-fields-can-be-merged', 'OverlappingFieldsCanBeMerged', {
349
434
  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.`,
350
435
  }), validationToRule('possible-fragment-spread', 'PossibleFragmentSpreads', {
351
436
  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.`,
@@ -598,8 +683,59 @@ const rule$1 = {
598
683
  },
599
684
  };
600
685
 
601
- const AVOID_TYPENAME_PREFIX = 'AVOID_TYPENAME_PREFIX';
602
686
  const rule$2 = {
687
+ meta: {
688
+ type: 'suggestion',
689
+ docs: {
690
+ category: 'Best Practices',
691
+ description: 'Avoid scalar result type on mutation type to make sure to return a valid state.',
692
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-scalar-result-type-on-mutation.md',
693
+ requiresSchema: true,
694
+ examples: [
695
+ {
696
+ title: 'Incorrect',
697
+ code: /* GraphQL */ `
698
+ type Mutation {
699
+ createUser: Boolean
700
+ }
701
+ `,
702
+ },
703
+ {
704
+ title: 'Correct',
705
+ code: /* GraphQL */ `
706
+ type Mutation {
707
+ createUser: User!
708
+ }
709
+ `,
710
+ },
711
+ ],
712
+ },
713
+ },
714
+ create(context) {
715
+ const schema = requireGraphQLSchemaFromContext('avoid-scalar-result-type-on-mutation', context);
716
+ const mutationType = schema.getMutationType();
717
+ if (!mutationType) {
718
+ return {};
719
+ }
720
+ const selector = `:matches(${Kind.OBJECT_TYPE_DEFINITION}, ${Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}] > ${Kind.FIELD_DEFINITION}`;
721
+ return {
722
+ [selector](node) {
723
+ const rawNode = node.rawNode();
724
+ const typeName = getTypeName(rawNode);
725
+ const graphQLType = schema.getType(typeName);
726
+ if (isScalarType(graphQLType)) {
727
+ context.report({
728
+ node,
729
+ message: `Unexpected scalar result type "${typeName}".`,
730
+ });
731
+ }
732
+ },
733
+ };
734
+ },
735
+ };
736
+
737
+ const AVOID_TYPENAME_PREFIX = 'AVOID_TYPENAME_PREFIX';
738
+ const rule$3 = {
603
739
  meta: {
604
740
  type: 'suggestion',
605
741
  docs: {
@@ -653,7 +789,7 @@ const rule$2 = {
653
789
  },
654
790
  };
655
791
 
656
- const rule$3 = {
792
+ const rule$4 = {
657
793
  meta: {
658
794
  type: 'suggestion',
659
795
  docs: {
@@ -713,7 +849,7 @@ const rule$3 = {
713
849
  },
714
850
  };
715
851
 
716
- const rule$4 = {
852
+ const rule$5 = {
717
853
  meta: {
718
854
  type: 'suggestion',
719
855
  docs: {
@@ -838,12 +974,12 @@ const CASE_STYLES = [
838
974
  const schemaOption = {
839
975
  oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
840
976
  };
841
- const rule$5 = {
977
+ const rule$6 = {
842
978
  meta: {
843
979
  type: 'suggestion',
844
980
  docs: {
845
981
  category: 'Best Practices',
846
- description: 'This rule allows you to enforce that the file name should match the operation name',
982
+ description: 'This rule allows you to enforce that the file name should match the operation name.',
847
983
  url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/match-document-filename.md`,
848
984
  examples: [
849
985
  {
@@ -1050,7 +1186,7 @@ function checkNameFormat(params) {
1050
1186
  errorMessage: '{{nodeType}} name "{{nodeName}}" should have "{{suffix}}" suffix',
1051
1187
  };
1052
1188
  }
1053
- if (style && !acceptedStyles.some(acceptedStyle => acceptedStyle === style)) {
1189
+ if (style && !acceptedStyles.includes(style)) {
1054
1190
  return {
1055
1191
  ok: false,
1056
1192
  errorMessage: `{{nodeType}} name "{{nodeName}}" should be in one of the following options: ${acceptedStyles.join(',')}`,
@@ -1083,7 +1219,7 @@ function checkNameFormat(params) {
1083
1219
  const schemaOption$1 = {
1084
1220
  oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
1085
1221
  };
1086
- const rule$6 = {
1222
+ const rule$7 = {
1087
1223
  meta: {
1088
1224
  type: 'suggestion',
1089
1225
  docs: {
@@ -1312,7 +1448,7 @@ const rule$6 = {
1312
1448
  };
1313
1449
 
1314
1450
  const NO_ANONYMOUS_OPERATIONS = 'NO_ANONYMOUS_OPERATIONS';
1315
- const rule$7 = {
1451
+ const rule$8 = {
1316
1452
  meta: {
1317
1453
  type: 'suggestion',
1318
1454
  docs: {
@@ -1370,13 +1506,14 @@ const rule$7 = {
1370
1506
  };
1371
1507
 
1372
1508
  const ERROR_MESSAGE_ID = 'NO_CASE_INSENSITIVE_ENUM_VALUES_DUPLICATES';
1373
- const rule$8 = {
1509
+ const rule$9 = {
1374
1510
  meta: {
1375
1511
  type: 'suggestion',
1376
1512
  docs: {
1377
1513
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-case-insensitive-enum-values-duplicates.md',
1378
1514
  category: 'Best Practices',
1379
1515
  recommended: true,
1516
+ description: 'Disallow case-insensitive enum values duplicates.',
1380
1517
  examples: [
1381
1518
  {
1382
1519
  title: 'Incorrect',
@@ -1424,7 +1561,7 @@ const rule$8 = {
1424
1561
  };
1425
1562
 
1426
1563
  const NO_DEPRECATED = 'NO_DEPRECATED';
1427
- const rule$9 = {
1564
+ const rule$a = {
1428
1565
  meta: {
1429
1566
  type: 'suggestion',
1430
1567
  docs: {
@@ -1538,7 +1675,7 @@ const rule$9 = {
1538
1675
  };
1539
1676
 
1540
1677
  const HASHTAG_COMMENT = 'HASHTAG_COMMENT';
1541
- const rule$a = {
1678
+ const rule$b = {
1542
1679
  meta: {
1543
1680
  messages: {
1544
1681
  [HASHTAG_COMMENT]: 'Using hashtag (#) for adding GraphQL descriptions is not allowed. Prefer using """ for multiline, or " for a single line description.',
@@ -1613,7 +1750,7 @@ const rule$a = {
1613
1750
  };
1614
1751
 
1615
1752
  const NO_OPERATION_NAME_SUFFIX = 'NO_OPERATION_NAME_SUFFIX';
1616
- const rule$b = {
1753
+ const rule$c = {
1617
1754
  meta: {
1618
1755
  fixable: 'code',
1619
1756
  type: 'suggestion',
@@ -1668,7 +1805,7 @@ const rule$b = {
1668
1805
 
1669
1806
  const UNREACHABLE_TYPE = 'UNREACHABLE_TYPE';
1670
1807
  const RULE_NAME = 'no-unreachable-types';
1671
- const rule$c = {
1808
+ const rule$d = {
1672
1809
  meta: {
1673
1810
  messages: {
1674
1811
  [UNREACHABLE_TYPE]: `Type "{{ typeName }}" is unreachable`,
@@ -1743,7 +1880,7 @@ const rule$c = {
1743
1880
 
1744
1881
  const UNUSED_FIELD = 'UNUSED_FIELD';
1745
1882
  const RULE_NAME$1 = 'no-unused-fields';
1746
- const rule$d = {
1883
+ const rule$e = {
1747
1884
  meta: {
1748
1885
  messages: {
1749
1886
  [UNUSED_FIELD]: `Field "{{fieldName}}" is unused`,
@@ -1925,7 +2062,112 @@ function convertDescription(node) {
1925
2062
  return [];
1926
2063
  }
1927
2064
 
1928
- const rule$e = {
2065
+ const DATE_REGEX = /^\d{2}\/\d{2}\/\d{4}$/;
2066
+ const MESSAGE_REQUIRE_DATE = 'MESSAGE_REQUIRE_DATE';
2067
+ const MESSAGE_INVALID_FORMAT = 'MESSAGE_INVALID_FORMAT';
2068
+ const MESSAGE_INVALID_DATE = 'MESSAGE_INVALID_DATE';
2069
+ const MESSAGE_CAN_BE_REMOVED = 'MESSAGE_CAN_BE_REMOVED';
2070
+ const rule$f = {
2071
+ meta: {
2072
+ type: 'suggestion',
2073
+ docs: {
2074
+ category: 'Best Practices',
2075
+ description: 'Require deletion date on `@deprecated` directive. Suggest removing deprecated things after deprecated date.',
2076
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-deprecation-date.md',
2077
+ examples: [
2078
+ {
2079
+ title: 'Incorrect',
2080
+ code: /* GraphQL */ `
2081
+ type User {
2082
+ firstname: String @deprecated
2083
+ firstName: String
2084
+ }
2085
+ `,
2086
+ },
2087
+ {
2088
+ title: 'Incorrect',
2089
+ code: /* GraphQL */ `
2090
+ type User {
2091
+ firstname: String @deprecated(reason: "Use 'firstName' instead")
2092
+ firstName: String
2093
+ }
2094
+ `,
2095
+ },
2096
+ {
2097
+ title: 'Correct',
2098
+ code: /* GraphQL */ `
2099
+ type User {
2100
+ firstname: String @deprecated(reason: "Use 'firstName' instead", deletionDate: "25/12/2022")
2101
+ firstName: String
2102
+ }
2103
+ `,
2104
+ },
2105
+ ],
2106
+ },
2107
+ messages: {
2108
+ [MESSAGE_REQUIRE_DATE]: 'Directive "@deprecated" must have a deletion date.',
2109
+ [MESSAGE_INVALID_FORMAT]: 'Deletion date must be in format "DD/MM/YYYY".',
2110
+ [MESSAGE_INVALID_DATE]: 'Invalid "{{ deletionDate }}" deletion date.',
2111
+ [MESSAGE_CAN_BE_REMOVED]: '"{{ nodeName }}" сan be removed.',
2112
+ },
2113
+ schema: [
2114
+ {
2115
+ type: 'object',
2116
+ additionalProperties: false,
2117
+ properties: {
2118
+ argumentName: {
2119
+ type: 'string',
2120
+ },
2121
+ },
2122
+ },
2123
+ ],
2124
+ },
2125
+ create(context) {
2126
+ return {
2127
+ 'Directive[name.value=deprecated]'(node) {
2128
+ var _a;
2129
+ const argName = ((_a = context.options[0]) === null || _a === void 0 ? void 0 : _a.argumentName) || 'deletionDate';
2130
+ const deletionDateNode = node.arguments.find(arg => arg.name.value === argName);
2131
+ if (!deletionDateNode) {
2132
+ context.report({ node: node.name, messageId: MESSAGE_REQUIRE_DATE });
2133
+ return;
2134
+ }
2135
+ const deletionDate = valueFromNode(deletionDateNode.value);
2136
+ const isValidDate = DATE_REGEX.test(deletionDate);
2137
+ if (!isValidDate) {
2138
+ context.report({ node: node.name, messageId: MESSAGE_INVALID_FORMAT });
2139
+ return;
2140
+ }
2141
+ let [day, month, year] = deletionDate.split('/');
2142
+ day = day.toString().padStart(2, '0');
2143
+ month = month.toString().padStart(2, '0');
2144
+ const deletionDateInMS = Date.parse(`${year}-${month}-${day}`);
2145
+ if (Number.isNaN(deletionDateInMS)) {
2146
+ context.report({
2147
+ node: node.name,
2148
+ messageId: MESSAGE_INVALID_DATE,
2149
+ data: {
2150
+ deletionDate,
2151
+ },
2152
+ });
2153
+ return;
2154
+ }
2155
+ const canRemove = Date.now() > deletionDateInMS;
2156
+ if (canRemove) {
2157
+ context.report({
2158
+ node: node.name,
2159
+ messageId: MESSAGE_CAN_BE_REMOVED,
2160
+ data: {
2161
+ nodeName: node.parent.name.value,
2162
+ },
2163
+ });
2164
+ }
2165
+ },
2166
+ };
2167
+ },
2168
+ };
2169
+
2170
+ const rule$g = {
1929
2171
  meta: {
1930
2172
  docs: {
1931
2173
  description: `Require all deprecation directives to specify a reason.`,
@@ -1967,11 +2209,11 @@ const rule$e = {
1967
2209
  if (node && node.name && node.name.value === 'deprecated') {
1968
2210
  const args = node.arguments || [];
1969
2211
  const reasonArg = args.find(arg => arg.name && arg.name.value === 'reason');
1970
- const value = reasonArg ? String(valueFromNode(reasonArg.value, {}) || '').trim() : null;
2212
+ const value = reasonArg ? String(valueFromNode(reasonArg.value) || '').trim() : null;
1971
2213
  if (!value) {
1972
2214
  context.report({
1973
2215
  node: node.name,
1974
- message: `Directive "@deprecated" must have a reason!`,
2216
+ message: 'Directive "@deprecated" must have a reason!',
1975
2217
  });
1976
2218
  }
1977
2219
  }
@@ -2015,13 +2257,12 @@ function verifyRule(context, node) {
2015
2257
  }
2016
2258
  }
2017
2259
  }
2018
- const rule$f = {
2260
+ const rule$h = {
2019
2261
  meta: {
2020
2262
  docs: {
2021
2263
  category: 'Best Practices',
2022
2264
  description: `Enforce descriptions in your type definitions.`,
2023
2265
  url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-description.md`,
2024
- recommended: true,
2025
2266
  examples: [
2026
2267
  {
2027
2268
  title: 'Incorrect',
@@ -2080,6 +2321,74 @@ const rule$f = {
2080
2321
  },
2081
2322
  };
2082
2323
 
2324
+ const RULE_NAME$2 = 'require-field-of-type-query-in-mutation-result';
2325
+ const rule$i = {
2326
+ meta: {
2327
+ type: 'suggestion',
2328
+ docs: {
2329
+ category: 'Best Practices',
2330
+ 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`.',
2331
+ url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$2}.md`,
2332
+ requiresSchema: true,
2333
+ examples: [
2334
+ {
2335
+ title: 'Incorrect',
2336
+ code: /* GraphQL */ `
2337
+ type User { ... }
2338
+
2339
+ type Mutation {
2340
+ createUser: User!
2341
+ }
2342
+ `,
2343
+ },
2344
+ {
2345
+ title: 'Correct',
2346
+ code: /* GraphQL */ `
2347
+ type User { ... }
2348
+
2349
+ type Query { ... }
2350
+
2351
+ type CreateUserPayload {
2352
+ user: User!
2353
+ query: Query!
2354
+ }
2355
+
2356
+ type Mutation {
2357
+ createUser: CreateUserPayload!
2358
+ }
2359
+ `,
2360
+ },
2361
+ ],
2362
+ },
2363
+ },
2364
+ create(context) {
2365
+ const schema = requireGraphQLSchemaFromContext(RULE_NAME$2, context);
2366
+ const mutationType = schema.getMutationType();
2367
+ const queryType = schema.getQueryType();
2368
+ if (!mutationType || !queryType) {
2369
+ return {};
2370
+ }
2371
+ const selector = `:matches(${Kind.OBJECT_TYPE_DEFINITION}, ${Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}] > ${Kind.FIELD_DEFINITION}`;
2372
+ return {
2373
+ [selector](node) {
2374
+ const rawNode = node.rawNode();
2375
+ const typeName = getTypeName(rawNode);
2376
+ const graphQLType = schema.getType(typeName);
2377
+ if (isObjectType$1(graphQLType)) {
2378
+ const { fields } = graphQLType.astNode;
2379
+ const hasQueryType = fields.some(field => getTypeName(field) === queryType.name);
2380
+ if (!hasQueryType) {
2381
+ context.report({
2382
+ node,
2383
+ message: `Mutation result type "${graphQLType.name}" must contain field of type "${queryType.name}".`,
2384
+ });
2385
+ }
2386
+ }
2387
+ },
2388
+ };
2389
+ },
2390
+ };
2391
+
2083
2392
  function convertToESTree(node, typeInfo) {
2084
2393
  const visitor = { leave: convertNode(typeInfo) };
2085
2394
  return {
@@ -2176,7 +2485,7 @@ const convertNode = (typeInfo) => (node, key, parent) => {
2176
2485
 
2177
2486
  const REQUIRE_ID_WHEN_AVAILABLE = 'REQUIRE_ID_WHEN_AVAILABLE';
2178
2487
  const DEFAULT_ID_FIELD_NAME = 'id';
2179
- const rule$g = {
2488
+ const rule$j = {
2180
2489
  meta: {
2181
2490
  type: 'suggestion',
2182
2491
  docs: {
@@ -2266,13 +2575,13 @@ const rule$g = {
2266
2575
  found = true;
2267
2576
  }
2268
2577
  else if (selection.kind === 'InlineFragment') {
2269
- found = !!(((_a = selection.selectionSet) === null || _a === void 0 ? void 0 : _a.selections) || []).find(s => s.kind === 'Field' && s.name.value === fieldName);
2578
+ found = (((_a = selection.selectionSet) === null || _a === void 0 ? void 0 : _a.selections) || []).some(s => s.kind === 'Field' && s.name.value === fieldName);
2270
2579
  }
2271
2580
  else if (selection.kind === 'FragmentSpread') {
2272
2581
  const foundSpread = siblings.getFragment(selection.name.value);
2273
2582
  if (foundSpread[0]) {
2274
2583
  checkedFragmentSpreads.add(foundSpread[0].document.name.value);
2275
- found = !!(((_b = foundSpread[0].document.selectionSet) === null || _b === void 0 ? void 0 : _b.selections) || []).find(s => s.kind === 'Field' && s.name.value === fieldName);
2584
+ found = (((_b = foundSpread[0].document.selectionSet) === null || _b === void 0 ? void 0 : _b.selections) || []).some(s => s.kind === 'Field' && s.name.value === fieldName);
2276
2585
  }
2277
2586
  }
2278
2587
  if (found) {
@@ -2284,7 +2593,7 @@ const rule$g = {
2284
2593
  parent.kind === 'InlineFragment' &&
2285
2594
  parent.parent &&
2286
2595
  parent.parent.kind === 'SelectionSet' &&
2287
- !!parent.parent.selections.find(s => s.kind === 'Field' && s.name.value === fieldName);
2596
+ parent.parent.selections.some(s => s.kind === 'Field' && s.name.value === fieldName);
2288
2597
  if (!found && !hasIdFieldInInterfaceSelectionSet) {
2289
2598
  context.report({
2290
2599
  loc: {
@@ -2312,7 +2621,7 @@ const rule$g = {
2312
2621
  },
2313
2622
  };
2314
2623
 
2315
- const rule$h = {
2624
+ const rule$k = {
2316
2625
  meta: {
2317
2626
  docs: {
2318
2627
  category: 'Best Practices',
@@ -2426,7 +2735,7 @@ const rule$h = {
2426
2735
 
2427
2736
  const shouldIgnoreNode = ({ node, exceptions }) => {
2428
2737
  const rawNode = node.rawNode();
2429
- if (exceptions.types && exceptions.types.some(type => rawNode.name.value === type)) {
2738
+ if (exceptions.types && exceptions.types.includes(rawNode.name.value)) {
2430
2739
  return true;
2431
2740
  }
2432
2741
  if (exceptions.suffixes && exceptions.suffixes.some(suffix => rawNode.name.value.endsWith(suffix))) {
@@ -2434,7 +2743,7 @@ const shouldIgnoreNode = ({ node, exceptions }) => {
2434
2743
  }
2435
2744
  return false;
2436
2745
  };
2437
- const rule$i = {
2746
+ const rule$l = {
2438
2747
  meta: {
2439
2748
  type: 'suggestion',
2440
2749
  docs: {
@@ -2584,7 +2893,7 @@ const rule$i = {
2584
2893
  },
2585
2894
  };
2586
2895
 
2587
- const RULE_NAME$2 = 'unique-fragment-name';
2896
+ const RULE_NAME$3 = 'unique-fragment-name';
2588
2897
  const UNIQUE_FRAGMENT_NAME = 'UNIQUE_FRAGMENT_NAME';
2589
2898
  const checkNode = (context, node, ruleName, messageId) => {
2590
2899
  var _a;
@@ -2624,13 +2933,13 @@ const checkNode = (context, node, ruleName, messageId) => {
2624
2933
  });
2625
2934
  }
2626
2935
  };
2627
- const rule$j = {
2936
+ const rule$m = {
2628
2937
  meta: {
2629
2938
  type: 'suggestion',
2630
2939
  docs: {
2631
2940
  category: 'Best Practices',
2632
2941
  description: `Enforce unique fragment names across your project.`,
2633
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$2}.md`,
2942
+ url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$3}.md`,
2634
2943
  requiresSiblings: true,
2635
2944
  examples: [
2636
2945
  {
@@ -2674,21 +2983,21 @@ const rule$j = {
2674
2983
  create(context) {
2675
2984
  return {
2676
2985
  FragmentDefinition(node) {
2677
- checkNode(context, node, RULE_NAME$2, UNIQUE_FRAGMENT_NAME);
2986
+ checkNode(context, node, RULE_NAME$3, UNIQUE_FRAGMENT_NAME);
2678
2987
  },
2679
2988
  };
2680
2989
  },
2681
2990
  };
2682
2991
 
2683
- const RULE_NAME$3 = 'unique-operation-name';
2992
+ const RULE_NAME$4 = 'unique-operation-name';
2684
2993
  const UNIQUE_OPERATION_NAME = 'UNIQUE_OPERATION_NAME';
2685
- const rule$k = {
2994
+ const rule$n = {
2686
2995
  meta: {
2687
2996
  type: 'suggestion',
2688
2997
  docs: {
2689
2998
  category: 'Best Practices',
2690
2999
  description: `Enforce unique operation names across your project.`,
2691
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$3}.md`,
3000
+ url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$4}.md`,
2692
3001
  requiresSiblings: true,
2693
3002
  examples: [
2694
3003
  {
@@ -2736,35 +3045,41 @@ const rule$k = {
2736
3045
  create(context) {
2737
3046
  return {
2738
3047
  OperationDefinition(node) {
2739
- checkNode(context, node, RULE_NAME$3, UNIQUE_OPERATION_NAME);
3048
+ checkNode(context, node, RULE_NAME$4, UNIQUE_OPERATION_NAME);
2740
3049
  },
2741
3050
  };
2742
3051
  },
2743
3052
  };
2744
3053
 
3054
+ /*
3055
+ * 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
3056
+ */
2745
3057
  const rules = {
3058
+ ...GRAPHQL_JS_VALIDATIONS,
2746
3059
  'avoid-duplicate-fields': rule,
2747
3060
  'avoid-operation-name-prefix': rule$1,
2748
- 'avoid-typename-prefix': rule$2,
2749
- 'description-style': rule$3,
2750
- 'input-name': rule$4,
2751
- 'match-document-filename': rule$5,
2752
- 'naming-convention': rule$6,
2753
- 'no-anonymous-operations': rule$7,
2754
- 'no-case-insensitive-enum-values-duplicates': rule$8,
2755
- 'no-deprecated': rule$9,
2756
- 'no-hashtag-description': rule$a,
2757
- 'no-operation-name-suffix': rule$b,
2758
- 'no-unreachable-types': rule$c,
2759
- 'no-unused-fields': rule$d,
2760
- 'require-deprecation-reason': rule$e,
2761
- 'require-description': rule$f,
2762
- 'require-id-when-available': rule$g,
2763
- 'selection-set-depth': rule$h,
2764
- 'strict-id-in-types': rule$i,
2765
- 'unique-fragment-name': rule$j,
2766
- 'unique-operation-name': rule$k,
2767
- ...GRAPHQL_JS_VALIDATIONS,
3061
+ 'avoid-scalar-result-type-on-mutation': rule$2,
3062
+ 'avoid-typename-prefix': rule$3,
3063
+ 'description-style': rule$4,
3064
+ 'input-name': rule$5,
3065
+ 'match-document-filename': rule$6,
3066
+ 'naming-convention': rule$7,
3067
+ 'no-anonymous-operations': rule$8,
3068
+ 'no-case-insensitive-enum-values-duplicates': rule$9,
3069
+ 'no-deprecated': rule$a,
3070
+ 'no-hashtag-description': rule$b,
3071
+ 'no-operation-name-suffix': rule$c,
3072
+ 'no-unreachable-types': rule$d,
3073
+ 'no-unused-fields': rule$e,
3074
+ 'require-deprecation-date': rule$f,
3075
+ 'require-deprecation-reason': rule$g,
3076
+ 'require-description': rule$h,
3077
+ 'require-field-of-type-query-in-mutation-result': rule$i,
3078
+ 'require-id-when-available': rule$j,
3079
+ 'selection-set-depth': rule$k,
3080
+ 'strict-id-in-types': rule$l,
3081
+ 'unique-fragment-name': rule$m,
3082
+ 'unique-operation-name': rule$n,
2768
3083
  };
2769
3084
 
2770
3085
  const RELEVANT_KEYWORDS = ['gql', 'graphql', '/* GraphQL */'];
@@ -3041,7 +3356,6 @@ function getReachableTypes(schema) {
3041
3356
  return reachableTypesCache;
3042
3357
  }
3043
3358
  const reachableTypes = new Set();
3044
- const getTypeName = node => ('type' in node ? getTypeName(node.type) : node.name.value);
3045
3359
  const collect = (node) => {
3046
3360
  const typeName = getTypeName(node);
3047
3361
  if (reachableTypes.has(typeName)) {
@@ -3177,4 +3491,4 @@ class GraphQLRuleTester extends require('eslint').RuleTester {
3177
3491
  }
3178
3492
  }
3179
3493
 
3180
- export { GraphQLRuleTester, convertDescription, convertLocation, convertRange, convertToESTree, extractCommentsFromAst, getBaseType, isNodeWithDescription, parse, parseForESLint, processors, rules, valueFromNode };
3494
+ export { GraphQLRuleTester, configs, convertDescription, convertLocation, convertRange, convertToESTree, extractCommentsFromAst, getBaseType, isNodeWithDescription, parse, parseForESLint, processors, rules, valueFromNode };