@graphql-eslint/eslint-plugin 3.0.0-alpha-580615a.0 → 3.0.0-alpha-4613dfe.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 (89) hide show
  1. package/README.md +19 -8
  2. package/configs/base.d.ts +5 -0
  3. package/configs/index.d.ts +78 -121
  4. package/configs/operations-all.d.ts +19 -0
  5. package/configs/operations-recommended.d.ts +51 -0
  6. package/configs/schema-all.d.ts +17 -0
  7. package/configs/schema-recommended.d.ts +46 -0
  8. package/docs/README.md +11 -19
  9. package/docs/deprecated-rules.md +21 -0
  10. package/docs/rules/alphabetize.md +1 -1
  11. package/docs/rules/description-style.md +5 -3
  12. package/docs/rules/executable-definitions.md +2 -2
  13. package/docs/rules/fields-on-correct-type.md +2 -2
  14. package/docs/rules/fragments-on-composite-type.md +2 -2
  15. package/docs/rules/input-name.md +1 -1
  16. package/docs/rules/known-argument-names.md +2 -2
  17. package/docs/rules/known-directives.md +2 -2
  18. package/docs/rules/known-fragment-names.md +2 -2
  19. package/docs/rules/known-type-names.md +2 -2
  20. package/docs/rules/lone-anonymous-operation.md +2 -2
  21. package/docs/rules/lone-schema-definition.md +2 -2
  22. package/docs/rules/match-document-filename.md +6 -4
  23. package/docs/rules/naming-convention.md +180 -41
  24. package/docs/rules/no-anonymous-operations.md +2 -2
  25. package/docs/rules/no-case-insensitive-enum-values-duplicates.md +2 -2
  26. package/docs/rules/no-deprecated.md +3 -1
  27. package/docs/rules/{avoid-duplicate-fields.md → no-duplicate-fields.md} +10 -8
  28. package/docs/rules/no-fragment-cycles.md +2 -2
  29. package/docs/rules/no-hashtag-description.md +3 -1
  30. package/docs/rules/no-root-type.md +9 -14
  31. package/docs/rules/{avoid-scalar-result-type-on-mutation.md → no-scalar-result-type-on-mutation.md} +7 -7
  32. package/docs/rules/no-typename-prefix.md +37 -0
  33. package/docs/rules/no-undefined-variables.md +2 -2
  34. package/docs/rules/no-unreachable-types.md +3 -1
  35. package/docs/rules/no-unused-fields.md +1 -1
  36. package/docs/rules/no-unused-fragments.md +2 -2
  37. package/docs/rules/no-unused-variables.md +2 -2
  38. package/docs/rules/one-field-subscriptions.md +2 -2
  39. package/docs/rules/overlapping-fields-can-be-merged.md +2 -2
  40. package/docs/rules/possible-fragment-spread.md +2 -2
  41. package/docs/rules/possible-type-extension.md +2 -2
  42. package/docs/rules/provided-required-arguments.md +2 -2
  43. package/docs/rules/require-deprecation-date.md +1 -1
  44. package/docs/rules/require-deprecation-reason.md +2 -2
  45. package/docs/rules/require-description.md +38 -22
  46. package/docs/rules/require-field-of-type-query-in-mutation-result.md +1 -1
  47. package/docs/rules/require-id-when-available.md +3 -1
  48. package/docs/rules/scalar-leafs.md +2 -2
  49. package/docs/rules/selection-set-depth.md +9 -2
  50. package/docs/rules/strict-id-in-types.md +16 -10
  51. package/docs/rules/unique-argument-names.md +2 -2
  52. package/docs/rules/unique-directive-names-per-location.md +2 -2
  53. package/docs/rules/unique-directive-names.md +2 -2
  54. package/docs/rules/unique-enum-value-names.md +2 -2
  55. package/docs/rules/unique-field-definition-names.md +2 -2
  56. package/docs/rules/unique-fragment-name.md +1 -1
  57. package/docs/rules/unique-input-field-names.md +2 -2
  58. package/docs/rules/unique-operation-name.md +1 -1
  59. package/docs/rules/unique-operation-types.md +2 -2
  60. package/docs/rules/unique-type-names.md +2 -2
  61. package/docs/rules/unique-variable-names.md +2 -2
  62. package/docs/rules/value-literals-of-correct-type.md +2 -2
  63. package/docs/rules/variables-are-input-types.md +2 -2
  64. package/docs/rules/variables-in-allowed-position.md +2 -2
  65. package/index.js +682 -718
  66. package/index.mjs +683 -719
  67. package/package.json +1 -1
  68. package/rules/alphabetize.d.ts +8 -10
  69. package/rules/description-style.d.ts +4 -6
  70. package/rules/index.d.ts +115 -119
  71. package/rules/input-name.d.ts +1 -1
  72. package/rules/match-document-filename.d.ts +8 -10
  73. package/rules/naming-convention.d.ts +3 -4
  74. package/rules/{avoid-duplicate-fields.d.ts → no-duplicate-fields.d.ts} +0 -0
  75. package/rules/no-root-type.d.ts +1 -1
  76. package/rules/{avoid-scalar-result-type-on-mutation.d.ts → no-scalar-result-type-on-mutation.d.ts} +0 -0
  77. package/rules/{avoid-typename-prefix.d.ts → no-typename-prefix.d.ts} +0 -0
  78. package/rules/require-description.d.ts +2 -3
  79. package/rules/require-id-when-available.d.ts +3 -3
  80. package/rules/selection-set-depth.d.ts +3 -3
  81. package/rules/strict-id-in-types.d.ts +6 -8
  82. package/types.d.ts +9 -5
  83. package/configs/all.d.ts +0 -104
  84. package/configs/recommended.d.ts +0 -72
  85. package/docs/rules/avoid-operation-name-prefix.md +0 -50
  86. package/docs/rules/avoid-typename-prefix.md +0 -37
  87. package/docs/rules/no-operation-name-suffix.md +0 -38
  88. package/rules/avoid-operation-name-prefix.d.ts +0 -9
  89. package/rules/no-operation-name-suffix.d.ts +0 -3
package/index.js CHANGED
@@ -18,14 +18,89 @@ const codeFileLoader = require('@graphql-tools/code-file-loader');
18
18
  const eslint = require('eslint');
19
19
  const codeFrame = require('@babel/code-frame');
20
20
 
21
+ const base = {
22
+ parser: '@graphql-eslint/eslint-plugin',
23
+ plugins: ['@graphql-eslint'],
24
+ };
25
+
21
26
  /*
22
27
  * 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
23
28
  */
24
- const recommendedConfig = {
25
- parser: '@graphql-eslint/eslint-plugin',
26
- plugins: ['@graphql-eslint'],
29
+ const schemaRecommendedConfig = {
30
+ extends: ['plugin:@graphql-eslint/base'],
31
+ rules: {
32
+ '@graphql-eslint/description-style': 'error',
33
+ '@graphql-eslint/known-argument-names': 'error',
34
+ '@graphql-eslint/known-directives': 'error',
35
+ '@graphql-eslint/known-type-names': 'error',
36
+ '@graphql-eslint/lone-schema-definition': 'error',
37
+ '@graphql-eslint/naming-convention': [
38
+ 'error',
39
+ {
40
+ types: 'PascalCase',
41
+ fields: 'camelCase',
42
+ EnumValueDefinition: 'UPPER_CASE',
43
+ 'FieldDefinition[parent.name.value=Query]': {
44
+ forbiddenPrefixes: ['query', 'get'],
45
+ forbiddenSuffixes: ['Query'],
46
+ },
47
+ 'FieldDefinition[parent.name.value=Mutation]': {
48
+ forbiddenPrefixes: ['mutation'],
49
+ forbiddenSuffixes: ['Mutation'],
50
+ },
51
+ 'FieldDefinition[parent.name.value=Subscription]': {
52
+ forbiddenPrefixes: ['subscription'],
53
+ forbiddenSuffixes: ['Subscription'],
54
+ },
55
+ },
56
+ ],
57
+ '@graphql-eslint/no-case-insensitive-enum-values-duplicates': 'error',
58
+ '@graphql-eslint/no-hashtag-description': 'error',
59
+ '@graphql-eslint/no-typename-prefix': 'error',
60
+ '@graphql-eslint/no-unreachable-types': 'error',
61
+ '@graphql-eslint/possible-type-extension': 'error',
62
+ '@graphql-eslint/provided-required-arguments': 'error',
63
+ '@graphql-eslint/require-deprecation-reason': 'error',
64
+ '@graphql-eslint/require-description': ['error', { types: true, DirectiveDefinition: true }],
65
+ '@graphql-eslint/strict-id-in-types': 'error',
66
+ '@graphql-eslint/unique-directive-names': 'error',
67
+ '@graphql-eslint/unique-directive-names-per-location': 'error',
68
+ '@graphql-eslint/unique-enum-value-names': 'error',
69
+ '@graphql-eslint/unique-field-definition-names': 'error',
70
+ '@graphql-eslint/unique-operation-types': 'error',
71
+ '@graphql-eslint/unique-type-names': 'error',
72
+ },
73
+ };
74
+
75
+ /*
76
+ * 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
77
+ */
78
+ const schemaAllConfig = {
79
+ extends: ['plugin:@graphql-eslint/base', 'plugin:@graphql-eslint/schema-recommended'],
80
+ rules: {
81
+ '@graphql-eslint/alphabetize': [
82
+ 'error',
83
+ {
84
+ fields: ['ObjectTypeDefinition', 'InterfaceTypeDefinition', 'InputObjectTypeDefinition'],
85
+ values: ['EnumTypeDefinition'],
86
+ arguments: ['FieldDefinition', 'Field', 'DirectiveDefinition', 'Directive'],
87
+ },
88
+ ],
89
+ '@graphql-eslint/input-name': 'error',
90
+ '@graphql-eslint/no-root-type': 'off',
91
+ '@graphql-eslint/no-scalar-result-type-on-mutation': 'error',
92
+ '@graphql-eslint/no-unused-fields': 'off',
93
+ '@graphql-eslint/require-deprecation-date': 'error',
94
+ '@graphql-eslint/require-field-of-type-query-in-mutation-result': 'error',
95
+ },
96
+ };
97
+
98
+ /*
99
+ * 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
100
+ */
101
+ const operationsRecommendedConfig = {
102
+ extends: ['plugin:@graphql-eslint/base'],
27
103
  rules: {
28
- '@graphql-eslint/avoid-typename-prefix': 'error',
29
104
  '@graphql-eslint/executable-definitions': 'error',
30
105
  '@graphql-eslint/fields-on-correct-type': 'error',
31
106
  '@graphql-eslint/fragments-on-composite-type': 'error',
@@ -34,58 +109,36 @@ const recommendedConfig = {
34
109
  '@graphql-eslint/known-fragment-names': 'error',
35
110
  '@graphql-eslint/known-type-names': 'error',
36
111
  '@graphql-eslint/lone-anonymous-operation': 'error',
37
- '@graphql-eslint/lone-schema-definition': 'error',
38
112
  '@graphql-eslint/naming-convention': [
39
113
  'error',
40
114
  {
41
- types: 'PascalCase',
42
- fields: 'camelCase',
43
- overrides: {
44
- EnumValueDefinition: 'UPPER_CASE',
45
- OperationDefinition: {
46
- style: 'PascalCase',
47
- forbiddenPrefixes: ['Query', 'Mutation', 'Subscription', 'Get'],
48
- forbiddenSuffixes: ['Query', 'Mutation', 'Subscription'],
49
- },
50
- FragmentDefinition: { style: 'PascalCase', forbiddenPrefixes: ['Fragment'], forbiddenSuffixes: ['Fragment'] },
51
- 'FieldDefinition[parent.name.value=Query]': {
52
- forbiddenPrefixes: ['query', 'get'],
53
- forbiddenSuffixes: ['Query'],
54
- },
55
- 'FieldDefinition[parent.name.value=Mutation]': {
56
- forbiddenPrefixes: ['mutation'],
57
- forbiddenSuffixes: ['Mutation'],
58
- },
59
- 'FieldDefinition[parent.name.value=Subscription]': {
60
- forbiddenPrefixes: ['subscription'],
61
- forbiddenSuffixes: ['Subscription'],
62
- },
63
- },
115
+ Argument: 'camelCase',
116
+ VariableDefinition: 'camelCase',
117
+ OperationDefinition: {
118
+ style: 'PascalCase',
119
+ forbiddenPrefixes: ['Query', 'Mutation', 'Subscription', 'Get'],
120
+ forbiddenSuffixes: ['Query', 'Mutation', 'Subscription'],
121
+ },
122
+ FragmentDefinition: { style: 'PascalCase', forbiddenPrefixes: ['Fragment'], forbiddenSuffixes: ['Fragment'] },
64
123
  },
65
124
  ],
66
125
  '@graphql-eslint/no-anonymous-operations': 'error',
67
- '@graphql-eslint/no-case-insensitive-enum-values-duplicates': 'error',
126
+ '@graphql-eslint/no-deprecated': 'error',
127
+ '@graphql-eslint/no-duplicate-fields': 'error',
68
128
  '@graphql-eslint/no-fragment-cycles': 'error',
69
- '@graphql-eslint/no-operation-name-suffix': 'error',
70
129
  '@graphql-eslint/no-undefined-variables': 'error',
71
130
  '@graphql-eslint/no-unused-fragments': 'error',
72
131
  '@graphql-eslint/no-unused-variables': 'error',
73
132
  '@graphql-eslint/one-field-subscriptions': 'error',
74
133
  '@graphql-eslint/overlapping-fields-can-be-merged': 'error',
75
134
  '@graphql-eslint/possible-fragment-spread': 'error',
76
- '@graphql-eslint/possible-type-extension': 'error',
77
135
  '@graphql-eslint/provided-required-arguments': 'error',
78
- '@graphql-eslint/require-deprecation-reason': 'error',
136
+ '@graphql-eslint/require-id-when-available': 'error',
79
137
  '@graphql-eslint/scalar-leafs': 'error',
80
- '@graphql-eslint/strict-id-in-types': 'error',
138
+ '@graphql-eslint/selection-set-depth': ['error', { maxDepth: 7 }],
81
139
  '@graphql-eslint/unique-argument-names': 'error',
82
- '@graphql-eslint/unique-directive-names': 'error',
83
140
  '@graphql-eslint/unique-directive-names-per-location': 'error',
84
- '@graphql-eslint/unique-enum-value-names': 'error',
85
- '@graphql-eslint/unique-field-definition-names': 'error',
86
141
  '@graphql-eslint/unique-input-field-names': 'error',
87
- '@graphql-eslint/unique-operation-types': 'error',
88
- '@graphql-eslint/unique-type-names': 'error',
89
142
  '@graphql-eslint/unique-variable-names': 'error',
90
143
  '@graphql-eslint/value-literals-of-correct-type': 'error',
91
144
  '@graphql-eslint/variables-are-input-types': 'error',
@@ -96,44 +149,32 @@ const recommendedConfig = {
96
149
  /*
97
150
  * 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
98
151
  */
99
- const allConfig = {
100
- ...recommendedConfig,
152
+ const operationsAllConfig = {
153
+ extends: ['plugin:@graphql-eslint/base', 'plugin:@graphql-eslint/operations-recommended'],
101
154
  rules: {
102
- ...recommendedConfig.rules,
103
155
  '@graphql-eslint/alphabetize': [
104
156
  'error',
105
157
  {
106
- fields: ['ObjectTypeDefinition', 'InterfaceTypeDefinition', 'InputObjectTypeDefinition'],
107
- values: ['EnumTypeDefinition'],
108
158
  selections: ['OperationDefinition', 'FragmentDefinition'],
109
159
  variables: ['OperationDefinition'],
110
- arguments: ['FieldDefinition', 'Field', 'DirectiveDefinition', 'Directive'],
160
+ arguments: ['Field', 'Directive'],
111
161
  },
112
162
  ],
113
- '@graphql-eslint/avoid-duplicate-fields': 'error',
114
- '@graphql-eslint/avoid-operation-name-prefix': 'error',
115
- '@graphql-eslint/avoid-scalar-result-type-on-mutation': 'error',
116
- '@graphql-eslint/description-style': 'error',
117
- '@graphql-eslint/input-name': 'error',
118
- '@graphql-eslint/match-document-filename': 'error',
119
- '@graphql-eslint/no-deprecated': 'error',
120
- '@graphql-eslint/no-hashtag-description': 'error',
121
- '@graphql-eslint/no-root-type': ['error', { disallow: ['subscription'] }],
122
- '@graphql-eslint/no-unreachable-types': 'error',
123
- '@graphql-eslint/no-unused-fields': 'error',
124
- '@graphql-eslint/require-deprecation-date': 'error',
125
- '@graphql-eslint/require-description': ['error', { types: true, overrides: { DirectiveDefinition: true } }],
126
- '@graphql-eslint/require-field-of-type-query-in-mutation-result': 'error',
127
- '@graphql-eslint/require-id-when-available': 'error',
128
- '@graphql-eslint/selection-set-depth': 'error',
163
+ '@graphql-eslint/match-document-filename': [
164
+ 'error',
165
+ { query: 'kebab-case', mutation: 'kebab-case', subscription: 'kebab-case', fragment: 'kebab-case' },
166
+ ],
129
167
  '@graphql-eslint/unique-fragment-name': 'error',
130
168
  '@graphql-eslint/unique-operation-name': 'error',
131
169
  },
132
170
  };
133
171
 
134
172
  const configs = {
135
- all: allConfig,
136
- recommended: recommendedConfig,
173
+ base,
174
+ 'schema-recommended': schemaRecommendedConfig,
175
+ 'schema-all': schemaAllConfig,
176
+ 'operations-recommended': operationsRecommendedConfig,
177
+ 'operations-all': operationsAllConfig,
137
178
  };
138
179
 
139
180
  function requireSiblingsOperations(ruleName, context) {
@@ -346,7 +387,6 @@ const validationToRule = (name, ruleName, docs, getDocumentNode) => {
346
387
  docs: {
347
388
  ...docs,
348
389
  graphQLJSRuleName: ruleName,
349
- category: 'Validation',
350
390
  recommended: true,
351
391
  requiresSchema,
352
392
  url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${name}.md`,
@@ -383,16 +423,22 @@ const importFiles = (context) => {
383
423
  return _import.processImport(context.getFilename());
384
424
  };
385
425
  const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-definitions', 'ExecutableDefinitions', {
426
+ category: 'Operations',
386
427
  description: `A GraphQL document is only valid for execution if all definitions are either operation or fragment definitions.`,
387
428
  }), validationToRule('fields-on-correct-type', 'FieldsOnCorrectType', {
429
+ category: 'Operations',
388
430
  description: 'A GraphQL document is only valid if all fields selected are defined by the parent type, or are an allowed meta field such as `__typename`.',
389
431
  }), validationToRule('fragments-on-composite-type', 'FragmentsOnCompositeTypes', {
432
+ category: 'Operations',
390
433
  description: `Fragments use a type condition to determine if they apply, since fragments can only be spread into a composite type (object, interface, or union), the type condition must also be a composite type.`,
391
434
  }), validationToRule('known-argument-names', 'KnownArgumentNames', {
435
+ category: ['Schema', 'Operations'],
392
436
  description: `A GraphQL field is only valid if all supplied arguments are defined by that field.`,
393
437
  }), validationToRule('known-directives', 'KnownDirectives', {
438
+ category: ['Schema', 'Operations'],
394
439
  description: `A GraphQL document is only valid if all \`@directives\` are known by the schema and legally positioned.`,
395
440
  }), validationToRule('known-fragment-names', 'KnownFragmentNames', {
441
+ category: 'Operations',
396
442
  description: `A GraphQL document is only valid if all \`...Fragment\` fragment spreads refer to fragments defined in the same document.`,
397
443
  examples: [
398
444
  {
@@ -459,17 +505,23 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
459
505
  },
460
506
  ],
461
507
  }, importFiles), validationToRule('known-type-names', 'KnownTypeNames', {
508
+ category: ['Schema', 'Operations'],
462
509
  description: `A GraphQL document is only valid if referenced types (specifically variable definitions and fragment conditions) are defined by the type schema.`,
463
510
  }), validationToRule('lone-anonymous-operation', 'LoneAnonymousOperation', {
511
+ category: 'Operations',
464
512
  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.`,
465
513
  }), validationToRule('lone-schema-definition', 'LoneSchemaDefinition', {
514
+ category: 'Schema',
466
515
  description: `A GraphQL document is only valid if it contains only one schema definition.`,
467
516
  requiresSchema: false,
468
517
  }), validationToRule('no-fragment-cycles', 'NoFragmentCycles', {
518
+ category: 'Operations',
469
519
  description: `A GraphQL fragment is only valid when it does not have cycles in fragments usage.`,
470
520
  }), validationToRule('no-undefined-variables', 'NoUndefinedVariables', {
521
+ category: 'Operations',
471
522
  description: `A GraphQL operation is only valid if all variables encountered, both directly and via fragment spreads, are defined by that operation.`,
472
523
  }, importFiles), validationToRule('no-unused-fragments', 'NoUnusedFragments', {
524
+ category: 'Operations',
473
525
  description: `A GraphQL document is only valid if all fragment definitions are spread within operations, or spread within other fragments spread within operations.`,
474
526
  requiresSiblings: true,
475
527
  }, context => {
@@ -499,49 +551,68 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
499
551
  };
500
552
  return getParentNode(context.getFilename());
501
553
  }), validationToRule('no-unused-variables', 'NoUnusedVariables', {
554
+ category: 'Operations',
502
555
  description: `A GraphQL operation is only valid if all variables defined by an operation are used, either directly or within a spread fragment.`,
503
556
  }, importFiles), validationToRule('overlapping-fields-can-be-merged', 'OverlappingFieldsCanBeMerged', {
557
+ category: 'Operations',
504
558
  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.`,
505
559
  }), validationToRule('possible-fragment-spread', 'PossibleFragmentSpreads', {
560
+ category: 'Operations',
506
561
  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.`,
507
562
  }), validationToRule('possible-type-extension', 'PossibleTypeExtensions', {
563
+ category: 'Schema',
508
564
  description: `A type extension is only valid if the type is defined and has the same kind.`,
509
565
  requiresSchema: false,
510
566
  }), validationToRule('provided-required-arguments', 'ProvidedRequiredArguments', {
567
+ category: ['Schema', 'Operations'],
511
568
  description: `A field or directive is only valid if all required (non-null without a default value) field arguments have been provided.`,
512
569
  }), validationToRule('scalar-leafs', 'ScalarLeafs', {
570
+ category: 'Operations',
513
571
  description: `A GraphQL document is valid only if all leaf fields (fields without sub selections) are of scalar or enum types.`,
514
572
  }), validationToRule('one-field-subscriptions', 'SingleFieldSubscriptions', {
573
+ category: 'Operations',
515
574
  description: `A GraphQL subscription is valid only if it contains a single root field.`,
516
575
  }), validationToRule('unique-argument-names', 'UniqueArgumentNames', {
576
+ category: 'Operations',
517
577
  description: `A GraphQL field or directive is only valid if all supplied arguments are uniquely named.`,
518
578
  }), validationToRule('unique-directive-names', 'UniqueDirectiveNames', {
579
+ category: 'Schema',
519
580
  description: `A GraphQL document is only valid if all defined directives have unique names.`,
520
581
  requiresSchema: false,
521
582
  }), validationToRule('unique-directive-names-per-location', 'UniqueDirectivesPerLocation', {
583
+ category: ['Schema', 'Operations'],
522
584
  description: `A GraphQL document is only valid if all non-repeatable directives at a given location are uniquely named.`,
523
585
  }), validationToRule('unique-enum-value-names', 'UniqueEnumValueNames', {
586
+ category: 'Schema',
524
587
  description: `A GraphQL enum type is only valid if all its values are uniquely named.`,
525
588
  requiresSchema: false,
526
589
  }), validationToRule('unique-field-definition-names', 'UniqueFieldDefinitionNames', {
590
+ category: 'Schema',
527
591
  description: `A GraphQL complex type is only valid if all its fields are uniquely named.`,
528
592
  requiresSchema: false,
529
593
  }), validationToRule('unique-input-field-names', 'UniqueInputFieldNames', {
594
+ category: 'Operations',
530
595
  description: `A GraphQL input object value is only valid if all supplied fields are uniquely named.`,
531
596
  requiresSchema: false,
532
597
  }), validationToRule('unique-operation-types', 'UniqueOperationTypes', {
598
+ category: 'Schema',
533
599
  description: `A GraphQL document is only valid if it has only one type per operation.`,
534
600
  requiresSchema: false,
535
601
  }), validationToRule('unique-type-names', 'UniqueTypeNames', {
602
+ category: 'Schema',
536
603
  description: `A GraphQL document is only valid if all defined types have unique names.`,
537
604
  requiresSchema: false,
538
605
  }), validationToRule('unique-variable-names', 'UniqueVariableNames', {
606
+ category: 'Operations',
539
607
  description: `A GraphQL operation is only valid if all its variables are uniquely named.`,
540
608
  }), validationToRule('value-literals-of-correct-type', 'ValuesOfCorrectType', {
609
+ category: 'Operations',
541
610
  description: `A GraphQL document is only valid if all value literals are of the type expected at their position.`,
542
611
  }), validationToRule('variables-are-input-types', 'VariablesAreInputTypes', {
612
+ category: 'Operations',
543
613
  description: `A GraphQL operation is only valid if all the variables it defines are of input types (scalar, enum, or input object).`,
544
614
  }), validationToRule('variables-in-allowed-position', 'VariablesInAllowedPosition', {
615
+ category: 'Operations',
545
616
  description: `Variables passed to field arguments conform to type.`,
546
617
  }));
547
618
 
@@ -567,7 +638,7 @@ const rule = {
567
638
  meta: {
568
639
  type: 'suggestion',
569
640
  docs: {
570
- category: 'Best Practices',
641
+ category: ['Schema', 'Operations'],
571
642
  description: 'Enforce arrange in alphabetical order for type fields, enum values, input object fields, operation selections and more.',
572
643
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/alphabetize.md',
573
644
  examples: [
@@ -646,15 +717,22 @@ const rule = {
646
717
  `,
647
718
  },
648
719
  ],
649
- optionsForConfig: [
650
- {
651
- fields: fieldsEnum,
652
- values: valuesEnum,
653
- selections: selectionsEnum,
654
- variables: variablesEnum,
655
- arguments: argumentsEnum,
656
- },
657
- ],
720
+ configOptions: {
721
+ schema: [
722
+ {
723
+ fields: fieldsEnum,
724
+ values: valuesEnum,
725
+ arguments: argumentsEnum,
726
+ },
727
+ ],
728
+ operations: [
729
+ {
730
+ selections: selectionsEnum,
731
+ variables: variablesEnum,
732
+ arguments: [graphql.Kind.FIELD, graphql.Kind.DIRECTIVE],
733
+ },
734
+ ],
735
+ },
658
736
  },
659
737
  messages: {
660
738
  [ALPHABETIZE]: '"{{ currName }}" should be before "{{ prevName }}"',
@@ -792,430 +870,128 @@ const rule = {
792
870
  },
793
871
  };
794
872
 
795
- const AVOID_DUPLICATE_FIELDS = 'AVOID_DUPLICATE_FIELDS';
796
873
  const rule$1 = {
797
874
  meta: {
798
875
  type: 'suggestion',
799
876
  docs: {
800
- description: `Checks for duplicate fields in selection set, variables in operation definition, or in arguments set of a field.`,
801
- category: 'Stylistic Issues',
802
- url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-duplicate-fields.md',
803
877
  examples: [
804
878
  {
805
879
  title: 'Incorrect',
880
+ usage: [{ style: 'inline' }],
806
881
  code: /* GraphQL */ `
807
- query {
808
- user {
809
- name
810
- email
811
- name # duplicate field
812
- }
813
- }
814
- `,
815
- },
816
- {
817
- title: 'Incorrect',
818
- code: /* GraphQL */ `
819
- query {
820
- users(
821
- first: 100
822
- skip: 50
823
- after: "cji629tngfgou0b73kt7vi5jo"
824
- first: 100 # duplicate argument
825
- ) {
826
- id
827
- }
882
+ """ Description """
883
+ type someTypeName {
884
+ # ...
828
885
  }
829
886
  `,
830
887
  },
831
888
  {
832
- title: 'Incorrect',
889
+ title: 'Correct',
890
+ usage: [{ style: 'inline' }],
833
891
  code: /* GraphQL */ `
834
- query (
835
- $first: Int!
836
- $first: Int! # duplicate variable
837
- ) {
838
- users(first: $first, skip: 50) {
839
- id
840
- }
892
+ " Description "
893
+ type someTypeName {
894
+ # ...
841
895
  }
842
896
  `,
843
897
  },
844
898
  ],
899
+ description: 'Require all comments to follow the same style (either block or inline).',
900
+ category: 'Schema',
901
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/description-style.md',
902
+ recommended: true,
845
903
  },
846
- messages: {
847
- [AVOID_DUPLICATE_FIELDS]: `{{ type }} "{{ fieldName }}" defined multiple times`,
848
- },
849
- schema: [],
904
+ schema: [
905
+ {
906
+ type: 'object',
907
+ additionalProperties: false,
908
+ properties: {
909
+ style: {
910
+ enum: ['block', 'inline'],
911
+ default: 'block',
912
+ },
913
+ },
914
+ },
915
+ ],
850
916
  },
851
917
  create(context) {
852
- function checkNode(usedFields, fieldName, type, node) {
853
- if (usedFields.has(fieldName)) {
918
+ const { style = 'block' } = context.options[0] || {};
919
+ const isBlock = style === 'block';
920
+ return {
921
+ [`.description[type=StringValue][block!=${isBlock}]`](node) {
854
922
  context.report({
855
- loc: getLocation((node.kind === graphql.Kind.FIELD && node.alias ? node.alias : node).loc, fieldName, {
856
- offsetEnd: node.kind === graphql.Kind.VARIABLE_DEFINITION ? 0 : 1,
857
- }),
858
- messageId: AVOID_DUPLICATE_FIELDS,
859
- data: {
860
- type,
861
- fieldName,
862
- },
923
+ loc: getLocation(node.loc),
924
+ message: `Unexpected ${isBlock ? 'inline' : 'block'} description`,
863
925
  });
864
- }
865
- else {
866
- usedFields.add(fieldName);
867
- }
868
- }
869
- return {
870
- OperationDefinition(node) {
871
- const set = new Set();
872
- for (const varDef of node.variableDefinitions) {
873
- checkNode(set, varDef.variable.name.value, 'Operation variable', varDef);
874
- }
875
- },
876
- Field(node) {
877
- const set = new Set();
878
- for (const arg of node.arguments) {
879
- checkNode(set, arg.name.value, 'Field argument', arg);
880
- }
881
- },
882
- SelectionSet(node) {
883
- var _a;
884
- const set = new Set();
885
- for (const selection of node.selections) {
886
- if (selection.kind === graphql.Kind.FIELD) {
887
- checkNode(set, ((_a = selection.alias) === null || _a === void 0 ? void 0 : _a.value) || selection.name.value, 'Field', selection);
888
- }
889
- }
890
926
  },
891
927
  };
892
928
  },
893
929
  };
894
930
 
895
- const AVOID_OPERATION_NAME_PREFIX = 'AVOID_OPERATION_NAME_PREFIX';
931
+ const isObjectType = (node) => [graphql.Kind.OBJECT_TYPE_DEFINITION, graphql.Kind.OBJECT_TYPE_EXTENSION].includes(node.type);
932
+ const isQueryType = (node) => isObjectType(node) && node.name.value === 'Query';
933
+ const isMutationType = (node) => isObjectType(node) && node.name.value === 'Mutation';
896
934
  const rule$2 = {
897
935
  meta: {
898
936
  type: 'suggestion',
899
937
  docs: {
900
- description: 'Enforce/avoid operation name prefix, useful if you wish to avoid prefix in your root fields, or avoid using REST terminology in your schema.',
901
- category: 'Stylistic Issues',
902
- url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-operation-name-prefix.md',
938
+ description: 'Require mutation argument to be always called "input" and input type to be called Mutation name + "Input".\nUsing the same name for all input parameters will make your schemas easier to consume and more predictable. Using the same name as mutation for InputType will make it easier to find mutations that InputType belongs to.',
939
+ category: 'Schema',
940
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/input-name.md',
903
941
  examples: [
904
942
  {
905
943
  title: 'Incorrect',
906
- usage: [{ keywords: ['get'] }],
944
+ usage: [{ checkInputType: true }],
907
945
  code: /* GraphQL */ `
908
- query getUserDetails {
909
- # ...
910
- }`,
946
+ type Mutation {
947
+ SetMessage(message: InputMessage): String
948
+ }
949
+ `,
911
950
  },
912
951
  {
913
- title: 'Correct',
914
- usage: [{ keywords: ['get'] }],
952
+ title: 'Correct (with checkInputType)',
953
+ usage: [{ checkInputType: true }],
915
954
  code: /* GraphQL */ `
916
- query userDetails {
917
- # ...
918
- }`,
955
+ type Mutation {
956
+ SetMessage(input: SetMessageInput): String
957
+ }
958
+ `,
959
+ },
960
+ {
961
+ title: 'Correct (without checkInputType)',
962
+ usage: [{ checkInputType: false }],
963
+ code: /* GraphQL */ `
964
+ type Mutation {
965
+ SetMessage(input: AnyInputTypeName): String
966
+ }
967
+ `,
919
968
  },
920
969
  ],
921
970
  },
922
- messages: {
923
- [AVOID_OPERATION_NAME_PREFIX]: `Forbidden operation name prefix: "{{ invalidPrefix }}"`,
924
- },
925
971
  schema: [
926
972
  {
927
- additionalProperties: false,
928
973
  type: 'object',
929
- required: ['keywords'],
974
+ additionalProperties: false,
930
975
  properties: {
931
- caseSensitive: {
976
+ checkInputType: {
977
+ type: 'boolean',
932
978
  default: false,
979
+ description: 'Check that the input type name follows the convention <mutationName>Input',
980
+ },
981
+ caseSensitiveInputType: {
933
982
  type: 'boolean',
983
+ default: true,
984
+ description: 'Allow for case discrepancies in the input type name',
934
985
  },
935
- keywords: {
936
- additionalItems: false,
937
- type: 'array',
938
- minItems: 1,
939
- items: {
940
- type: 'string',
941
- },
942
- },
943
- },
944
- },
945
- ],
946
- },
947
- create(context) {
948
- return {
949
- 'OperationDefinition, FragmentDefinition'(node) {
950
- const config = context.options[0] || { keywords: [], caseSensitive: false };
951
- const caseSensitive = config.caseSensitive;
952
- const keywords = config.keywords || [];
953
- if (node && node.name && node.name.value !== '') {
954
- for (const keyword of keywords) {
955
- const testKeyword = caseSensitive ? keyword : keyword.toLowerCase();
956
- const testName = caseSensitive ? node.name.value : node.name.value.toLowerCase();
957
- if (testName.startsWith(testKeyword)) {
958
- const { start } = node.name.loc;
959
- context.report({
960
- loc: {
961
- start: {
962
- line: start.line,
963
- column: start.column - 1,
964
- },
965
- end: {
966
- line: start.line,
967
- column: start.column - 1 + testKeyword.length,
968
- },
969
- },
970
- data: {
971
- invalidPrefix: keyword,
972
- },
973
- messageId: AVOID_OPERATION_NAME_PREFIX,
974
- });
975
- }
976
- }
977
- }
978
- },
979
- };
980
- },
981
- };
982
-
983
- const rule$3 = {
984
- meta: {
985
- type: 'suggestion',
986
- docs: {
987
- category: 'Best Practices',
988
- description: 'Avoid scalar result type on mutation type to make sure to return a valid state.',
989
- url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-scalar-result-type-on-mutation.md',
990
- requiresSchema: true,
991
- examples: [
992
- {
993
- title: 'Incorrect',
994
- code: /* GraphQL */ `
995
- type Mutation {
996
- createUser: Boolean
997
- }
998
- `,
999
- },
1000
- {
1001
- title: 'Correct',
1002
- code: /* GraphQL */ `
1003
- type Mutation {
1004
- createUser: User!
1005
- }
1006
- `,
1007
- },
1008
- ],
1009
- },
1010
- schema: [],
1011
- },
1012
- create(context) {
1013
- const schema = requireGraphQLSchemaFromContext('avoid-scalar-result-type-on-mutation', context);
1014
- const mutationType = schema.getMutationType();
1015
- if (!mutationType) {
1016
- return {};
1017
- }
1018
- const selector = [
1019
- `:matches(${graphql.Kind.OBJECT_TYPE_DEFINITION}, ${graphql.Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}]`,
1020
- '>',
1021
- graphql.Kind.FIELD_DEFINITION,
1022
- graphql.Kind.NAMED_TYPE,
1023
- ].join(' ');
1024
- return {
1025
- [selector](node) {
1026
- const typeName = node.name.value;
1027
- const graphQLType = schema.getType(typeName);
1028
- if (graphql.isScalarType(graphQLType)) {
1029
- context.report({
1030
- loc: getLocation(node.loc, typeName),
1031
- message: `Unexpected scalar result type "${typeName}"`,
1032
- });
1033
- }
1034
- },
1035
- };
1036
- },
1037
- };
1038
-
1039
- const AVOID_TYPENAME_PREFIX = 'AVOID_TYPENAME_PREFIX';
1040
- const rule$4 = {
1041
- meta: {
1042
- type: 'suggestion',
1043
- docs: {
1044
- category: 'Best Practices',
1045
- description: 'Enforces users to avoid using the type name in a field name while defining your schema.',
1046
- recommended: true,
1047
- url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-typename-prefix.md',
1048
- examples: [
1049
- {
1050
- title: 'Incorrect',
1051
- code: /* GraphQL */ `
1052
- type User {
1053
- userId: ID!
1054
- }
1055
- `,
1056
- },
1057
- {
1058
- title: 'Correct',
1059
- code: /* GraphQL */ `
1060
- type User {
1061
- id: ID!
1062
- }
1063
- `,
1064
- },
1065
- ],
1066
- },
1067
- messages: {
1068
- [AVOID_TYPENAME_PREFIX]: `Field "{{ fieldName }}" starts with the name of the parent type "{{ typeName }}"`,
1069
- },
1070
- schema: [],
1071
- },
1072
- create(context) {
1073
- return {
1074
- 'ObjectTypeDefinition, ObjectTypeExtension, InterfaceTypeDefinition, InterfaceTypeExtension'(node) {
1075
- const typeName = node.name.value;
1076
- const lowerTypeName = typeName.toLowerCase();
1077
- for (const field of node.fields) {
1078
- const fieldName = field.name.value;
1079
- if (fieldName.toLowerCase().startsWith(lowerTypeName)) {
1080
- context.report({
1081
- data: {
1082
- fieldName,
1083
- typeName,
1084
- },
1085
- messageId: AVOID_TYPENAME_PREFIX,
1086
- loc: getLocation(field.loc, lowerTypeName),
1087
- });
1088
- }
1089
- }
1090
- },
1091
- };
1092
- },
1093
- };
1094
-
1095
- const rule$5 = {
1096
- meta: {
1097
- type: 'suggestion',
1098
- docs: {
1099
- examples: [
1100
- {
1101
- title: 'Incorrect',
1102
- usage: [{ style: 'inline' }],
1103
- code: /* GraphQL */ `
1104
- """ Description """
1105
- type someTypeName {
1106
- # ...
1107
- }
1108
- `,
1109
- },
1110
- {
1111
- title: 'Correct',
1112
- usage: [{ style: 'inline' }],
1113
- code: /* GraphQL */ `
1114
- " Description "
1115
- type someTypeName {
1116
- # ...
1117
- }
1118
- `,
1119
- },
1120
- ],
1121
- description: 'Require all comments to follow the same style (either block or inline).',
1122
- category: 'Stylistic Issues',
1123
- url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/description-style.md',
1124
- },
1125
- schema: [
1126
- {
1127
- type: 'object',
1128
- properties: {
1129
- style: {
1130
- type: 'string',
1131
- enum: ['block', 'inline'],
1132
- default: 'inline',
1133
- },
1134
- },
1135
- additionalProperties: false,
1136
- },
1137
- ],
1138
- },
1139
- create(context) {
1140
- const { style } = context.options[0] || { style: 'inline' };
1141
- const wrongDescriptionType = style === 'block' ? 'inline' : 'block';
1142
- return {
1143
- '[description.type="StringValue"]': node => {
1144
- if (node.description.block !== (style === 'block')) {
1145
- context.report({
1146
- loc: getLocation(node.description.loc),
1147
- message: `Unexpected ${wrongDescriptionType} description`,
1148
- });
1149
- }
1150
- },
1151
- };
1152
- },
1153
- };
1154
-
1155
- const isObjectType = (node) => [graphql.Kind.OBJECT_TYPE_DEFINITION, graphql.Kind.OBJECT_TYPE_EXTENSION].includes(node.type);
1156
- const isQueryType = (node) => isObjectType(node) && node.name.value === 'Query';
1157
- const isMutationType = (node) => isObjectType(node) && node.name.value === 'Mutation';
1158
- const rule$6 = {
1159
- meta: {
1160
- type: 'suggestion',
1161
- docs: {
1162
- description: 'Require mutation argument to be always called "input" and input type to be called Mutation name + "Input".\nUsing the same name for all input parameters will make your schemas easier to consume and more predictable. Using the same name as mutation for InputType will make it easier to find mutations that InputType belongs to.',
1163
- category: 'Stylistic Issues',
1164
- url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/input-name.md',
1165
- examples: [
1166
- {
1167
- title: 'Incorrect',
1168
- usage: [{ checkInputType: true }],
1169
- code: /* GraphQL */ `
1170
- type Mutation {
1171
- SetMessage(message: InputMessage): String
1172
- }
1173
- `,
1174
- },
1175
- {
1176
- title: 'Correct (with checkInputType)',
1177
- usage: [{ checkInputType: true }],
1178
- code: /* GraphQL */ `
1179
- type Mutation {
1180
- SetMessage(input: SetMessageInput): String
1181
- }
1182
- `,
1183
- },
1184
- {
1185
- title: 'Correct (without checkInputType)',
1186
- usage: [{ checkInputType: false }],
1187
- code: /* GraphQL */ `
1188
- type Mutation {
1189
- SetMessage(input: AnyInputTypeName): String
1190
- }
1191
- `,
1192
- },
1193
- ],
1194
- },
1195
- schema: [
1196
- {
1197
- type: 'object',
1198
- additionalProperties: false,
1199
- properties: {
1200
- checkInputType: {
1201
- type: 'boolean',
1202
- default: false,
1203
- description: 'Check that the input type name follows the convention <mutationName>Input',
1204
- },
1205
- caseSensitiveInputType: {
1206
- type: 'boolean',
1207
- default: true,
1208
- description: 'Allow for case discrepancies in the input type name',
1209
- },
1210
- checkQueries: {
1211
- type: 'boolean',
1212
- default: false,
1213
- description: 'Apply the rule to Queries',
1214
- },
1215
- checkMutations: {
1216
- type: 'boolean',
1217
- default: true,
1218
- description: 'Apply the rule to Mutations',
986
+ checkQueries: {
987
+ type: 'boolean',
988
+ default: false,
989
+ description: 'Apply the rule to Queries',
990
+ },
991
+ checkMutations: {
992
+ type: 'boolean',
993
+ default: true,
994
+ description: 'Apply the rule to Mutations',
1219
995
  },
1220
996
  },
1221
997
  },
@@ -1281,11 +1057,11 @@ const CASE_STYLES = [
1281
1057
  const schemaOption = {
1282
1058
  oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
1283
1059
  };
1284
- const rule$7 = {
1060
+ const rule$3 = {
1285
1061
  meta: {
1286
1062
  type: 'suggestion',
1287
1063
  docs: {
1288
- category: 'Best Practices',
1064
+ category: 'Operations',
1289
1065
  description: 'This rule allows you to enforce that the file name should match the operation name.',
1290
1066
  url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/match-document-filename.md`,
1291
1067
  examples: [
@@ -1359,6 +1135,14 @@ const rule$7 = {
1359
1135
  `,
1360
1136
  },
1361
1137
  ],
1138
+ configOptions: [
1139
+ {
1140
+ query: CaseStyle.kebabCase,
1141
+ mutation: CaseStyle.kebabCase,
1142
+ subscription: CaseStyle.kebabCase,
1143
+ fragment: CaseStyle.kebabCase,
1144
+ },
1145
+ ],
1362
1146
  },
1363
1147
  messages: {
1364
1148
  [MATCH_EXTENSION]: `File extension "{{ fileExtension }}" don't match extension "{{ expectedFileExtension }}"`,
@@ -1367,27 +1151,29 @@ const rule$7 = {
1367
1151
  schema: {
1368
1152
  definitions: {
1369
1153
  asString: {
1370
- type: 'string',
1371
- description: `One of: ${CASE_STYLES.map(t => `\`${t}\``).join(', ')}`,
1372
1154
  enum: CASE_STYLES,
1155
+ description: `One of: ${CASE_STYLES.map(t => `\`${t}\``).join(', ')}`,
1373
1156
  },
1374
1157
  asObject: {
1375
1158
  type: 'object',
1159
+ additionalProperties: false,
1376
1160
  properties: {
1377
1161
  style: {
1378
- type: 'string',
1379
1162
  enum: CASE_STYLES,
1380
1163
  },
1164
+ suffix: {
1165
+ type: 'string',
1166
+ },
1381
1167
  },
1382
1168
  },
1383
1169
  },
1384
- $schema: 'http://json-schema.org/draft-04/schema#',
1385
1170
  type: 'array',
1171
+ maxItems: 1,
1386
1172
  items: {
1387
1173
  type: 'object',
1174
+ additionalProperties: false,
1388
1175
  properties: {
1389
1176
  fileExtension: {
1390
- type: 'string',
1391
1177
  enum: ACCEPTED_EXTENSIONS,
1392
1178
  },
1393
1179
  query: schemaOption,
@@ -1462,13 +1248,7 @@ const rule$7 = {
1462
1248
  },
1463
1249
  };
1464
1250
 
1465
- const FIELDS_KINDS = [
1466
- graphql.Kind.FIELD_DEFINITION,
1467
- graphql.Kind.INPUT_VALUE_DEFINITION,
1468
- graphql.Kind.VARIABLE_DEFINITION,
1469
- graphql.Kind.ARGUMENT,
1470
- graphql.Kind.DIRECTIVE_DEFINITION,
1471
- ];
1251
+ const FIELDS_KINDS = [graphql.Kind.FIELD_DEFINITION, graphql.Kind.INPUT_VALUE_DEFINITION, graphql.Kind.ARGUMENT, graphql.Kind.DIRECTIVE_DEFINITION];
1472
1252
  const KindToDisplayName = {
1473
1253
  // types
1474
1254
  [graphql.Kind.OBJECT_TYPE_DEFINITION]: 'Type',
@@ -1480,13 +1260,13 @@ const KindToDisplayName = {
1480
1260
  // fields
1481
1261
  [graphql.Kind.FIELD_DEFINITION]: 'Field',
1482
1262
  [graphql.Kind.INPUT_VALUE_DEFINITION]: 'Input property',
1483
- [graphql.Kind.VARIABLE_DEFINITION]: 'Variable',
1484
1263
  [graphql.Kind.ARGUMENT]: 'Argument',
1485
1264
  [graphql.Kind.DIRECTIVE_DEFINITION]: 'Directive',
1486
1265
  // rest
1487
1266
  [graphql.Kind.ENUM_VALUE_DEFINITION]: 'Enumeration value',
1488
1267
  [graphql.Kind.OPERATION_DEFINITION]: 'Operation',
1489
1268
  [graphql.Kind.FRAGMENT_DEFINITION]: 'Fragment',
1269
+ [graphql.Kind.VARIABLE_DEFINITION]: 'Variable',
1490
1270
  };
1491
1271
  const StyleToRegex = {
1492
1272
  camelCase: /^[a-z][\dA-Za-z]*$/,
@@ -1499,12 +1279,12 @@ const ALLOWED_STYLES = Object.keys(StyleToRegex);
1499
1279
  const schemaOption$1 = {
1500
1280
  oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
1501
1281
  };
1502
- const rule$8 = {
1282
+ const rule$4 = {
1503
1283
  meta: {
1504
1284
  type: 'suggestion',
1505
1285
  docs: {
1506
1286
  description: 'Require names to follow specified conventions.',
1507
- category: 'Best Practices',
1287
+ category: ['Schema', 'Operations'],
1508
1288
  recommended: true,
1509
1289
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/naming-convention.md',
1510
1290
  examples: [
@@ -1515,6 +1295,24 @@ const rule$8 = {
1515
1295
  type user {
1516
1296
  first_name: String!
1517
1297
  }
1298
+ `,
1299
+ },
1300
+ {
1301
+ title: 'Incorrect',
1302
+ usage: [{ FragmentDefinition: { style: 'PascalCase', forbiddenSuffixes: ['Fragment'] } }],
1303
+ code: /* GraphQL */ `
1304
+ fragment UserFragment on User {
1305
+ # ...
1306
+ }
1307
+ `,
1308
+ },
1309
+ {
1310
+ title: 'Incorrect',
1311
+ usage: [{ 'FieldDefinition[parent.name.value=Query]': { forbiddenPrefixes: ['get'] } }],
1312
+ code: /* GraphQL */ `
1313
+ type Query {
1314
+ getUsers: [User!]!
1315
+ }
1518
1316
  `,
1519
1317
  },
1520
1318
  {
@@ -1526,23 +1324,31 @@ const rule$8 = {
1526
1324
  }
1527
1325
  `,
1528
1326
  },
1529
- ],
1530
- optionsForConfig: [
1531
1327
  {
1532
- types: 'PascalCase',
1533
- fields: 'camelCase',
1534
- overrides: {
1328
+ title: 'Correct',
1329
+ usage: [{ FragmentDefinition: { style: 'PascalCase', forbiddenSuffixes: ['Fragment'] } }],
1330
+ code: /* GraphQL */ `
1331
+ fragment UserFields on User {
1332
+ # ...
1333
+ }
1334
+ `,
1335
+ },
1336
+ {
1337
+ title: 'Correct',
1338
+ usage: [{ 'FieldDefinition[parent.name.value=Query]': { forbiddenPrefixes: ['get'] } }],
1339
+ code: /* GraphQL */ `
1340
+ type Query {
1341
+ users: [User!]!
1342
+ }
1343
+ `,
1344
+ },
1345
+ ],
1346
+ configOptions: {
1347
+ schema: [
1348
+ {
1349
+ types: 'PascalCase',
1350
+ fields: 'camelCase',
1535
1351
  EnumValueDefinition: 'UPPER_CASE',
1536
- OperationDefinition: {
1537
- style: 'PascalCase',
1538
- forbiddenPrefixes: ['Query', 'Mutation', 'Subscription', 'Get'],
1539
- forbiddenSuffixes: ['Query', 'Mutation', 'Subscription'],
1540
- },
1541
- FragmentDefinition: {
1542
- style: 'PascalCase',
1543
- forbiddenPrefixes: ['Fragment'],
1544
- forbiddenSuffixes: ['Fragment'],
1545
- },
1546
1352
  'FieldDefinition[parent.name.value=Query]': {
1547
1353
  forbiddenPrefixes: ['query', 'get'],
1548
1354
  forbiddenSuffixes: ['Query'],
@@ -1556,8 +1362,24 @@ const rule$8 = {
1556
1362
  forbiddenSuffixes: ['Subscription'],
1557
1363
  },
1558
1364
  },
1559
- },
1560
- ],
1365
+ ],
1366
+ operations: [
1367
+ {
1368
+ Argument: 'camelCase',
1369
+ VariableDefinition: 'camelCase',
1370
+ OperationDefinition: {
1371
+ style: 'PascalCase',
1372
+ forbiddenPrefixes: ['Query', 'Mutation', 'Subscription', 'Get'],
1373
+ forbiddenSuffixes: ['Query', 'Mutation', 'Subscription'],
1374
+ },
1375
+ FragmentDefinition: {
1376
+ style: 'PascalCase',
1377
+ forbiddenPrefixes: ['Fragment'],
1378
+ forbiddenSuffixes: ['Fragment'],
1379
+ },
1380
+ },
1381
+ ],
1382
+ },
1561
1383
  },
1562
1384
  schema: {
1563
1385
  definitions: {
@@ -1595,12 +1417,19 @@ const rule$8 = {
1595
1417
  properties: {
1596
1418
  types: {
1597
1419
  ...schemaOption$1,
1598
- description: `Includes:\n\n${TYPES_KINDS.map(kind => `- [${kind}](https://spec.graphql.org/October2021/#${kind})`).join('\n')}`,
1420
+ description: `Includes:\n\n${TYPES_KINDS.map(kind => `- \`${kind}\``).join('\n')}`,
1599
1421
  },
1600
1422
  fields: {
1601
1423
  ...schemaOption$1,
1602
- description: `Includes:\n\n${FIELDS_KINDS.map(kind => `- [${kind}](https://spec.graphql.org/October2021/#${kind})`).join('\n')}`,
1424
+ description: `Includes:\n\n${FIELDS_KINDS.map(kind => `- \`${kind}\``).join('\n')}`,
1603
1425
  },
1426
+ ...Object.fromEntries(ALLOWED_KINDS.map(kind => [
1427
+ kind,
1428
+ {
1429
+ ...schemaOption$1,
1430
+ description: `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`,
1431
+ },
1432
+ ])),
1604
1433
  allowLeadingUnderscore: {
1605
1434
  type: 'boolean',
1606
1435
  default: false,
@@ -1609,35 +1438,27 @@ const rule$8 = {
1609
1438
  type: 'boolean',
1610
1439
  default: false,
1611
1440
  },
1612
- overrides: {
1613
- type: 'object',
1614
- additionalProperties: false,
1615
- description: [
1616
- 'May contain the following `ASTNode` names:',
1617
- '',
1618
- ...ALLOWED_KINDS.map(kind => `- [${kind}](https://spec.graphql.org/October2021/#${kind})`),
1619
- '',
1620
- "> It's also possible to use a [`selector`](https://eslint.org/docs/developer-guide/selectors) that starts with `ASTNode` name",
1621
- '>',
1622
- '> Example: pattern property `FieldDefinition[parent.name.value=Query]` will match only fields for type `Query`',
1623
- ].join('\n'),
1624
- patternProperties: {
1625
- [`^(${ALLOWED_KINDS.join('|')})(.+)?$`]: schemaOption$1,
1626
- },
1627
- },
1628
1441
  },
1442
+ patternProperties: {
1443
+ [`^(${ALLOWED_KINDS.join('|')})(.+)?$`]: schemaOption$1,
1444
+ },
1445
+ description: [
1446
+ "> It's possible to use a [`selector`](https://eslint.org/docs/developer-guide/selectors) that starts with allowed `ASTNode` names which are described below.",
1447
+ '>',
1448
+ '> Paste or drop code into the editor in [ASTExplorer](https://astexplorer.net) and inspect the generated AST to compose your selector.',
1449
+ '>',
1450
+ '> Example: pattern property `FieldDefinition[parent.name.value=Query]` will match only fields for type `Query`.',
1451
+ ].join('\n'),
1629
1452
  },
1630
1453
  },
1631
1454
  },
1632
1455
  create(context) {
1633
- const options = {
1634
- overrides: {},
1635
- ...context.options[0],
1636
- };
1456
+ const options = context.options[0] || {};
1457
+ const { allowLeadingUnderscore, allowTrailingUnderscore, types, fields, ...restOptions } = options;
1637
1458
  function normalisePropertyOption(kind) {
1638
- let style = options.overrides[kind];
1459
+ let style = options[kind];
1639
1460
  if (!style) {
1640
- style = TYPES_KINDS.includes(kind) ? options.types : options.fields;
1461
+ style = TYPES_KINDS.includes(kind) ? types : fields;
1641
1462
  }
1642
1463
  return typeof style === 'object' ? style : { style };
1643
1464
  }
@@ -1658,10 +1479,10 @@ const rule$8 = {
1658
1479
  }
1659
1480
  function getErrorMessage() {
1660
1481
  let name = nodeName;
1661
- if (options.allowLeadingUnderscore) {
1482
+ if (allowLeadingUnderscore) {
1662
1483
  name = name.replace(/^_*/, '');
1663
1484
  }
1664
- if (options.allowTrailingUnderscore) {
1485
+ if (allowTrailingUnderscore) {
1665
1486
  name = name.replace(/_*$/, '');
1666
1487
  }
1667
1488
  if (prefix && !name.startsWith(prefix)) {
@@ -1695,15 +1516,13 @@ const rule$8 = {
1695
1516
  });
1696
1517
  };
1697
1518
  const listeners = {};
1698
- if (!options.allowLeadingUnderscore) {
1519
+ if (!allowLeadingUnderscore) {
1699
1520
  listeners['Name[value=/^_/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore;
1700
1521
  }
1701
- if (!options.allowTrailingUnderscore) {
1522
+ if (!allowTrailingUnderscore) {
1702
1523
  listeners['Name[value=/_$/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore;
1703
1524
  }
1704
- const selectors = new Set([options.types && TYPES_KINDS, options.fields && FIELDS_KINDS, Object.keys(options.overrides)]
1705
- .flat()
1706
- .filter(Boolean));
1525
+ const selectors = new Set([types && TYPES_KINDS, fields && FIELDS_KINDS, Object.keys(restOptions)].flat().filter(Boolean));
1707
1526
  for (const selector of selectors) {
1708
1527
  listeners[selector] = checkNode(selector);
1709
1528
  }
@@ -1712,11 +1531,11 @@ const rule$8 = {
1712
1531
  };
1713
1532
 
1714
1533
  const NO_ANONYMOUS_OPERATIONS = 'NO_ANONYMOUS_OPERATIONS';
1715
- const rule$9 = {
1534
+ const rule$5 = {
1716
1535
  meta: {
1717
1536
  type: 'suggestion',
1718
1537
  docs: {
1719
- category: 'Best Practices',
1538
+ category: 'Operations',
1720
1539
  description: 'Require name for your GraphQL operations. This is useful since most GraphQL client libraries are using the operation name for caching purposes.',
1721
1540
  recommended: true,
1722
1541
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-anonymous-operations.md',
@@ -1760,12 +1579,12 @@ const rule$9 = {
1760
1579
  };
1761
1580
 
1762
1581
  const ERROR_MESSAGE_ID = 'NO_CASE_INSENSITIVE_ENUM_VALUES_DUPLICATES';
1763
- const rule$a = {
1582
+ const rule$6 = {
1764
1583
  meta: {
1765
1584
  type: 'suggestion',
1766
1585
  docs: {
1767
1586
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-case-insensitive-enum-values-duplicates.md',
1768
- category: 'Best Practices',
1587
+ category: 'Schema',
1769
1588
  recommended: true,
1770
1589
  description: 'Disallow case-insensitive enum values duplicates.',
1771
1590
  examples: [
@@ -1816,11 +1635,11 @@ const rule$a = {
1816
1635
  };
1817
1636
 
1818
1637
  const NO_DEPRECATED = 'NO_DEPRECATED';
1819
- const rule$b = {
1638
+ const rule$7 = {
1820
1639
  meta: {
1821
1640
  type: 'suggestion',
1822
1641
  docs: {
1823
- category: 'Best Practices',
1642
+ category: 'Operations',
1824
1643
  description: `Enforce that deprecated fields or enum values are not in use by operations.`,
1825
1644
  url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-deprecated.md`,
1826
1645
  requiresSchema: true,
@@ -1883,48 +1702,150 @@ const rule$b = {
1883
1702
  fullName
1884
1703
  }
1885
1704
  }
1886
- `,
1887
- },
1888
- ],
1889
- },
1890
- messages: {
1891
- [NO_DEPRECATED]: `This {{ type }} is marked as deprecated in your GraphQL schema {{ reason }}`,
1892
- },
1893
- schema: [],
1894
- },
1895
- create(context) {
1705
+ `,
1706
+ },
1707
+ ],
1708
+ recommended: true,
1709
+ },
1710
+ messages: {
1711
+ [NO_DEPRECATED]: `This {{ type }} is marked as deprecated in your GraphQL schema {{ reason }}`,
1712
+ },
1713
+ schema: [],
1714
+ },
1715
+ create(context) {
1716
+ return {
1717
+ EnumValue(node) {
1718
+ requireGraphQLSchemaFromContext('no-deprecated', context);
1719
+ const typeInfo = node.typeInfo();
1720
+ if (typeInfo && typeInfo.enumValue) {
1721
+ if (typeInfo.enumValue.deprecationReason) {
1722
+ const enumValueName = node.value;
1723
+ context.report({
1724
+ loc: getLocation(node.loc, enumValueName),
1725
+ messageId: NO_DEPRECATED,
1726
+ data: {
1727
+ type: 'enum value',
1728
+ reason: typeInfo.enumValue.deprecationReason ? `(reason: ${typeInfo.enumValue.deprecationReason})` : '',
1729
+ },
1730
+ });
1731
+ }
1732
+ }
1733
+ },
1734
+ Field(node) {
1735
+ requireGraphQLSchemaFromContext('no-deprecated', context);
1736
+ const typeInfo = node.typeInfo();
1737
+ if (typeInfo && typeInfo.fieldDef) {
1738
+ if (typeInfo.fieldDef.deprecationReason) {
1739
+ const fieldName = node.name.value;
1740
+ context.report({
1741
+ loc: getLocation(node.loc, fieldName),
1742
+ messageId: NO_DEPRECATED,
1743
+ data: {
1744
+ type: 'field',
1745
+ reason: typeInfo.fieldDef.deprecationReason ? `(reason: ${typeInfo.fieldDef.deprecationReason})` : '',
1746
+ },
1747
+ });
1748
+ }
1749
+ }
1750
+ },
1751
+ };
1752
+ },
1753
+ };
1754
+
1755
+ const NO_DUPLICATE_FIELDS = 'NO_DUPLICATE_FIELDS';
1756
+ const rule$8 = {
1757
+ meta: {
1758
+ type: 'suggestion',
1759
+ docs: {
1760
+ description: `Checks for duplicate fields in selection set, variables in operation definition, or in arguments set of a field.`,
1761
+ category: 'Operations',
1762
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-duplicate-fields.md',
1763
+ recommended: true,
1764
+ examples: [
1765
+ {
1766
+ title: 'Incorrect',
1767
+ code: /* GraphQL */ `
1768
+ query {
1769
+ user {
1770
+ name
1771
+ email
1772
+ name # duplicate field
1773
+ }
1774
+ }
1775
+ `,
1776
+ },
1777
+ {
1778
+ title: 'Incorrect',
1779
+ code: /* GraphQL */ `
1780
+ query {
1781
+ users(
1782
+ first: 100
1783
+ skip: 50
1784
+ after: "cji629tngfgou0b73kt7vi5jo"
1785
+ first: 100 # duplicate argument
1786
+ ) {
1787
+ id
1788
+ }
1789
+ }
1790
+ `,
1791
+ },
1792
+ {
1793
+ title: 'Incorrect',
1794
+ code: /* GraphQL */ `
1795
+ query (
1796
+ $first: Int!
1797
+ $first: Int! # duplicate variable
1798
+ ) {
1799
+ users(first: $first, skip: 50) {
1800
+ id
1801
+ }
1802
+ }
1803
+ `,
1804
+ },
1805
+ ],
1806
+ },
1807
+ messages: {
1808
+ [NO_DUPLICATE_FIELDS]: `{{ type }} "{{ fieldName }}" defined multiple times`,
1809
+ },
1810
+ schema: [],
1811
+ },
1812
+ create(context) {
1813
+ function checkNode(usedFields, fieldName, type, node) {
1814
+ if (usedFields.has(fieldName)) {
1815
+ context.report({
1816
+ loc: getLocation((node.kind === graphql.Kind.FIELD && node.alias ? node.alias : node).loc, fieldName, {
1817
+ offsetEnd: node.kind === graphql.Kind.VARIABLE_DEFINITION ? 0 : 1,
1818
+ }),
1819
+ messageId: NO_DUPLICATE_FIELDS,
1820
+ data: {
1821
+ type,
1822
+ fieldName,
1823
+ },
1824
+ });
1825
+ }
1826
+ else {
1827
+ usedFields.add(fieldName);
1828
+ }
1829
+ }
1896
1830
  return {
1897
- EnumValue(node) {
1898
- requireGraphQLSchemaFromContext('no-deprecated', context);
1899
- const typeInfo = node.typeInfo();
1900
- if (typeInfo && typeInfo.enumValue) {
1901
- if (typeInfo.enumValue.deprecationReason) {
1902
- const enumValueName = node.value;
1903
- context.report({
1904
- loc: getLocation(node.loc, enumValueName),
1905
- messageId: NO_DEPRECATED,
1906
- data: {
1907
- type: 'enum value',
1908
- reason: typeInfo.enumValue.deprecationReason ? `(reason: ${typeInfo.enumValue.deprecationReason})` : '',
1909
- },
1910
- });
1911
- }
1831
+ OperationDefinition(node) {
1832
+ const set = new Set();
1833
+ for (const varDef of node.variableDefinitions) {
1834
+ checkNode(set, varDef.variable.name.value, 'Operation variable', varDef);
1912
1835
  }
1913
1836
  },
1914
1837
  Field(node) {
1915
- requireGraphQLSchemaFromContext('no-deprecated', context);
1916
- const typeInfo = node.typeInfo();
1917
- if (typeInfo && typeInfo.fieldDef) {
1918
- if (typeInfo.fieldDef.deprecationReason) {
1919
- const fieldName = node.name.value;
1920
- context.report({
1921
- loc: getLocation(node.loc, fieldName),
1922
- messageId: NO_DEPRECATED,
1923
- data: {
1924
- type: 'field',
1925
- reason: typeInfo.fieldDef.deprecationReason ? `(reason: ${typeInfo.fieldDef.deprecationReason})` : '',
1926
- },
1927
- });
1838
+ const set = new Set();
1839
+ for (const arg of node.arguments) {
1840
+ checkNode(set, arg.name.value, 'Field argument', arg);
1841
+ }
1842
+ },
1843
+ SelectionSet(node) {
1844
+ var _a;
1845
+ const set = new Set();
1846
+ for (const selection of node.selections) {
1847
+ if (selection.kind === graphql.Kind.FIELD) {
1848
+ checkNode(set, ((_a = selection.alias) === null || _a === void 0 ? void 0 : _a.value) || selection.name.value, 'Field', selection);
1928
1849
  }
1929
1850
  }
1930
1851
  },
@@ -1933,14 +1854,14 @@ const rule$b = {
1933
1854
  };
1934
1855
 
1935
1856
  const HASHTAG_COMMENT = 'HASHTAG_COMMENT';
1936
- const rule$c = {
1857
+ const rule$9 = {
1937
1858
  meta: {
1938
1859
  messages: {
1939
1860
  [HASHTAG_COMMENT]: 'Using hashtag (#) for adding GraphQL descriptions is not allowed. Prefer using """ for multiline, or " for a single line description.',
1940
1861
  },
1941
1862
  docs: {
1942
1863
  description: 'Requires to use `"""` or `"` for adding a GraphQL description instead of `#`.\nAllows to use hashtag for comments, as long as it\'s not attached to an AST definition.',
1943
- category: 'Best Practices',
1864
+ category: 'Schema',
1944
1865
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-hashtag-description.md',
1945
1866
  examples: [
1946
1867
  {
@@ -1977,6 +1898,7 @@ const rule$c = {
1977
1898
  `,
1978
1899
  },
1979
1900
  ],
1901
+ recommended: true,
1980
1902
  },
1981
1903
  type: 'suggestion',
1982
1904
  schema: [],
@@ -2005,85 +1927,18 @@ const rule$c = {
2005
1927
  },
2006
1928
  };
2007
1929
 
2008
- const NO_OPERATION_NAME_SUFFIX = 'NO_OPERATION_NAME_SUFFIX';
2009
- const rule$d = {
2010
- meta: {
2011
- fixable: 'code',
2012
- type: 'suggestion',
2013
- docs: {
2014
- category: 'Stylistic Issues',
2015
- recommended: true,
2016
- description: `Makes sure you are not adding the operation type to the name of the operation.`,
2017
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-operation-name-suffix.md`,
2018
- examples: [
2019
- {
2020
- title: 'Incorrect',
2021
- code: /* GraphQL */ `
2022
- query userQuery {
2023
- # ...
2024
- }
2025
- `,
2026
- },
2027
- {
2028
- title: 'Correct',
2029
- code: /* GraphQL */ `
2030
- query user {
2031
- # ...
2032
- }
2033
- `,
2034
- },
2035
- ],
2036
- },
2037
- messages: {
2038
- [NO_OPERATION_NAME_SUFFIX]: `Unnecessary "{{ invalidSuffix }}" suffix in your operation name!`,
2039
- },
2040
- schema: [],
2041
- },
2042
- create(context) {
2043
- return {
2044
- 'OperationDefinition, FragmentDefinition'(node) {
2045
- var _a;
2046
- const name = ((_a = node.name) === null || _a === void 0 ? void 0 : _a.value) || '';
2047
- if (name.length > 0) {
2048
- const invalidSuffix = 'operation' in node ? node.operation : 'fragment';
2049
- if (name.toLowerCase().endsWith(invalidSuffix)) {
2050
- const { start, end } = node.name.loc;
2051
- context.report({
2052
- loc: {
2053
- start: {
2054
- column: start.column - 1 + name.length - invalidSuffix.length,
2055
- line: start.line,
2056
- },
2057
- end: {
2058
- column: end.column - 1 + name.length,
2059
- line: end.line,
2060
- },
2061
- },
2062
- data: {
2063
- invalidSuffix,
2064
- },
2065
- fix: fixer => fixer.removeRange([node.name.range[1] - invalidSuffix.length, node.name.range[1]]),
2066
- messageId: NO_OPERATION_NAME_SUFFIX,
2067
- });
2068
- }
2069
- }
2070
- },
2071
- };
2072
- },
2073
- };
2074
-
2075
- const ROOT_TYPES = ['query', 'mutation', 'subscription'];
2076
- const rule$e = {
1930
+ const ROOT_TYPES = ['mutation', 'subscription'];
1931
+ const rule$a = {
2077
1932
  meta: {
2078
1933
  type: 'suggestion',
2079
1934
  docs: {
2080
- category: 'Validation',
2081
- description: 'Disallow using root types for `read-only` or `write-only` schemas.',
1935
+ category: 'Schema',
1936
+ description: 'Disallow using root types `mutation` and/or `subscription`.',
2082
1937
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-root-type.md',
2083
1938
  requiresSchema: true,
2084
1939
  examples: [
2085
1940
  {
2086
- title: 'Incorrect (`read-only` schema)',
1941
+ title: 'Incorrect',
2087
1942
  usage: [{ disallow: ['mutation', 'subscription'] }],
2088
1943
  code: /* GraphQL */ `
2089
1944
  type Mutation {
@@ -2092,16 +1947,7 @@ const rule$e = {
2092
1947
  `,
2093
1948
  },
2094
1949
  {
2095
- title: 'Incorrect (`write-only` schema)',
2096
- usage: [{ disallow: ['query'] }],
2097
- code: /* GraphQL */ `
2098
- type Query {
2099
- users: [User!]!
2100
- }
2101
- `,
2102
- },
2103
- {
2104
- title: 'Correct (`read-only` schema)',
1950
+ title: 'Correct',
2105
1951
  usage: [{ disallow: ['mutation', 'subscription'] }],
2106
1952
  code: /* GraphQL */ `
2107
1953
  type Query {
@@ -2110,7 +1956,6 @@ const rule$e = {
2110
1956
  `,
2111
1957
  },
2112
1958
  ],
2113
- optionsForConfig: [{ disallow: ['subscription'] }],
2114
1959
  },
2115
1960
  schema: {
2116
1961
  type: 'array',
@@ -2137,7 +1982,6 @@ const rule$e = {
2137
1982
  const schema = requireGraphQLSchemaFromContext('no-root-type', context);
2138
1983
  const disallow = new Set(context.options[0].disallow);
2139
1984
  const rootTypeNames = [
2140
- disallow.has('query') && schema.getQueryType(),
2141
1985
  disallow.has('mutation') && schema.getMutationType(),
2142
1986
  disallow.has('subscription') && schema.getSubscriptionType(),
2143
1987
  ]
@@ -2163,16 +2007,128 @@ const rule$e = {
2163
2007
  },
2164
2008
  };
2165
2009
 
2010
+ const rule$b = {
2011
+ meta: {
2012
+ type: 'suggestion',
2013
+ docs: {
2014
+ category: 'Schema',
2015
+ description: 'Avoid scalar result type on mutation type to make sure to return a valid state.',
2016
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-scalar-result-type-on-mutation.md',
2017
+ requiresSchema: true,
2018
+ examples: [
2019
+ {
2020
+ title: 'Incorrect',
2021
+ code: /* GraphQL */ `
2022
+ type Mutation {
2023
+ createUser: Boolean
2024
+ }
2025
+ `,
2026
+ },
2027
+ {
2028
+ title: 'Correct',
2029
+ code: /* GraphQL */ `
2030
+ type Mutation {
2031
+ createUser: User!
2032
+ }
2033
+ `,
2034
+ },
2035
+ ],
2036
+ },
2037
+ schema: [],
2038
+ },
2039
+ create(context) {
2040
+ const schema = requireGraphQLSchemaFromContext('no-scalar-result-type-on-mutation', context);
2041
+ const mutationType = schema.getMutationType();
2042
+ if (!mutationType) {
2043
+ return {};
2044
+ }
2045
+ const selector = [
2046
+ `:matches(${graphql.Kind.OBJECT_TYPE_DEFINITION}, ${graphql.Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}]`,
2047
+ '>',
2048
+ graphql.Kind.FIELD_DEFINITION,
2049
+ graphql.Kind.NAMED_TYPE,
2050
+ ].join(' ');
2051
+ return {
2052
+ [selector](node) {
2053
+ const typeName = node.name.value;
2054
+ const graphQLType = schema.getType(typeName);
2055
+ if (graphql.isScalarType(graphQLType)) {
2056
+ context.report({
2057
+ loc: getLocation(node.loc, typeName),
2058
+ message: `Unexpected scalar result type "${typeName}"`,
2059
+ });
2060
+ }
2061
+ },
2062
+ };
2063
+ },
2064
+ };
2065
+
2066
+ const NO_TYPENAME_PREFIX = 'NO_TYPENAME_PREFIX';
2067
+ const rule$c = {
2068
+ meta: {
2069
+ type: 'suggestion',
2070
+ docs: {
2071
+ category: 'Schema',
2072
+ description: 'Enforces users to avoid using the type name in a field name while defining your schema.',
2073
+ recommended: true,
2074
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-typename-prefix.md',
2075
+ examples: [
2076
+ {
2077
+ title: 'Incorrect',
2078
+ code: /* GraphQL */ `
2079
+ type User {
2080
+ userId: ID!
2081
+ }
2082
+ `,
2083
+ },
2084
+ {
2085
+ title: 'Correct',
2086
+ code: /* GraphQL */ `
2087
+ type User {
2088
+ id: ID!
2089
+ }
2090
+ `,
2091
+ },
2092
+ ],
2093
+ },
2094
+ messages: {
2095
+ [NO_TYPENAME_PREFIX]: `Field "{{ fieldName }}" starts with the name of the parent type "{{ typeName }}"`,
2096
+ },
2097
+ schema: [],
2098
+ },
2099
+ create(context) {
2100
+ return {
2101
+ 'ObjectTypeDefinition, ObjectTypeExtension, InterfaceTypeDefinition, InterfaceTypeExtension'(node) {
2102
+ const typeName = node.name.value;
2103
+ const lowerTypeName = typeName.toLowerCase();
2104
+ for (const field of node.fields) {
2105
+ const fieldName = field.name.value;
2106
+ if (fieldName.toLowerCase().startsWith(lowerTypeName)) {
2107
+ context.report({
2108
+ data: {
2109
+ fieldName,
2110
+ typeName,
2111
+ },
2112
+ messageId: NO_TYPENAME_PREFIX,
2113
+ loc: getLocation(field.loc, lowerTypeName),
2114
+ });
2115
+ }
2116
+ }
2117
+ },
2118
+ };
2119
+ },
2120
+ };
2121
+
2166
2122
  const UNREACHABLE_TYPE = 'UNREACHABLE_TYPE';
2167
2123
  const RULE_NAME = 'no-unreachable-types';
2168
- const rule$f = {
2124
+ const rule$d = {
2169
2125
  meta: {
2170
2126
  messages: {
2171
- [UNREACHABLE_TYPE]: `Type "{{ typeName }}" is unreachable`,
2127
+ [UNREACHABLE_TYPE]: 'Type "{{ typeName }}" is unreachable',
2172
2128
  },
2173
2129
  docs: {
2174
2130
  description: `Requires all types to be reachable at some level by root level fields.`,
2175
- category: 'Best Practices',
2131
+ category: 'Schema',
2176
2132
  url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME}.md`,
2177
2133
  requiresSchema: true,
2178
2134
  examples: [
@@ -2203,6 +2159,7 @@ const rule$f = {
2203
2159
  `,
2204
2160
  },
2205
2161
  ],
2162
+ recommended: true,
2206
2163
  },
2207
2164
  fixable: 'code',
2208
2165
  type: 'suggestion',
@@ -2241,14 +2198,14 @@ const rule$f = {
2241
2198
 
2242
2199
  const UNUSED_FIELD = 'UNUSED_FIELD';
2243
2200
  const RULE_NAME$1 = 'no-unused-fields';
2244
- const rule$g = {
2201
+ const rule$e = {
2245
2202
  meta: {
2246
2203
  messages: {
2247
2204
  [UNUSED_FIELD]: `Field "{{fieldName}}" is unused`,
2248
2205
  },
2249
2206
  docs: {
2250
2207
  description: `Requires all fields to be used at some level by siblings operations.`,
2251
- category: 'Best Practices',
2208
+ category: 'Schema',
2252
2209
  url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$1}.md`,
2253
2210
  requiresSiblings: true,
2254
2211
  requiresSchema: true,
@@ -2430,11 +2387,11 @@ const MESSAGE_REQUIRE_DATE = 'MESSAGE_REQUIRE_DATE';
2430
2387
  const MESSAGE_INVALID_FORMAT = 'MESSAGE_INVALID_FORMAT';
2431
2388
  const MESSAGE_INVALID_DATE = 'MESSAGE_INVALID_DATE';
2432
2389
  const MESSAGE_CAN_BE_REMOVED = 'MESSAGE_CAN_BE_REMOVED';
2433
- const rule$h = {
2390
+ const rule$f = {
2434
2391
  meta: {
2435
2392
  type: 'suggestion',
2436
2393
  docs: {
2437
- category: 'Best Practices',
2394
+ category: 'Schema',
2438
2395
  description: 'Require deletion date on `@deprecated` directive. Suggest removing deprecated things after deprecated date.',
2439
2396
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-deprecation-date.md',
2440
2397
  examples: [
@@ -2533,11 +2490,11 @@ const rule$h = {
2533
2490
  },
2534
2491
  };
2535
2492
 
2536
- const rule$i = {
2493
+ const rule$g = {
2537
2494
  meta: {
2538
2495
  docs: {
2539
2496
  description: `Require all deprecation directives to specify a reason.`,
2540
- category: 'Best Practices',
2497
+ category: 'Schema',
2541
2498
  url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-deprecation-reason.md`,
2542
2499
  recommended: true,
2543
2500
  examples: [
@@ -2595,16 +2552,16 @@ const ALLOWED_KINDS$1 = [
2595
2552
  graphql.Kind.ENUM_VALUE_DEFINITION,
2596
2553
  graphql.Kind.DIRECTIVE_DEFINITION,
2597
2554
  ];
2598
- const rule$j = {
2555
+ const rule$h = {
2599
2556
  meta: {
2600
2557
  docs: {
2601
- category: 'Best Practices',
2558
+ category: 'Schema',
2602
2559
  description: 'Enforce descriptions in your type definitions.',
2603
2560
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-description.md',
2604
2561
  examples: [
2605
2562
  {
2606
2563
  title: 'Incorrect',
2607
- usage: [{ types: true, overrides: { FieldDefinition: true } }],
2564
+ usage: [{ types: true, FieldDefinition: true }],
2608
2565
  code: /* GraphQL */ `
2609
2566
  type someTypeName {
2610
2567
  name: String
@@ -2613,7 +2570,7 @@ const rule$j = {
2613
2570
  },
2614
2571
  {
2615
2572
  title: 'Correct',
2616
- usage: [{ types: true, overrides: { FieldDefinition: true } }],
2573
+ usage: [{ types: true, FieldDefinition: true }],
2617
2574
  code: /* GraphQL */ `
2618
2575
  """
2619
2576
  Some type description
@@ -2627,14 +2584,13 @@ const rule$j = {
2627
2584
  `,
2628
2585
  },
2629
2586
  ],
2630
- optionsForConfig: [
2587
+ configOptions: [
2631
2588
  {
2632
2589
  types: true,
2633
- overrides: {
2634
- [graphql.Kind.DIRECTIVE_DEFINITION]: true,
2635
- },
2590
+ [graphql.Kind.DIRECTIVE_DEFINITION]: true,
2636
2591
  },
2637
2592
  ],
2593
+ recommended: true,
2638
2594
  },
2639
2595
  type: 'suggestion',
2640
2596
  messages: {
@@ -2651,22 +2607,23 @@ const rule$j = {
2651
2607
  properties: {
2652
2608
  types: {
2653
2609
  type: 'boolean',
2654
- description: `Includes:\n\n${TYPES_KINDS.map(kind => `- [${kind}](https://spec.graphql.org/October2021/#${kind})`).join('\n')}`,
2655
- },
2656
- overrides: {
2657
- type: 'object',
2658
- description: 'Configuration for precise `ASTNode`',
2659
- additionalProperties: false,
2660
- properties: Object.fromEntries(ALLOWED_KINDS$1.map(kind => [kind, { type: 'boolean' }])),
2610
+ description: `Includes:\n\n${TYPES_KINDS.map(kind => `- \`${kind}\``).join('\n')}`,
2661
2611
  },
2612
+ ...Object.fromEntries([...ALLOWED_KINDS$1].sort().map(kind => [
2613
+ kind,
2614
+ {
2615
+ type: 'boolean',
2616
+ description: `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`,
2617
+ },
2618
+ ])),
2662
2619
  },
2663
2620
  },
2664
2621
  },
2665
2622
  },
2666
2623
  create(context) {
2667
- const { types, overrides = {} } = context.options[0];
2624
+ const { types, ...restOptions } = context.options[0];
2668
2625
  const kinds = new Set(types ? TYPES_KINDS : []);
2669
- for (const [kind, isEnabled] of Object.entries(overrides)) {
2626
+ for (const [kind, isEnabled] of Object.entries(restOptions)) {
2670
2627
  if (isEnabled) {
2671
2628
  kinds.add(kind);
2672
2629
  }
@@ -2694,11 +2651,11 @@ const rule$j = {
2694
2651
  };
2695
2652
 
2696
2653
  const RULE_NAME$2 = 'require-field-of-type-query-in-mutation-result';
2697
- const rule$k = {
2654
+ const rule$i = {
2698
2655
  meta: {
2699
2656
  type: 'suggestion',
2700
2657
  docs: {
2701
- category: 'Best Practices',
2658
+ category: 'Schema',
2702
2659
  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`.',
2703
2660
  url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$2}.md`,
2704
2661
  requiresSchema: true,
@@ -2862,13 +2819,13 @@ const convertNode = (typeInfo) => (node, key, parent) => {
2862
2819
 
2863
2820
  const REQUIRE_ID_WHEN_AVAILABLE = 'REQUIRE_ID_WHEN_AVAILABLE';
2864
2821
  const DEFAULT_ID_FIELD_NAME = 'id';
2865
- const rule$l = {
2822
+ const rule$j = {
2866
2823
  meta: {
2867
2824
  type: 'suggestion',
2868
2825
  docs: {
2869
- category: 'Best Practices',
2870
- description: `Enforce selecting specific fields when they are available on the GraphQL type.`,
2871
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-id-when-available.md`,
2826
+ category: 'Operations',
2827
+ description: 'Enforce selecting specific fields when they are available on the GraphQL type.',
2828
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-id-when-available.md',
2872
2829
  requiresSchema: true,
2873
2830
  requiresSiblings: true,
2874
2831
  examples: [
@@ -2908,17 +2865,17 @@ const rule$l = {
2908
2865
  `,
2909
2866
  },
2910
2867
  ],
2868
+ recommended: true,
2911
2869
  },
2912
2870
  messages: {
2913
- [REQUIRE_ID_WHEN_AVAILABLE]: `Field "{{ fieldName }}" must be selected when it's available on a type. Please make sure to include it in your selection set!\nIf you are using fragments, make sure that all used fragments {{checkedFragments}} specifies the field "{{ fieldName }}".`,
2871
+ [REQUIRE_ID_WHEN_AVAILABLE]: `Field "{{ fieldName }}" must be selected when it's available on a type. Please make sure to include it in your selection set!\nIf you are using fragments, make sure that all used fragments {{ checkedFragments }} specifies the field "{{ fieldName }}".`,
2914
2872
  },
2915
2873
  schema: {
2916
2874
  type: 'array',
2917
- additionalItems: false,
2918
- minItems: 0,
2919
2875
  maxItems: 1,
2920
2876
  items: {
2921
2877
  type: 'object',
2878
+ additionalProperties: false,
2922
2879
  properties: {
2923
2880
  fieldName: {
2924
2881
  type: 'string',
@@ -2998,12 +2955,12 @@ const rule$l = {
2998
2955
  },
2999
2956
  };
3000
2957
 
3001
- const rule$m = {
2958
+ const rule$k = {
3002
2959
  meta: {
3003
2960
  docs: {
3004
- category: 'Best Practices',
2961
+ category: 'Operations',
3005
2962
  description: `Limit the complexity of the GraphQL operations solely by their depth. Based on [graphql-depth-limit](https://github.com/stems/graphql-depth-limit).`,
3006
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/selection-set-depth.md`,
2963
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/selection-set-depth.md',
3007
2964
  requiresSiblings: true,
3008
2965
  examples: [
3009
2966
  {
@@ -3046,22 +3003,26 @@ const rule$m = {
3046
3003
  `,
3047
3004
  },
3048
3005
  ],
3006
+ recommended: true,
3007
+ configOptions: [{ maxDepth: 7 }],
3049
3008
  },
3050
3009
  type: 'suggestion',
3051
3010
  schema: {
3052
3011
  type: 'array',
3053
- additionalItems: false,
3054
3012
  minItems: 1,
3055
3013
  maxItems: 1,
3056
3014
  items: {
3057
3015
  type: 'object',
3058
- require: ['maxDepth'],
3016
+ additionalProperties: false,
3017
+ required: ['maxDepth'],
3059
3018
  properties: {
3060
3019
  maxDepth: {
3061
3020
  type: 'number',
3062
3021
  },
3063
3022
  ignore: {
3064
3023
  type: 'array',
3024
+ uniqueItems: true,
3025
+ minItems: 1,
3065
3026
  items: {
3066
3027
  type: 'string',
3067
3028
  },
@@ -3120,12 +3081,12 @@ const shouldIgnoreNode = ({ node, exceptions }) => {
3120
3081
  }
3121
3082
  return false;
3122
3083
  };
3123
- const rule$n = {
3084
+ const rule$l = {
3124
3085
  meta: {
3125
3086
  type: 'suggestion',
3126
3087
  docs: {
3127
3088
  description: 'Requires output types to have one unique identifier unless they do not have a logical one. Exceptions can be used to ignore output types that do not have unique identifiers.',
3128
- category: 'Best Practices',
3089
+ category: 'Schema',
3129
3090
  recommended: true,
3130
3091
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/strict-id-in-types.md',
3131
3092
  examples: [
@@ -3185,13 +3146,15 @@ const rule$n = {
3185
3146
  ],
3186
3147
  },
3187
3148
  schema: {
3188
- $schema: 'http://json-schema.org/draft-04/schema#',
3189
3149
  type: 'array',
3150
+ maxItems: 1,
3190
3151
  items: {
3191
3152
  type: 'object',
3153
+ additionalProperties: false,
3192
3154
  properties: {
3193
3155
  acceptedIdNames: {
3194
3156
  type: 'array',
3157
+ uniqueItems: true,
3195
3158
  items: {
3196
3159
  type: 'string',
3197
3160
  },
@@ -3199,6 +3162,7 @@ const rule$n = {
3199
3162
  },
3200
3163
  acceptedIdTypes: {
3201
3164
  type: 'array',
3165
+ uniqueItems: true,
3202
3166
  items: {
3203
3167
  type: 'string',
3204
3168
  },
@@ -3209,19 +3173,21 @@ const rule$n = {
3209
3173
  properties: {
3210
3174
  types: {
3211
3175
  type: 'array',
3176
+ uniqueItems: true,
3177
+ minItems: 1,
3212
3178
  description: 'This is used to exclude types with names that match one of the specified values.',
3213
3179
  items: {
3214
3180
  type: 'string',
3215
3181
  },
3216
- default: [],
3217
3182
  },
3218
3183
  suffixes: {
3219
3184
  type: 'array',
3185
+ uniqueItems: true,
3186
+ minItems: 1,
3220
3187
  description: 'This is used to exclude types with names with suffixes that match one of the specified values.',
3221
3188
  items: {
3222
3189
  type: 'string',
3223
3190
  },
3224
- default: [],
3225
3191
  },
3226
3192
  },
3227
3193
  },
@@ -3297,11 +3263,11 @@ const checkNode = (context, node, ruleName, messageId) => {
3297
3263
  });
3298
3264
  }
3299
3265
  };
3300
- const rule$o = {
3266
+ const rule$m = {
3301
3267
  meta: {
3302
3268
  type: 'suggestion',
3303
3269
  docs: {
3304
- category: 'Best Practices',
3270
+ category: 'Operations',
3305
3271
  description: `Enforce unique fragment names across your project.`,
3306
3272
  url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$3}.md`,
3307
3273
  requiresSiblings: true,
@@ -3356,11 +3322,11 @@ const rule$o = {
3356
3322
 
3357
3323
  const RULE_NAME$4 = 'unique-operation-name';
3358
3324
  const UNIQUE_OPERATION_NAME = 'UNIQUE_OPERATION_NAME';
3359
- const rule$p = {
3325
+ const rule$n = {
3360
3326
  meta: {
3361
3327
  type: 'suggestion',
3362
3328
  docs: {
3363
- category: 'Best Practices',
3329
+ category: 'Operations',
3364
3330
  description: `Enforce unique operation names across your project.`,
3365
3331
  url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$4}.md`,
3366
3332
  requiresSiblings: true,
@@ -3423,31 +3389,29 @@ const rule$p = {
3423
3389
  const rules = {
3424
3390
  ...GRAPHQL_JS_VALIDATIONS,
3425
3391
  alphabetize: rule,
3426
- 'avoid-duplicate-fields': rule$1,
3427
- 'avoid-operation-name-prefix': rule$2,
3428
- 'avoid-scalar-result-type-on-mutation': rule$3,
3429
- 'avoid-typename-prefix': rule$4,
3430
- 'description-style': rule$5,
3431
- 'input-name': rule$6,
3432
- 'match-document-filename': rule$7,
3433
- 'naming-convention': rule$8,
3434
- 'no-anonymous-operations': rule$9,
3435
- 'no-case-insensitive-enum-values-duplicates': rule$a,
3436
- 'no-deprecated': rule$b,
3437
- 'no-hashtag-description': rule$c,
3438
- 'no-operation-name-suffix': rule$d,
3439
- 'no-root-type': rule$e,
3440
- 'no-unreachable-types': rule$f,
3441
- 'no-unused-fields': rule$g,
3442
- 'require-deprecation-date': rule$h,
3443
- 'require-deprecation-reason': rule$i,
3444
- 'require-description': rule$j,
3445
- 'require-field-of-type-query-in-mutation-result': rule$k,
3446
- 'require-id-when-available': rule$l,
3447
- 'selection-set-depth': rule$m,
3448
- 'strict-id-in-types': rule$n,
3449
- 'unique-fragment-name': rule$o,
3450
- 'unique-operation-name': rule$p,
3392
+ 'description-style': rule$1,
3393
+ 'input-name': rule$2,
3394
+ 'match-document-filename': rule$3,
3395
+ 'naming-convention': rule$4,
3396
+ 'no-anonymous-operations': rule$5,
3397
+ 'no-case-insensitive-enum-values-duplicates': rule$6,
3398
+ 'no-deprecated': rule$7,
3399
+ 'no-duplicate-fields': rule$8,
3400
+ 'no-hashtag-description': rule$9,
3401
+ 'no-root-type': rule$a,
3402
+ 'no-scalar-result-type-on-mutation': rule$b,
3403
+ 'no-typename-prefix': rule$c,
3404
+ 'no-unreachable-types': rule$d,
3405
+ 'no-unused-fields': rule$e,
3406
+ 'require-deprecation-date': rule$f,
3407
+ 'require-deprecation-reason': rule$g,
3408
+ 'require-description': rule$h,
3409
+ 'require-field-of-type-query-in-mutation-result': rule$i,
3410
+ 'require-id-when-available': rule$j,
3411
+ 'selection-set-depth': rule$k,
3412
+ 'strict-id-in-types': rule$l,
3413
+ 'unique-fragment-name': rule$m,
3414
+ 'unique-operation-name': rule$n,
3451
3415
  };
3452
3416
 
3453
3417
  const RELEVANT_KEYWORDS = ['gql', 'graphql', '/* GraphQL */'];