@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.js CHANGED
@@ -16,6 +16,86 @@ const graphqlTagPluck = require('@graphql-tools/graphql-tag-pluck');
16
16
  const graphqlConfig$1 = require('graphql-config');
17
17
  const codeFileLoader = require('@graphql-tools/code-file-loader');
18
18
 
19
+ /*
20
+ * 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
21
+ */
22
+ const recommendedConfig = {
23
+ parser: '@graphql-eslint/eslint-plugin',
24
+ plugins: ['@graphql-eslint'],
25
+ rules: {
26
+ '@graphql-eslint/avoid-typename-prefix': 'error',
27
+ '@graphql-eslint/executable-definitions': 'error',
28
+ '@graphql-eslint/fields-on-correct-type': 'error',
29
+ '@graphql-eslint/fragments-on-composite-type': 'error',
30
+ '@graphql-eslint/known-argument-names': 'error',
31
+ '@graphql-eslint/known-directives': 'error',
32
+ '@graphql-eslint/known-fragment-names': 'error',
33
+ '@graphql-eslint/known-type-names': 'error',
34
+ '@graphql-eslint/lone-anonymous-operation': 'error',
35
+ '@graphql-eslint/lone-schema-definition': 'error',
36
+ '@graphql-eslint/naming-convention': 'error',
37
+ '@graphql-eslint/no-anonymous-operations': 'error',
38
+ '@graphql-eslint/no-case-insensitive-enum-values-duplicates': 'error',
39
+ '@graphql-eslint/no-fragment-cycles': 'error',
40
+ '@graphql-eslint/no-operation-name-suffix': 'error',
41
+ '@graphql-eslint/no-undefined-variables': 'error',
42
+ '@graphql-eslint/no-unused-fragments': 'error',
43
+ '@graphql-eslint/no-unused-variables': 'error',
44
+ '@graphql-eslint/one-field-subscriptions': 'error',
45
+ '@graphql-eslint/overlapping-fields-can-be-merged': 'error',
46
+ '@graphql-eslint/possible-fragment-spread': 'error',
47
+ '@graphql-eslint/possible-type-extension': 'error',
48
+ '@graphql-eslint/provided-required-arguments': 'error',
49
+ '@graphql-eslint/require-deprecation-reason': 'error',
50
+ '@graphql-eslint/scalar-leafs': 'error',
51
+ '@graphql-eslint/strict-id-in-types': 'error',
52
+ '@graphql-eslint/unique-argument-names': 'error',
53
+ '@graphql-eslint/unique-directive-names': 'error',
54
+ '@graphql-eslint/unique-directive-names-per-location': 'error',
55
+ '@graphql-eslint/unique-enum-value-names': 'error',
56
+ '@graphql-eslint/unique-field-definition-names': 'error',
57
+ '@graphql-eslint/unique-input-field-names': 'error',
58
+ '@graphql-eslint/unique-operation-types': 'error',
59
+ '@graphql-eslint/unique-type-names': 'error',
60
+ '@graphql-eslint/unique-variable-names': 'error',
61
+ '@graphql-eslint/value-literals-of-correct-type': 'error',
62
+ '@graphql-eslint/variables-are-input-types': 'error',
63
+ '@graphql-eslint/variables-in-allowed-position': 'error',
64
+ },
65
+ };
66
+
67
+ /*
68
+ * 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
69
+ */
70
+ const allConfig = {
71
+ ...recommendedConfig,
72
+ rules: {
73
+ ...recommendedConfig.rules,
74
+ '@graphql-eslint/avoid-duplicate-fields': 'error',
75
+ '@graphql-eslint/avoid-operation-name-prefix': 'error',
76
+ '@graphql-eslint/avoid-scalar-result-type-on-mutation': 'error',
77
+ '@graphql-eslint/description-style': 'error',
78
+ '@graphql-eslint/input-name': 'error',
79
+ '@graphql-eslint/match-document-filename': 'error',
80
+ '@graphql-eslint/no-deprecated': 'error',
81
+ '@graphql-eslint/no-hashtag-description': 'error',
82
+ '@graphql-eslint/no-unreachable-types': 'error',
83
+ '@graphql-eslint/no-unused-fields': 'error',
84
+ '@graphql-eslint/require-deprecation-date': 'error',
85
+ '@graphql-eslint/require-description': 'error',
86
+ '@graphql-eslint/require-field-of-type-query-in-mutation-result': 'error',
87
+ '@graphql-eslint/require-id-when-available': 'error',
88
+ '@graphql-eslint/selection-set-depth': 'error',
89
+ '@graphql-eslint/unique-fragment-name': 'error',
90
+ '@graphql-eslint/unique-operation-name': 'error',
91
+ },
92
+ };
93
+
94
+ const configs = {
95
+ all: allConfig,
96
+ recommended: recommendedConfig,
97
+ };
98
+
19
99
  function requireSiblingsOperations(ruleName, context) {
20
100
  if (!context.parserServices) {
21
101
  throw new Error(`Rule '${ruleName}' requires 'parserOptions.operations' to be set and loaded. See http://bit.ly/graphql-eslint-operations for more info`);
@@ -101,6 +181,7 @@ const getOnDiskFilepath = (filepath) => {
101
181
  }
102
182
  return filepath;
103
183
  };
184
+ const getTypeName = node => ('type' in node ? getTypeName(node.type) : node.name.value);
104
185
  // Small workaround for the bug in older versions of @graphql-tools/load
105
186
  // Can be removed after graphql-config bumps to a new version
106
187
  const loaderCache = new Proxy(Object.create(null), {
@@ -118,7 +199,7 @@ const loaderCache = new Proxy(Object.create(null), {
118
199
  return true;
119
200
  },
120
201
  });
121
- const isObjectType = (node) => ['ObjectTypeDefinition', 'ObjectTypeExtension'].includes(node.type);
202
+ const isObjectType = (node) => [graphql.Kind.OBJECT_TYPE_DEFINITION, graphql.Kind.OBJECT_TYPE_EXTENSION].includes(node.type);
122
203
  const isQueryType = (node) => isObjectType(node) && node.name.value === 'Query';
123
204
  const isMutationType = (node) => isObjectType(node) && node.name.value === 'Mutation';
124
205
  var CaseStyle;
@@ -129,16 +210,18 @@ var CaseStyle;
129
210
  CaseStyle["upperCase"] = "UPPER_CASE";
130
211
  CaseStyle["kebabCase"] = "kebab-case";
131
212
  })(CaseStyle || (CaseStyle = {}));
213
+ const pascalCase = (str) => lowerCase(str)
214
+ .split(' ')
215
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
216
+ .join('');
217
+ const camelCase = (str) => {
218
+ const result = pascalCase(str);
219
+ return result.charAt(0).toLowerCase() + result.slice(1);
220
+ };
132
221
  const convertCase = (style, str) => {
133
- const pascalCase = (str) => lowerCase(str)
134
- .split(' ')
135
- .map(word => word.charAt(0).toUpperCase() + word.slice(1))
136
- .join('');
137
222
  switch (style) {
138
- case CaseStyle.camelCase: {
139
- const result = pascalCase(str);
140
- return result.charAt(0).toLowerCase() + result.slice(1);
141
- }
223
+ case CaseStyle.camelCase:
224
+ return camelCase(str);
142
225
  case CaseStyle.pascalCase:
143
226
  return pascalCase(str);
144
227
  case CaseStyle.snakeCase:
@@ -200,9 +283,10 @@ const validationToRule = (name, ruleName, docs, getDocumentNode) => {
200
283
  docs: {
201
284
  ...docs,
202
285
  category: 'Validation',
286
+ recommended: true,
203
287
  requiresSchema,
204
288
  url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${name}.md`,
205
- 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).`,
289
+ 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).`,
206
290
  },
207
291
  },
208
292
  create(context) {
@@ -226,6 +310,14 @@ const validationToRule = (name, ruleName, docs, getDocumentNode) => {
226
310
  },
227
311
  };
228
312
  };
313
+ const importFiles = (context) => {
314
+ const code = context.getSourceCode().text;
315
+ if (!isGraphQLImportFile(code)) {
316
+ return null;
317
+ }
318
+ // Import documents because file contains '#import' comments
319
+ return _import.processImport(context.getFilename());
320
+ };
229
321
  const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-definitions', 'ExecutableDefinitions', {
230
322
  description: `A GraphQL document is only valid for execution if all definitions are either operation or fragment definitions.`,
231
323
  }), validationToRule('fields-on-correct-type', 'FieldsOnCorrectType', {
@@ -302,14 +394,7 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
302
394
  \``,
303
395
  },
304
396
  ],
305
- }, context => {
306
- const code = context.getSourceCode().text;
307
- if (!isGraphQLImportFile(code)) {
308
- return null;
309
- }
310
- // Import documents because file contains '#import' comments
311
- return _import.processImport(context.getFilename());
312
- }), validationToRule('known-type-names', 'KnownTypeNames', {
397
+ }, importFiles), validationToRule('known-type-names', 'KnownTypeNames', {
313
398
  description: `A GraphQL document is only valid if referenced types (specifically variable definitions and fragment conditions) are defined by the type schema.`,
314
399
  }), validationToRule('lone-anonymous-operation', 'LoneAnonymousOperation', {
315
400
  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.`,
@@ -351,7 +436,7 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
351
436
  return getParentNode(context.getFilename());
352
437
  }), validationToRule('no-unused-variables', 'NoUnusedVariables', {
353
438
  description: `A GraphQL operation is only valid if all variables defined by an operation are used, either directly or within a spread fragment.`,
354
- }), validationToRule('overlapping-fields-can-be-merged', 'OverlappingFieldsCanBeMerged', {
439
+ }, importFiles), validationToRule('overlapping-fields-can-be-merged', 'OverlappingFieldsCanBeMerged', {
355
440
  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.`,
356
441
  }), validationToRule('possible-fragment-spread', 'PossibleFragmentSpreads', {
357
442
  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.`,
@@ -604,8 +689,59 @@ const rule$1 = {
604
689
  },
605
690
  };
606
691
 
607
- const AVOID_TYPENAME_PREFIX = 'AVOID_TYPENAME_PREFIX';
608
692
  const rule$2 = {
693
+ meta: {
694
+ type: 'suggestion',
695
+ docs: {
696
+ category: 'Best Practices',
697
+ description: 'Avoid scalar result type on mutation type to make sure to return a valid state.',
698
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-scalar-result-type-on-mutation.md',
699
+ requiresSchema: true,
700
+ examples: [
701
+ {
702
+ title: 'Incorrect',
703
+ code: /* GraphQL */ `
704
+ type Mutation {
705
+ createUser: Boolean
706
+ }
707
+ `,
708
+ },
709
+ {
710
+ title: 'Correct',
711
+ code: /* GraphQL */ `
712
+ type Mutation {
713
+ createUser: User!
714
+ }
715
+ `,
716
+ },
717
+ ],
718
+ },
719
+ },
720
+ create(context) {
721
+ const schema = requireGraphQLSchemaFromContext('avoid-scalar-result-type-on-mutation', context);
722
+ const mutationType = schema.getMutationType();
723
+ if (!mutationType) {
724
+ return {};
725
+ }
726
+ const selector = `:matches(${graphql.Kind.OBJECT_TYPE_DEFINITION}, ${graphql.Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}] > ${graphql.Kind.FIELD_DEFINITION}`;
727
+ return {
728
+ [selector](node) {
729
+ const rawNode = node.rawNode();
730
+ const typeName = getTypeName(rawNode);
731
+ const graphQLType = schema.getType(typeName);
732
+ if (graphql.isScalarType(graphQLType)) {
733
+ context.report({
734
+ node,
735
+ message: `Unexpected scalar result type "${typeName}".`,
736
+ });
737
+ }
738
+ },
739
+ };
740
+ },
741
+ };
742
+
743
+ const AVOID_TYPENAME_PREFIX = 'AVOID_TYPENAME_PREFIX';
744
+ const rule$3 = {
609
745
  meta: {
610
746
  type: 'suggestion',
611
747
  docs: {
@@ -659,7 +795,7 @@ const rule$2 = {
659
795
  },
660
796
  };
661
797
 
662
- const rule$3 = {
798
+ const rule$4 = {
663
799
  meta: {
664
800
  type: 'suggestion',
665
801
  docs: {
@@ -719,7 +855,7 @@ const rule$3 = {
719
855
  },
720
856
  };
721
857
 
722
- const rule$4 = {
858
+ const rule$5 = {
723
859
  meta: {
724
860
  type: 'suggestion',
725
861
  docs: {
@@ -844,12 +980,12 @@ const CASE_STYLES = [
844
980
  const schemaOption = {
845
981
  oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
846
982
  };
847
- const rule$5 = {
983
+ const rule$6 = {
848
984
  meta: {
849
985
  type: 'suggestion',
850
986
  docs: {
851
987
  category: 'Best Practices',
852
- description: 'This rule allows you to enforce that the file name should match the operation name',
988
+ description: 'This rule allows you to enforce that the file name should match the operation name.',
853
989
  url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/match-document-filename.md`,
854
990
  examples: [
855
991
  {
@@ -1056,7 +1192,7 @@ function checkNameFormat(params) {
1056
1192
  errorMessage: '{{nodeType}} name "{{nodeName}}" should have "{{suffix}}" suffix',
1057
1193
  };
1058
1194
  }
1059
- if (style && !acceptedStyles.some(acceptedStyle => acceptedStyle === style)) {
1195
+ if (style && !acceptedStyles.includes(style)) {
1060
1196
  return {
1061
1197
  ok: false,
1062
1198
  errorMessage: `{{nodeType}} name "{{nodeName}}" should be in one of the following options: ${acceptedStyles.join(',')}`,
@@ -1089,7 +1225,7 @@ function checkNameFormat(params) {
1089
1225
  const schemaOption$1 = {
1090
1226
  oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
1091
1227
  };
1092
- const rule$6 = {
1228
+ const rule$7 = {
1093
1229
  meta: {
1094
1230
  type: 'suggestion',
1095
1231
  docs: {
@@ -1318,7 +1454,7 @@ const rule$6 = {
1318
1454
  };
1319
1455
 
1320
1456
  const NO_ANONYMOUS_OPERATIONS = 'NO_ANONYMOUS_OPERATIONS';
1321
- const rule$7 = {
1457
+ const rule$8 = {
1322
1458
  meta: {
1323
1459
  type: 'suggestion',
1324
1460
  docs: {
@@ -1376,13 +1512,14 @@ const rule$7 = {
1376
1512
  };
1377
1513
 
1378
1514
  const ERROR_MESSAGE_ID = 'NO_CASE_INSENSITIVE_ENUM_VALUES_DUPLICATES';
1379
- const rule$8 = {
1515
+ const rule$9 = {
1380
1516
  meta: {
1381
1517
  type: 'suggestion',
1382
1518
  docs: {
1383
1519
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-case-insensitive-enum-values-duplicates.md',
1384
1520
  category: 'Best Practices',
1385
1521
  recommended: true,
1522
+ description: 'Disallow case-insensitive enum values duplicates.',
1386
1523
  examples: [
1387
1524
  {
1388
1525
  title: 'Incorrect',
@@ -1430,7 +1567,7 @@ const rule$8 = {
1430
1567
  };
1431
1568
 
1432
1569
  const NO_DEPRECATED = 'NO_DEPRECATED';
1433
- const rule$9 = {
1570
+ const rule$a = {
1434
1571
  meta: {
1435
1572
  type: 'suggestion',
1436
1573
  docs: {
@@ -1544,7 +1681,7 @@ const rule$9 = {
1544
1681
  };
1545
1682
 
1546
1683
  const HASHTAG_COMMENT = 'HASHTAG_COMMENT';
1547
- const rule$a = {
1684
+ const rule$b = {
1548
1685
  meta: {
1549
1686
  messages: {
1550
1687
  [HASHTAG_COMMENT]: 'Using hashtag (#) for adding GraphQL descriptions is not allowed. Prefer using """ for multiline, or " for a single line description.',
@@ -1619,7 +1756,7 @@ const rule$a = {
1619
1756
  };
1620
1757
 
1621
1758
  const NO_OPERATION_NAME_SUFFIX = 'NO_OPERATION_NAME_SUFFIX';
1622
- const rule$b = {
1759
+ const rule$c = {
1623
1760
  meta: {
1624
1761
  fixable: 'code',
1625
1762
  type: 'suggestion',
@@ -1674,7 +1811,7 @@ const rule$b = {
1674
1811
 
1675
1812
  const UNREACHABLE_TYPE = 'UNREACHABLE_TYPE';
1676
1813
  const RULE_NAME = 'no-unreachable-types';
1677
- const rule$c = {
1814
+ const rule$d = {
1678
1815
  meta: {
1679
1816
  messages: {
1680
1817
  [UNREACHABLE_TYPE]: `Type "{{ typeName }}" is unreachable`,
@@ -1749,7 +1886,7 @@ const rule$c = {
1749
1886
 
1750
1887
  const UNUSED_FIELD = 'UNUSED_FIELD';
1751
1888
  const RULE_NAME$1 = 'no-unused-fields';
1752
- const rule$d = {
1889
+ const rule$e = {
1753
1890
  meta: {
1754
1891
  messages: {
1755
1892
  [UNUSED_FIELD]: `Field "{{fieldName}}" is unused`,
@@ -1931,7 +2068,112 @@ function convertDescription(node) {
1931
2068
  return [];
1932
2069
  }
1933
2070
 
1934
- const rule$e = {
2071
+ const DATE_REGEX = /^\d{2}\/\d{2}\/\d{4}$/;
2072
+ const MESSAGE_REQUIRE_DATE = 'MESSAGE_REQUIRE_DATE';
2073
+ const MESSAGE_INVALID_FORMAT = 'MESSAGE_INVALID_FORMAT';
2074
+ const MESSAGE_INVALID_DATE = 'MESSAGE_INVALID_DATE';
2075
+ const MESSAGE_CAN_BE_REMOVED = 'MESSAGE_CAN_BE_REMOVED';
2076
+ const rule$f = {
2077
+ meta: {
2078
+ type: 'suggestion',
2079
+ docs: {
2080
+ category: 'Best Practices',
2081
+ description: 'Require deletion date on `@deprecated` directive. Suggest removing deprecated things after deprecated date.',
2082
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-deprecation-date.md',
2083
+ examples: [
2084
+ {
2085
+ title: 'Incorrect',
2086
+ code: /* GraphQL */ `
2087
+ type User {
2088
+ firstname: String @deprecated
2089
+ firstName: String
2090
+ }
2091
+ `,
2092
+ },
2093
+ {
2094
+ title: 'Incorrect',
2095
+ code: /* GraphQL */ `
2096
+ type User {
2097
+ firstname: String @deprecated(reason: "Use 'firstName' instead")
2098
+ firstName: String
2099
+ }
2100
+ `,
2101
+ },
2102
+ {
2103
+ title: 'Correct',
2104
+ code: /* GraphQL */ `
2105
+ type User {
2106
+ firstname: String @deprecated(reason: "Use 'firstName' instead", deletionDate: "25/12/2022")
2107
+ firstName: String
2108
+ }
2109
+ `,
2110
+ },
2111
+ ],
2112
+ },
2113
+ messages: {
2114
+ [MESSAGE_REQUIRE_DATE]: 'Directive "@deprecated" must have a deletion date.',
2115
+ [MESSAGE_INVALID_FORMAT]: 'Deletion date must be in format "DD/MM/YYYY".',
2116
+ [MESSAGE_INVALID_DATE]: 'Invalid "{{ deletionDate }}" deletion date.',
2117
+ [MESSAGE_CAN_BE_REMOVED]: '"{{ nodeName }}" сan be removed.',
2118
+ },
2119
+ schema: [
2120
+ {
2121
+ type: 'object',
2122
+ additionalProperties: false,
2123
+ properties: {
2124
+ argumentName: {
2125
+ type: 'string',
2126
+ },
2127
+ },
2128
+ },
2129
+ ],
2130
+ },
2131
+ create(context) {
2132
+ return {
2133
+ 'Directive[name.value=deprecated]'(node) {
2134
+ var _a;
2135
+ const argName = ((_a = context.options[0]) === null || _a === void 0 ? void 0 : _a.argumentName) || 'deletionDate';
2136
+ const deletionDateNode = node.arguments.find(arg => arg.name.value === argName);
2137
+ if (!deletionDateNode) {
2138
+ context.report({ node: node.name, messageId: MESSAGE_REQUIRE_DATE });
2139
+ return;
2140
+ }
2141
+ const deletionDate = valueFromNode(deletionDateNode.value);
2142
+ const isValidDate = DATE_REGEX.test(deletionDate);
2143
+ if (!isValidDate) {
2144
+ context.report({ node: node.name, messageId: MESSAGE_INVALID_FORMAT });
2145
+ return;
2146
+ }
2147
+ let [day, month, year] = deletionDate.split('/');
2148
+ day = day.toString().padStart(2, '0');
2149
+ month = month.toString().padStart(2, '0');
2150
+ const deletionDateInMS = Date.parse(`${year}-${month}-${day}`);
2151
+ if (Number.isNaN(deletionDateInMS)) {
2152
+ context.report({
2153
+ node: node.name,
2154
+ messageId: MESSAGE_INVALID_DATE,
2155
+ data: {
2156
+ deletionDate,
2157
+ },
2158
+ });
2159
+ return;
2160
+ }
2161
+ const canRemove = Date.now() > deletionDateInMS;
2162
+ if (canRemove) {
2163
+ context.report({
2164
+ node: node.name,
2165
+ messageId: MESSAGE_CAN_BE_REMOVED,
2166
+ data: {
2167
+ nodeName: node.parent.name.value,
2168
+ },
2169
+ });
2170
+ }
2171
+ },
2172
+ };
2173
+ },
2174
+ };
2175
+
2176
+ const rule$g = {
1935
2177
  meta: {
1936
2178
  docs: {
1937
2179
  description: `Require all deprecation directives to specify a reason.`,
@@ -1973,11 +2215,11 @@ const rule$e = {
1973
2215
  if (node && node.name && node.name.value === 'deprecated') {
1974
2216
  const args = node.arguments || [];
1975
2217
  const reasonArg = args.find(arg => arg.name && arg.name.value === 'reason');
1976
- const value = reasonArg ? String(valueFromNode(reasonArg.value, {}) || '').trim() : null;
2218
+ const value = reasonArg ? String(valueFromNode(reasonArg.value) || '').trim() : null;
1977
2219
  if (!value) {
1978
2220
  context.report({
1979
2221
  node: node.name,
1980
- message: `Directive "@deprecated" must have a reason!`,
2222
+ message: 'Directive "@deprecated" must have a reason!',
1981
2223
  });
1982
2224
  }
1983
2225
  }
@@ -2021,13 +2263,12 @@ function verifyRule(context, node) {
2021
2263
  }
2022
2264
  }
2023
2265
  }
2024
- const rule$f = {
2266
+ const rule$h = {
2025
2267
  meta: {
2026
2268
  docs: {
2027
2269
  category: 'Best Practices',
2028
2270
  description: `Enforce descriptions in your type definitions.`,
2029
2271
  url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-description.md`,
2030
- recommended: true,
2031
2272
  examples: [
2032
2273
  {
2033
2274
  title: 'Incorrect',
@@ -2086,6 +2327,74 @@ const rule$f = {
2086
2327
  },
2087
2328
  };
2088
2329
 
2330
+ const RULE_NAME$2 = 'require-field-of-type-query-in-mutation-result';
2331
+ const rule$i = {
2332
+ meta: {
2333
+ type: 'suggestion',
2334
+ docs: {
2335
+ category: 'Best Practices',
2336
+ 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`.',
2337
+ url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$2}.md`,
2338
+ requiresSchema: true,
2339
+ examples: [
2340
+ {
2341
+ title: 'Incorrect',
2342
+ code: /* GraphQL */ `
2343
+ type User { ... }
2344
+
2345
+ type Mutation {
2346
+ createUser: User!
2347
+ }
2348
+ `,
2349
+ },
2350
+ {
2351
+ title: 'Correct',
2352
+ code: /* GraphQL */ `
2353
+ type User { ... }
2354
+
2355
+ type Query { ... }
2356
+
2357
+ type CreateUserPayload {
2358
+ user: User!
2359
+ query: Query!
2360
+ }
2361
+
2362
+ type Mutation {
2363
+ createUser: CreateUserPayload!
2364
+ }
2365
+ `,
2366
+ },
2367
+ ],
2368
+ },
2369
+ },
2370
+ create(context) {
2371
+ const schema = requireGraphQLSchemaFromContext(RULE_NAME$2, context);
2372
+ const mutationType = schema.getMutationType();
2373
+ const queryType = schema.getQueryType();
2374
+ if (!mutationType || !queryType) {
2375
+ return {};
2376
+ }
2377
+ const selector = `:matches(${graphql.Kind.OBJECT_TYPE_DEFINITION}, ${graphql.Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}] > ${graphql.Kind.FIELD_DEFINITION}`;
2378
+ return {
2379
+ [selector](node) {
2380
+ const rawNode = node.rawNode();
2381
+ const typeName = getTypeName(rawNode);
2382
+ const graphQLType = schema.getType(typeName);
2383
+ if (graphql.isObjectType(graphQLType)) {
2384
+ const { fields } = graphQLType.astNode;
2385
+ const hasQueryType = fields.some(field => getTypeName(field) === queryType.name);
2386
+ if (!hasQueryType) {
2387
+ context.report({
2388
+ node,
2389
+ message: `Mutation result type "${graphQLType.name}" must contain field of type "${queryType.name}".`,
2390
+ });
2391
+ }
2392
+ }
2393
+ },
2394
+ };
2395
+ },
2396
+ };
2397
+
2089
2398
  function convertToESTree(node, typeInfo) {
2090
2399
  const visitor = { leave: convertNode(typeInfo) };
2091
2400
  return {
@@ -2182,7 +2491,7 @@ const convertNode = (typeInfo) => (node, key, parent) => {
2182
2491
 
2183
2492
  const REQUIRE_ID_WHEN_AVAILABLE = 'REQUIRE_ID_WHEN_AVAILABLE';
2184
2493
  const DEFAULT_ID_FIELD_NAME = 'id';
2185
- const rule$g = {
2494
+ const rule$j = {
2186
2495
  meta: {
2187
2496
  type: 'suggestion',
2188
2497
  docs: {
@@ -2272,13 +2581,13 @@ const rule$g = {
2272
2581
  found = true;
2273
2582
  }
2274
2583
  else if (selection.kind === 'InlineFragment') {
2275
- found = !!(((_a = selection.selectionSet) === null || _a === void 0 ? void 0 : _a.selections) || []).find(s => s.kind === 'Field' && s.name.value === fieldName);
2584
+ found = (((_a = selection.selectionSet) === null || _a === void 0 ? void 0 : _a.selections) || []).some(s => s.kind === 'Field' && s.name.value === fieldName);
2276
2585
  }
2277
2586
  else if (selection.kind === 'FragmentSpread') {
2278
2587
  const foundSpread = siblings.getFragment(selection.name.value);
2279
2588
  if (foundSpread[0]) {
2280
2589
  checkedFragmentSpreads.add(foundSpread[0].document.name.value);
2281
- found = !!(((_b = foundSpread[0].document.selectionSet) === null || _b === void 0 ? void 0 : _b.selections) || []).find(s => s.kind === 'Field' && s.name.value === fieldName);
2590
+ found = (((_b = foundSpread[0].document.selectionSet) === null || _b === void 0 ? void 0 : _b.selections) || []).some(s => s.kind === 'Field' && s.name.value === fieldName);
2282
2591
  }
2283
2592
  }
2284
2593
  if (found) {
@@ -2290,7 +2599,7 @@ const rule$g = {
2290
2599
  parent.kind === 'InlineFragment' &&
2291
2600
  parent.parent &&
2292
2601
  parent.parent.kind === 'SelectionSet' &&
2293
- !!parent.parent.selections.find(s => s.kind === 'Field' && s.name.value === fieldName);
2602
+ parent.parent.selections.some(s => s.kind === 'Field' && s.name.value === fieldName);
2294
2603
  if (!found && !hasIdFieldInInterfaceSelectionSet) {
2295
2604
  context.report({
2296
2605
  loc: {
@@ -2318,7 +2627,7 @@ const rule$g = {
2318
2627
  },
2319
2628
  };
2320
2629
 
2321
- const rule$h = {
2630
+ const rule$k = {
2322
2631
  meta: {
2323
2632
  docs: {
2324
2633
  category: 'Best Practices',
@@ -2432,7 +2741,7 @@ const rule$h = {
2432
2741
 
2433
2742
  const shouldIgnoreNode = ({ node, exceptions }) => {
2434
2743
  const rawNode = node.rawNode();
2435
- if (exceptions.types && exceptions.types.some(type => rawNode.name.value === type)) {
2744
+ if (exceptions.types && exceptions.types.includes(rawNode.name.value)) {
2436
2745
  return true;
2437
2746
  }
2438
2747
  if (exceptions.suffixes && exceptions.suffixes.some(suffix => rawNode.name.value.endsWith(suffix))) {
@@ -2440,7 +2749,7 @@ const shouldIgnoreNode = ({ node, exceptions }) => {
2440
2749
  }
2441
2750
  return false;
2442
2751
  };
2443
- const rule$i = {
2752
+ const rule$l = {
2444
2753
  meta: {
2445
2754
  type: 'suggestion',
2446
2755
  docs: {
@@ -2590,7 +2899,7 @@ const rule$i = {
2590
2899
  },
2591
2900
  };
2592
2901
 
2593
- const RULE_NAME$2 = 'unique-fragment-name';
2902
+ const RULE_NAME$3 = 'unique-fragment-name';
2594
2903
  const UNIQUE_FRAGMENT_NAME = 'UNIQUE_FRAGMENT_NAME';
2595
2904
  const checkNode = (context, node, ruleName, messageId) => {
2596
2905
  var _a;
@@ -2630,13 +2939,13 @@ const checkNode = (context, node, ruleName, messageId) => {
2630
2939
  });
2631
2940
  }
2632
2941
  };
2633
- const rule$j = {
2942
+ const rule$m = {
2634
2943
  meta: {
2635
2944
  type: 'suggestion',
2636
2945
  docs: {
2637
2946
  category: 'Best Practices',
2638
2947
  description: `Enforce unique fragment names across your project.`,
2639
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$2}.md`,
2948
+ url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$3}.md`,
2640
2949
  requiresSiblings: true,
2641
2950
  examples: [
2642
2951
  {
@@ -2680,21 +2989,21 @@ const rule$j = {
2680
2989
  create(context) {
2681
2990
  return {
2682
2991
  FragmentDefinition(node) {
2683
- checkNode(context, node, RULE_NAME$2, UNIQUE_FRAGMENT_NAME);
2992
+ checkNode(context, node, RULE_NAME$3, UNIQUE_FRAGMENT_NAME);
2684
2993
  },
2685
2994
  };
2686
2995
  },
2687
2996
  };
2688
2997
 
2689
- const RULE_NAME$3 = 'unique-operation-name';
2998
+ const RULE_NAME$4 = 'unique-operation-name';
2690
2999
  const UNIQUE_OPERATION_NAME = 'UNIQUE_OPERATION_NAME';
2691
- const rule$k = {
3000
+ const rule$n = {
2692
3001
  meta: {
2693
3002
  type: 'suggestion',
2694
3003
  docs: {
2695
3004
  category: 'Best Practices',
2696
3005
  description: `Enforce unique operation names across your project.`,
2697
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$3}.md`,
3006
+ url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$4}.md`,
2698
3007
  requiresSiblings: true,
2699
3008
  examples: [
2700
3009
  {
@@ -2742,35 +3051,41 @@ const rule$k = {
2742
3051
  create(context) {
2743
3052
  return {
2744
3053
  OperationDefinition(node) {
2745
- checkNode(context, node, RULE_NAME$3, UNIQUE_OPERATION_NAME);
3054
+ checkNode(context, node, RULE_NAME$4, UNIQUE_OPERATION_NAME);
2746
3055
  },
2747
3056
  };
2748
3057
  },
2749
3058
  };
2750
3059
 
3060
+ /*
3061
+ * 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
3062
+ */
2751
3063
  const rules = {
3064
+ ...GRAPHQL_JS_VALIDATIONS,
2752
3065
  'avoid-duplicate-fields': rule,
2753
3066
  'avoid-operation-name-prefix': rule$1,
2754
- 'avoid-typename-prefix': rule$2,
2755
- 'description-style': rule$3,
2756
- 'input-name': rule$4,
2757
- 'match-document-filename': rule$5,
2758
- 'naming-convention': rule$6,
2759
- 'no-anonymous-operations': rule$7,
2760
- 'no-case-insensitive-enum-values-duplicates': rule$8,
2761
- 'no-deprecated': rule$9,
2762
- 'no-hashtag-description': rule$a,
2763
- 'no-operation-name-suffix': rule$b,
2764
- 'no-unreachable-types': rule$c,
2765
- 'no-unused-fields': rule$d,
2766
- 'require-deprecation-reason': rule$e,
2767
- 'require-description': rule$f,
2768
- 'require-id-when-available': rule$g,
2769
- 'selection-set-depth': rule$h,
2770
- 'strict-id-in-types': rule$i,
2771
- 'unique-fragment-name': rule$j,
2772
- 'unique-operation-name': rule$k,
2773
- ...GRAPHQL_JS_VALIDATIONS,
3067
+ 'avoid-scalar-result-type-on-mutation': rule$2,
3068
+ 'avoid-typename-prefix': rule$3,
3069
+ 'description-style': rule$4,
3070
+ 'input-name': rule$5,
3071
+ 'match-document-filename': rule$6,
3072
+ 'naming-convention': rule$7,
3073
+ 'no-anonymous-operations': rule$8,
3074
+ 'no-case-insensitive-enum-values-duplicates': rule$9,
3075
+ 'no-deprecated': rule$a,
3076
+ 'no-hashtag-description': rule$b,
3077
+ 'no-operation-name-suffix': rule$c,
3078
+ 'no-unreachable-types': rule$d,
3079
+ 'no-unused-fields': rule$e,
3080
+ 'require-deprecation-date': rule$f,
3081
+ 'require-deprecation-reason': rule$g,
3082
+ 'require-description': rule$h,
3083
+ 'require-field-of-type-query-in-mutation-result': rule$i,
3084
+ 'require-id-when-available': rule$j,
3085
+ 'selection-set-depth': rule$k,
3086
+ 'strict-id-in-types': rule$l,
3087
+ 'unique-fragment-name': rule$m,
3088
+ 'unique-operation-name': rule$n,
2774
3089
  };
2775
3090
 
2776
3091
  const RELEVANT_KEYWORDS = ['gql', 'graphql', '/* GraphQL */'];
@@ -3047,7 +3362,6 @@ function getReachableTypes(schema) {
3047
3362
  return reachableTypesCache;
3048
3363
  }
3049
3364
  const reachableTypes = new Set();
3050
- const getTypeName = node => ('type' in node ? getTypeName(node.type) : node.name.value);
3051
3365
  const collect = (node) => {
3052
3366
  const typeName = getTypeName(node);
3053
3367
  if (reachableTypes.has(typeName)) {
@@ -3184,6 +3498,7 @@ class GraphQLRuleTester extends require('eslint').RuleTester {
3184
3498
  }
3185
3499
 
3186
3500
  exports.GraphQLRuleTester = GraphQLRuleTester;
3501
+ exports.configs = configs;
3187
3502
  exports.convertDescription = convertDescription;
3188
3503
  exports.convertLocation = convertLocation;
3189
3504
  exports.convertRange = convertRange;