@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.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { Kind, validate, isScalarType, TokenKind, isNonNullType, isListType, isObjectType as isObjectType$1, visit, visitWithTypeInfo, GraphQLObjectType, GraphQLInterfaceType, TypeInfo, isInterfaceType, Source, GraphQLError } from 'graphql';
1
+ import { Kind, validate, TokenKind, isScalarType, isNonNullType, isListType, isObjectType as isObjectType$1, visit, visitWithTypeInfo, GraphQLObjectType, GraphQLInterfaceType, TypeInfo, isInterfaceType, Source, GraphQLError } from 'graphql';
2
2
  import { validateSDL } from 'graphql/validation/validate';
3
3
  import { processImport, parseImportLine } from '@graphql-tools/import';
4
4
  import { statSync, existsSync, readFileSync } from 'fs';
@@ -12,14 +12,89 @@ import { CodeFileLoader } from '@graphql-tools/code-file-loader';
12
12
  import { RuleTester, Linter } from 'eslint';
13
13
  import { codeFrameColumns } from '@babel/code-frame';
14
14
 
15
+ const base = {
16
+ parser: '@graphql-eslint/eslint-plugin',
17
+ plugins: ['@graphql-eslint'],
18
+ };
19
+
15
20
  /*
16
21
  * 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
17
22
  */
18
- const recommendedConfig = {
19
- parser: '@graphql-eslint/eslint-plugin',
20
- plugins: ['@graphql-eslint'],
23
+ const schemaRecommendedConfig = {
24
+ extends: ['plugin:@graphql-eslint/base'],
25
+ rules: {
26
+ '@graphql-eslint/description-style': 'error',
27
+ '@graphql-eslint/known-argument-names': 'error',
28
+ '@graphql-eslint/known-directives': 'error',
29
+ '@graphql-eslint/known-type-names': 'error',
30
+ '@graphql-eslint/lone-schema-definition': 'error',
31
+ '@graphql-eslint/naming-convention': [
32
+ 'error',
33
+ {
34
+ types: 'PascalCase',
35
+ fields: 'camelCase',
36
+ EnumValueDefinition: 'UPPER_CASE',
37
+ 'FieldDefinition[parent.name.value=Query]': {
38
+ forbiddenPrefixes: ['query', 'get'],
39
+ forbiddenSuffixes: ['Query'],
40
+ },
41
+ 'FieldDefinition[parent.name.value=Mutation]': {
42
+ forbiddenPrefixes: ['mutation'],
43
+ forbiddenSuffixes: ['Mutation'],
44
+ },
45
+ 'FieldDefinition[parent.name.value=Subscription]': {
46
+ forbiddenPrefixes: ['subscription'],
47
+ forbiddenSuffixes: ['Subscription'],
48
+ },
49
+ },
50
+ ],
51
+ '@graphql-eslint/no-case-insensitive-enum-values-duplicates': 'error',
52
+ '@graphql-eslint/no-hashtag-description': 'error',
53
+ '@graphql-eslint/no-typename-prefix': 'error',
54
+ '@graphql-eslint/no-unreachable-types': 'error',
55
+ '@graphql-eslint/possible-type-extension': 'error',
56
+ '@graphql-eslint/provided-required-arguments': 'error',
57
+ '@graphql-eslint/require-deprecation-reason': 'error',
58
+ '@graphql-eslint/require-description': ['error', { types: true, DirectiveDefinition: true }],
59
+ '@graphql-eslint/strict-id-in-types': 'error',
60
+ '@graphql-eslint/unique-directive-names': 'error',
61
+ '@graphql-eslint/unique-directive-names-per-location': 'error',
62
+ '@graphql-eslint/unique-enum-value-names': 'error',
63
+ '@graphql-eslint/unique-field-definition-names': 'error',
64
+ '@graphql-eslint/unique-operation-types': 'error',
65
+ '@graphql-eslint/unique-type-names': 'error',
66
+ },
67
+ };
68
+
69
+ /*
70
+ * 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
71
+ */
72
+ const schemaAllConfig = {
73
+ extends: ['plugin:@graphql-eslint/base', 'plugin:@graphql-eslint/schema-recommended'],
74
+ rules: {
75
+ '@graphql-eslint/alphabetize': [
76
+ 'error',
77
+ {
78
+ fields: ['ObjectTypeDefinition', 'InterfaceTypeDefinition', 'InputObjectTypeDefinition'],
79
+ values: ['EnumTypeDefinition'],
80
+ arguments: ['FieldDefinition', 'Field', 'DirectiveDefinition', 'Directive'],
81
+ },
82
+ ],
83
+ '@graphql-eslint/input-name': 'error',
84
+ '@graphql-eslint/no-root-type': 'off',
85
+ '@graphql-eslint/no-scalar-result-type-on-mutation': 'error',
86
+ '@graphql-eslint/no-unused-fields': 'off',
87
+ '@graphql-eslint/require-deprecation-date': 'error',
88
+ '@graphql-eslint/require-field-of-type-query-in-mutation-result': 'error',
89
+ },
90
+ };
91
+
92
+ /*
93
+ * 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
94
+ */
95
+ const operationsRecommendedConfig = {
96
+ extends: ['plugin:@graphql-eslint/base'],
21
97
  rules: {
22
- '@graphql-eslint/avoid-typename-prefix': 'error',
23
98
  '@graphql-eslint/executable-definitions': 'error',
24
99
  '@graphql-eslint/fields-on-correct-type': 'error',
25
100
  '@graphql-eslint/fragments-on-composite-type': 'error',
@@ -28,58 +103,36 @@ const recommendedConfig = {
28
103
  '@graphql-eslint/known-fragment-names': 'error',
29
104
  '@graphql-eslint/known-type-names': 'error',
30
105
  '@graphql-eslint/lone-anonymous-operation': 'error',
31
- '@graphql-eslint/lone-schema-definition': 'error',
32
106
  '@graphql-eslint/naming-convention': [
33
107
  'error',
34
108
  {
35
- types: 'PascalCase',
36
- fields: 'camelCase',
37
- overrides: {
38
- EnumValueDefinition: 'UPPER_CASE',
39
- OperationDefinition: {
40
- style: 'PascalCase',
41
- forbiddenPrefixes: ['Query', 'Mutation', 'Subscription', 'Get'],
42
- forbiddenSuffixes: ['Query', 'Mutation', 'Subscription'],
43
- },
44
- FragmentDefinition: { style: 'PascalCase', forbiddenPrefixes: ['Fragment'], forbiddenSuffixes: ['Fragment'] },
45
- 'FieldDefinition[parent.name.value=Query]': {
46
- forbiddenPrefixes: ['query', 'get'],
47
- forbiddenSuffixes: ['Query'],
48
- },
49
- 'FieldDefinition[parent.name.value=Mutation]': {
50
- forbiddenPrefixes: ['mutation'],
51
- forbiddenSuffixes: ['Mutation'],
52
- },
53
- 'FieldDefinition[parent.name.value=Subscription]': {
54
- forbiddenPrefixes: ['subscription'],
55
- forbiddenSuffixes: ['Subscription'],
56
- },
57
- },
109
+ Argument: 'camelCase',
110
+ VariableDefinition: 'camelCase',
111
+ OperationDefinition: {
112
+ style: 'PascalCase',
113
+ forbiddenPrefixes: ['Query', 'Mutation', 'Subscription', 'Get'],
114
+ forbiddenSuffixes: ['Query', 'Mutation', 'Subscription'],
115
+ },
116
+ FragmentDefinition: { style: 'PascalCase', forbiddenPrefixes: ['Fragment'], forbiddenSuffixes: ['Fragment'] },
58
117
  },
59
118
  ],
60
119
  '@graphql-eslint/no-anonymous-operations': 'error',
61
- '@graphql-eslint/no-case-insensitive-enum-values-duplicates': 'error',
120
+ '@graphql-eslint/no-deprecated': 'error',
121
+ '@graphql-eslint/no-duplicate-fields': 'error',
62
122
  '@graphql-eslint/no-fragment-cycles': 'error',
63
- '@graphql-eslint/no-operation-name-suffix': 'error',
64
123
  '@graphql-eslint/no-undefined-variables': 'error',
65
124
  '@graphql-eslint/no-unused-fragments': 'error',
66
125
  '@graphql-eslint/no-unused-variables': 'error',
67
126
  '@graphql-eslint/one-field-subscriptions': 'error',
68
127
  '@graphql-eslint/overlapping-fields-can-be-merged': 'error',
69
128
  '@graphql-eslint/possible-fragment-spread': 'error',
70
- '@graphql-eslint/possible-type-extension': 'error',
71
129
  '@graphql-eslint/provided-required-arguments': 'error',
72
- '@graphql-eslint/require-deprecation-reason': 'error',
130
+ '@graphql-eslint/require-id-when-available': 'error',
73
131
  '@graphql-eslint/scalar-leafs': 'error',
74
- '@graphql-eslint/strict-id-in-types': 'error',
132
+ '@graphql-eslint/selection-set-depth': ['error', { maxDepth: 7 }],
75
133
  '@graphql-eslint/unique-argument-names': 'error',
76
- '@graphql-eslint/unique-directive-names': 'error',
77
134
  '@graphql-eslint/unique-directive-names-per-location': 'error',
78
- '@graphql-eslint/unique-enum-value-names': 'error',
79
- '@graphql-eslint/unique-field-definition-names': 'error',
80
135
  '@graphql-eslint/unique-input-field-names': 'error',
81
- '@graphql-eslint/unique-operation-types': 'error',
82
- '@graphql-eslint/unique-type-names': 'error',
83
136
  '@graphql-eslint/unique-variable-names': 'error',
84
137
  '@graphql-eslint/value-literals-of-correct-type': 'error',
85
138
  '@graphql-eslint/variables-are-input-types': 'error',
@@ -90,44 +143,32 @@ const recommendedConfig = {
90
143
  /*
91
144
  * 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
92
145
  */
93
- const allConfig = {
94
- ...recommendedConfig,
146
+ const operationsAllConfig = {
147
+ extends: ['plugin:@graphql-eslint/base', 'plugin:@graphql-eslint/operations-recommended'],
95
148
  rules: {
96
- ...recommendedConfig.rules,
97
149
  '@graphql-eslint/alphabetize': [
98
150
  'error',
99
151
  {
100
- fields: ['ObjectTypeDefinition', 'InterfaceTypeDefinition', 'InputObjectTypeDefinition'],
101
- values: ['EnumTypeDefinition'],
102
152
  selections: ['OperationDefinition', 'FragmentDefinition'],
103
153
  variables: ['OperationDefinition'],
104
- arguments: ['FieldDefinition', 'Field', 'DirectiveDefinition', 'Directive'],
154
+ arguments: ['Field', 'Directive'],
105
155
  },
106
156
  ],
107
- '@graphql-eslint/avoid-duplicate-fields': 'error',
108
- '@graphql-eslint/avoid-operation-name-prefix': 'error',
109
- '@graphql-eslint/avoid-scalar-result-type-on-mutation': 'error',
110
- '@graphql-eslint/description-style': 'error',
111
- '@graphql-eslint/input-name': 'error',
112
- '@graphql-eslint/match-document-filename': 'error',
113
- '@graphql-eslint/no-deprecated': 'error',
114
- '@graphql-eslint/no-hashtag-description': 'error',
115
- '@graphql-eslint/no-root-type': ['error', { disallow: ['subscription'] }],
116
- '@graphql-eslint/no-unreachable-types': 'error',
117
- '@graphql-eslint/no-unused-fields': 'error',
118
- '@graphql-eslint/require-deprecation-date': 'error',
119
- '@graphql-eslint/require-description': ['error', { types: true, overrides: { DirectiveDefinition: true } }],
120
- '@graphql-eslint/require-field-of-type-query-in-mutation-result': 'error',
121
- '@graphql-eslint/require-id-when-available': 'error',
122
- '@graphql-eslint/selection-set-depth': 'error',
157
+ '@graphql-eslint/match-document-filename': [
158
+ 'error',
159
+ { query: 'kebab-case', mutation: 'kebab-case', subscription: 'kebab-case', fragment: 'kebab-case' },
160
+ ],
123
161
  '@graphql-eslint/unique-fragment-name': 'error',
124
162
  '@graphql-eslint/unique-operation-name': 'error',
125
163
  },
126
164
  };
127
165
 
128
166
  const configs = {
129
- all: allConfig,
130
- recommended: recommendedConfig,
167
+ base,
168
+ 'schema-recommended': schemaRecommendedConfig,
169
+ 'schema-all': schemaAllConfig,
170
+ 'operations-recommended': operationsRecommendedConfig,
171
+ 'operations-all': operationsAllConfig,
131
172
  };
132
173
 
133
174
  function requireSiblingsOperations(ruleName, context) {
@@ -340,7 +381,6 @@ const validationToRule = (name, ruleName, docs, getDocumentNode) => {
340
381
  docs: {
341
382
  ...docs,
342
383
  graphQLJSRuleName: ruleName,
343
- category: 'Validation',
344
384
  recommended: true,
345
385
  requiresSchema,
346
386
  url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${name}.md`,
@@ -377,16 +417,22 @@ const importFiles = (context) => {
377
417
  return processImport(context.getFilename());
378
418
  };
379
419
  const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-definitions', 'ExecutableDefinitions', {
420
+ category: 'Operations',
380
421
  description: `A GraphQL document is only valid for execution if all definitions are either operation or fragment definitions.`,
381
422
  }), validationToRule('fields-on-correct-type', 'FieldsOnCorrectType', {
423
+ category: 'Operations',
382
424
  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`.',
383
425
  }), validationToRule('fragments-on-composite-type', 'FragmentsOnCompositeTypes', {
426
+ category: 'Operations',
384
427
  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.`,
385
428
  }), validationToRule('known-argument-names', 'KnownArgumentNames', {
429
+ category: ['Schema', 'Operations'],
386
430
  description: `A GraphQL field is only valid if all supplied arguments are defined by that field.`,
387
431
  }), validationToRule('known-directives', 'KnownDirectives', {
432
+ category: ['Schema', 'Operations'],
388
433
  description: `A GraphQL document is only valid if all \`@directives\` are known by the schema and legally positioned.`,
389
434
  }), validationToRule('known-fragment-names', 'KnownFragmentNames', {
435
+ category: 'Operations',
390
436
  description: `A GraphQL document is only valid if all \`...Fragment\` fragment spreads refer to fragments defined in the same document.`,
391
437
  examples: [
392
438
  {
@@ -453,17 +499,23 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
453
499
  },
454
500
  ],
455
501
  }, importFiles), validationToRule('known-type-names', 'KnownTypeNames', {
502
+ category: ['Schema', 'Operations'],
456
503
  description: `A GraphQL document is only valid if referenced types (specifically variable definitions and fragment conditions) are defined by the type schema.`,
457
504
  }), validationToRule('lone-anonymous-operation', 'LoneAnonymousOperation', {
505
+ category: 'Operations',
458
506
  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.`,
459
507
  }), validationToRule('lone-schema-definition', 'LoneSchemaDefinition', {
508
+ category: 'Schema',
460
509
  description: `A GraphQL document is only valid if it contains only one schema definition.`,
461
510
  requiresSchema: false,
462
511
  }), validationToRule('no-fragment-cycles', 'NoFragmentCycles', {
512
+ category: 'Operations',
463
513
  description: `A GraphQL fragment is only valid when it does not have cycles in fragments usage.`,
464
514
  }), validationToRule('no-undefined-variables', 'NoUndefinedVariables', {
515
+ category: 'Operations',
465
516
  description: `A GraphQL operation is only valid if all variables encountered, both directly and via fragment spreads, are defined by that operation.`,
466
517
  }, importFiles), validationToRule('no-unused-fragments', 'NoUnusedFragments', {
518
+ category: 'Operations',
467
519
  description: `A GraphQL document is only valid if all fragment definitions are spread within operations, or spread within other fragments spread within operations.`,
468
520
  requiresSiblings: true,
469
521
  }, context => {
@@ -493,49 +545,68 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
493
545
  };
494
546
  return getParentNode(context.getFilename());
495
547
  }), validationToRule('no-unused-variables', 'NoUnusedVariables', {
548
+ category: 'Operations',
496
549
  description: `A GraphQL operation is only valid if all variables defined by an operation are used, either directly or within a spread fragment.`,
497
550
  }, importFiles), validationToRule('overlapping-fields-can-be-merged', 'OverlappingFieldsCanBeMerged', {
551
+ category: 'Operations',
498
552
  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.`,
499
553
  }), validationToRule('possible-fragment-spread', 'PossibleFragmentSpreads', {
554
+ category: 'Operations',
500
555
  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.`,
501
556
  }), validationToRule('possible-type-extension', 'PossibleTypeExtensions', {
557
+ category: 'Schema',
502
558
  description: `A type extension is only valid if the type is defined and has the same kind.`,
503
559
  requiresSchema: false,
504
560
  }), validationToRule('provided-required-arguments', 'ProvidedRequiredArguments', {
561
+ category: ['Schema', 'Operations'],
505
562
  description: `A field or directive is only valid if all required (non-null without a default value) field arguments have been provided.`,
506
563
  }), validationToRule('scalar-leafs', 'ScalarLeafs', {
564
+ category: 'Operations',
507
565
  description: `A GraphQL document is valid only if all leaf fields (fields without sub selections) are of scalar or enum types.`,
508
566
  }), validationToRule('one-field-subscriptions', 'SingleFieldSubscriptions', {
567
+ category: 'Operations',
509
568
  description: `A GraphQL subscription is valid only if it contains a single root field.`,
510
569
  }), validationToRule('unique-argument-names', 'UniqueArgumentNames', {
570
+ category: 'Operations',
511
571
  description: `A GraphQL field or directive is only valid if all supplied arguments are uniquely named.`,
512
572
  }), validationToRule('unique-directive-names', 'UniqueDirectiveNames', {
573
+ category: 'Schema',
513
574
  description: `A GraphQL document is only valid if all defined directives have unique names.`,
514
575
  requiresSchema: false,
515
576
  }), validationToRule('unique-directive-names-per-location', 'UniqueDirectivesPerLocation', {
577
+ category: ['Schema', 'Operations'],
516
578
  description: `A GraphQL document is only valid if all non-repeatable directives at a given location are uniquely named.`,
517
579
  }), validationToRule('unique-enum-value-names', 'UniqueEnumValueNames', {
580
+ category: 'Schema',
518
581
  description: `A GraphQL enum type is only valid if all its values are uniquely named.`,
519
582
  requiresSchema: false,
520
583
  }), validationToRule('unique-field-definition-names', 'UniqueFieldDefinitionNames', {
584
+ category: 'Schema',
521
585
  description: `A GraphQL complex type is only valid if all its fields are uniquely named.`,
522
586
  requiresSchema: false,
523
587
  }), validationToRule('unique-input-field-names', 'UniqueInputFieldNames', {
588
+ category: 'Operations',
524
589
  description: `A GraphQL input object value is only valid if all supplied fields are uniquely named.`,
525
590
  requiresSchema: false,
526
591
  }), validationToRule('unique-operation-types', 'UniqueOperationTypes', {
592
+ category: 'Schema',
527
593
  description: `A GraphQL document is only valid if it has only one type per operation.`,
528
594
  requiresSchema: false,
529
595
  }), validationToRule('unique-type-names', 'UniqueTypeNames', {
596
+ category: 'Schema',
530
597
  description: `A GraphQL document is only valid if all defined types have unique names.`,
531
598
  requiresSchema: false,
532
599
  }), validationToRule('unique-variable-names', 'UniqueVariableNames', {
600
+ category: 'Operations',
533
601
  description: `A GraphQL operation is only valid if all its variables are uniquely named.`,
534
602
  }), validationToRule('value-literals-of-correct-type', 'ValuesOfCorrectType', {
603
+ category: 'Operations',
535
604
  description: `A GraphQL document is only valid if all value literals are of the type expected at their position.`,
536
605
  }), validationToRule('variables-are-input-types', 'VariablesAreInputTypes', {
606
+ category: 'Operations',
537
607
  description: `A GraphQL operation is only valid if all the variables it defines are of input types (scalar, enum, or input object).`,
538
608
  }), validationToRule('variables-in-allowed-position', 'VariablesInAllowedPosition', {
609
+ category: 'Operations',
539
610
  description: `Variables passed to field arguments conform to type.`,
540
611
  }));
541
612
 
@@ -561,7 +632,7 @@ const rule = {
561
632
  meta: {
562
633
  type: 'suggestion',
563
634
  docs: {
564
- category: 'Best Practices',
635
+ category: ['Schema', 'Operations'],
565
636
  description: 'Enforce arrange in alphabetical order for type fields, enum values, input object fields, operation selections and more.',
566
637
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/alphabetize.md',
567
638
  examples: [
@@ -640,15 +711,22 @@ const rule = {
640
711
  `,
641
712
  },
642
713
  ],
643
- optionsForConfig: [
644
- {
645
- fields: fieldsEnum,
646
- values: valuesEnum,
647
- selections: selectionsEnum,
648
- variables: variablesEnum,
649
- arguments: argumentsEnum,
650
- },
651
- ],
714
+ configOptions: {
715
+ schema: [
716
+ {
717
+ fields: fieldsEnum,
718
+ values: valuesEnum,
719
+ arguments: argumentsEnum,
720
+ },
721
+ ],
722
+ operations: [
723
+ {
724
+ selections: selectionsEnum,
725
+ variables: variablesEnum,
726
+ arguments: [Kind.FIELD, Kind.DIRECTIVE],
727
+ },
728
+ ],
729
+ },
652
730
  },
653
731
  messages: {
654
732
  [ALPHABETIZE]: '"{{ currName }}" should be before "{{ prevName }}"',
@@ -786,430 +864,128 @@ const rule = {
786
864
  },
787
865
  };
788
866
 
789
- const AVOID_DUPLICATE_FIELDS = 'AVOID_DUPLICATE_FIELDS';
790
867
  const rule$1 = {
791
868
  meta: {
792
869
  type: 'suggestion',
793
870
  docs: {
794
- description: `Checks for duplicate fields in selection set, variables in operation definition, or in arguments set of a field.`,
795
- category: 'Stylistic Issues',
796
- url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-duplicate-fields.md',
797
871
  examples: [
798
872
  {
799
873
  title: 'Incorrect',
874
+ usage: [{ style: 'inline' }],
800
875
  code: /* GraphQL */ `
801
- query {
802
- user {
803
- name
804
- email
805
- name # duplicate field
806
- }
807
- }
808
- `,
809
- },
810
- {
811
- title: 'Incorrect',
812
- code: /* GraphQL */ `
813
- query {
814
- users(
815
- first: 100
816
- skip: 50
817
- after: "cji629tngfgou0b73kt7vi5jo"
818
- first: 100 # duplicate argument
819
- ) {
820
- id
821
- }
876
+ """ Description """
877
+ type someTypeName {
878
+ # ...
822
879
  }
823
880
  `,
824
881
  },
825
882
  {
826
- title: 'Incorrect',
883
+ title: 'Correct',
884
+ usage: [{ style: 'inline' }],
827
885
  code: /* GraphQL */ `
828
- query (
829
- $first: Int!
830
- $first: Int! # duplicate variable
831
- ) {
832
- users(first: $first, skip: 50) {
833
- id
834
- }
886
+ " Description "
887
+ type someTypeName {
888
+ # ...
835
889
  }
836
890
  `,
837
891
  },
838
892
  ],
893
+ description: 'Require all comments to follow the same style (either block or inline).',
894
+ category: 'Schema',
895
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/description-style.md',
896
+ recommended: true,
839
897
  },
840
- messages: {
841
- [AVOID_DUPLICATE_FIELDS]: `{{ type }} "{{ fieldName }}" defined multiple times`,
842
- },
843
- schema: [],
898
+ schema: [
899
+ {
900
+ type: 'object',
901
+ additionalProperties: false,
902
+ properties: {
903
+ style: {
904
+ enum: ['block', 'inline'],
905
+ default: 'block',
906
+ },
907
+ },
908
+ },
909
+ ],
844
910
  },
845
911
  create(context) {
846
- function checkNode(usedFields, fieldName, type, node) {
847
- if (usedFields.has(fieldName)) {
912
+ const { style = 'block' } = context.options[0] || {};
913
+ const isBlock = style === 'block';
914
+ return {
915
+ [`.description[type=StringValue][block!=${isBlock}]`](node) {
848
916
  context.report({
849
- loc: getLocation((node.kind === Kind.FIELD && node.alias ? node.alias : node).loc, fieldName, {
850
- offsetEnd: node.kind === Kind.VARIABLE_DEFINITION ? 0 : 1,
851
- }),
852
- messageId: AVOID_DUPLICATE_FIELDS,
853
- data: {
854
- type,
855
- fieldName,
856
- },
917
+ loc: getLocation(node.loc),
918
+ message: `Unexpected ${isBlock ? 'inline' : 'block'} description`,
857
919
  });
858
- }
859
- else {
860
- usedFields.add(fieldName);
861
- }
862
- }
863
- return {
864
- OperationDefinition(node) {
865
- const set = new Set();
866
- for (const varDef of node.variableDefinitions) {
867
- checkNode(set, varDef.variable.name.value, 'Operation variable', varDef);
868
- }
869
- },
870
- Field(node) {
871
- const set = new Set();
872
- for (const arg of node.arguments) {
873
- checkNode(set, arg.name.value, 'Field argument', arg);
874
- }
875
- },
876
- SelectionSet(node) {
877
- var _a;
878
- const set = new Set();
879
- for (const selection of node.selections) {
880
- if (selection.kind === Kind.FIELD) {
881
- checkNode(set, ((_a = selection.alias) === null || _a === void 0 ? void 0 : _a.value) || selection.name.value, 'Field', selection);
882
- }
883
- }
884
920
  },
885
921
  };
886
922
  },
887
923
  };
888
924
 
889
- const AVOID_OPERATION_NAME_PREFIX = 'AVOID_OPERATION_NAME_PREFIX';
925
+ const isObjectType = (node) => [Kind.OBJECT_TYPE_DEFINITION, Kind.OBJECT_TYPE_EXTENSION].includes(node.type);
926
+ const isQueryType = (node) => isObjectType(node) && node.name.value === 'Query';
927
+ const isMutationType = (node) => isObjectType(node) && node.name.value === 'Mutation';
890
928
  const rule$2 = {
891
929
  meta: {
892
930
  type: 'suggestion',
893
931
  docs: {
894
- 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.',
895
- category: 'Stylistic Issues',
896
- url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-operation-name-prefix.md',
932
+ 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.',
933
+ category: 'Schema',
934
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/input-name.md',
897
935
  examples: [
898
936
  {
899
937
  title: 'Incorrect',
900
- usage: [{ keywords: ['get'] }],
938
+ usage: [{ checkInputType: true }],
901
939
  code: /* GraphQL */ `
902
- query getUserDetails {
903
- # ...
904
- }`,
940
+ type Mutation {
941
+ SetMessage(message: InputMessage): String
942
+ }
943
+ `,
905
944
  },
906
945
  {
907
- title: 'Correct',
908
- usage: [{ keywords: ['get'] }],
946
+ title: 'Correct (with checkInputType)',
947
+ usage: [{ checkInputType: true }],
909
948
  code: /* GraphQL */ `
910
- query userDetails {
911
- # ...
912
- }`,
949
+ type Mutation {
950
+ SetMessage(input: SetMessageInput): String
951
+ }
952
+ `,
953
+ },
954
+ {
955
+ title: 'Correct (without checkInputType)',
956
+ usage: [{ checkInputType: false }],
957
+ code: /* GraphQL */ `
958
+ type Mutation {
959
+ SetMessage(input: AnyInputTypeName): String
960
+ }
961
+ `,
913
962
  },
914
963
  ],
915
964
  },
916
- messages: {
917
- [AVOID_OPERATION_NAME_PREFIX]: `Forbidden operation name prefix: "{{ invalidPrefix }}"`,
918
- },
919
965
  schema: [
920
966
  {
921
- additionalProperties: false,
922
967
  type: 'object',
923
- required: ['keywords'],
968
+ additionalProperties: false,
924
969
  properties: {
925
- caseSensitive: {
970
+ checkInputType: {
971
+ type: 'boolean',
926
972
  default: false,
973
+ description: 'Check that the input type name follows the convention <mutationName>Input',
974
+ },
975
+ caseSensitiveInputType: {
927
976
  type: 'boolean',
977
+ default: true,
978
+ description: 'Allow for case discrepancies in the input type name',
928
979
  },
929
- keywords: {
930
- additionalItems: false,
931
- type: 'array',
932
- minItems: 1,
933
- items: {
934
- type: 'string',
935
- },
936
- },
937
- },
938
- },
939
- ],
940
- },
941
- create(context) {
942
- return {
943
- 'OperationDefinition, FragmentDefinition'(node) {
944
- const config = context.options[0] || { keywords: [], caseSensitive: false };
945
- const caseSensitive = config.caseSensitive;
946
- const keywords = config.keywords || [];
947
- if (node && node.name && node.name.value !== '') {
948
- for (const keyword of keywords) {
949
- const testKeyword = caseSensitive ? keyword : keyword.toLowerCase();
950
- const testName = caseSensitive ? node.name.value : node.name.value.toLowerCase();
951
- if (testName.startsWith(testKeyword)) {
952
- const { start } = node.name.loc;
953
- context.report({
954
- loc: {
955
- start: {
956
- line: start.line,
957
- column: start.column - 1,
958
- },
959
- end: {
960
- line: start.line,
961
- column: start.column - 1 + testKeyword.length,
962
- },
963
- },
964
- data: {
965
- invalidPrefix: keyword,
966
- },
967
- messageId: AVOID_OPERATION_NAME_PREFIX,
968
- });
969
- }
970
- }
971
- }
972
- },
973
- };
974
- },
975
- };
976
-
977
- const rule$3 = {
978
- meta: {
979
- type: 'suggestion',
980
- docs: {
981
- category: 'Best Practices',
982
- description: 'Avoid scalar result type on mutation type to make sure to return a valid state.',
983
- url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-scalar-result-type-on-mutation.md',
984
- requiresSchema: true,
985
- examples: [
986
- {
987
- title: 'Incorrect',
988
- code: /* GraphQL */ `
989
- type Mutation {
990
- createUser: Boolean
991
- }
992
- `,
993
- },
994
- {
995
- title: 'Correct',
996
- code: /* GraphQL */ `
997
- type Mutation {
998
- createUser: User!
999
- }
1000
- `,
1001
- },
1002
- ],
1003
- },
1004
- schema: [],
1005
- },
1006
- create(context) {
1007
- const schema = requireGraphQLSchemaFromContext('avoid-scalar-result-type-on-mutation', context);
1008
- const mutationType = schema.getMutationType();
1009
- if (!mutationType) {
1010
- return {};
1011
- }
1012
- const selector = [
1013
- `:matches(${Kind.OBJECT_TYPE_DEFINITION}, ${Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}]`,
1014
- '>',
1015
- Kind.FIELD_DEFINITION,
1016
- Kind.NAMED_TYPE,
1017
- ].join(' ');
1018
- return {
1019
- [selector](node) {
1020
- const typeName = node.name.value;
1021
- const graphQLType = schema.getType(typeName);
1022
- if (isScalarType(graphQLType)) {
1023
- context.report({
1024
- loc: getLocation(node.loc, typeName),
1025
- message: `Unexpected scalar result type "${typeName}"`,
1026
- });
1027
- }
1028
- },
1029
- };
1030
- },
1031
- };
1032
-
1033
- const AVOID_TYPENAME_PREFIX = 'AVOID_TYPENAME_PREFIX';
1034
- const rule$4 = {
1035
- meta: {
1036
- type: 'suggestion',
1037
- docs: {
1038
- category: 'Best Practices',
1039
- description: 'Enforces users to avoid using the type name in a field name while defining your schema.',
1040
- recommended: true,
1041
- url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-typename-prefix.md',
1042
- examples: [
1043
- {
1044
- title: 'Incorrect',
1045
- code: /* GraphQL */ `
1046
- type User {
1047
- userId: ID!
1048
- }
1049
- `,
1050
- },
1051
- {
1052
- title: 'Correct',
1053
- code: /* GraphQL */ `
1054
- type User {
1055
- id: ID!
1056
- }
1057
- `,
1058
- },
1059
- ],
1060
- },
1061
- messages: {
1062
- [AVOID_TYPENAME_PREFIX]: `Field "{{ fieldName }}" starts with the name of the parent type "{{ typeName }}"`,
1063
- },
1064
- schema: [],
1065
- },
1066
- create(context) {
1067
- return {
1068
- 'ObjectTypeDefinition, ObjectTypeExtension, InterfaceTypeDefinition, InterfaceTypeExtension'(node) {
1069
- const typeName = node.name.value;
1070
- const lowerTypeName = typeName.toLowerCase();
1071
- for (const field of node.fields) {
1072
- const fieldName = field.name.value;
1073
- if (fieldName.toLowerCase().startsWith(lowerTypeName)) {
1074
- context.report({
1075
- data: {
1076
- fieldName,
1077
- typeName,
1078
- },
1079
- messageId: AVOID_TYPENAME_PREFIX,
1080
- loc: getLocation(field.loc, lowerTypeName),
1081
- });
1082
- }
1083
- }
1084
- },
1085
- };
1086
- },
1087
- };
1088
-
1089
- const rule$5 = {
1090
- meta: {
1091
- type: 'suggestion',
1092
- docs: {
1093
- examples: [
1094
- {
1095
- title: 'Incorrect',
1096
- usage: [{ style: 'inline' }],
1097
- code: /* GraphQL */ `
1098
- """ Description """
1099
- type someTypeName {
1100
- # ...
1101
- }
1102
- `,
1103
- },
1104
- {
1105
- title: 'Correct',
1106
- usage: [{ style: 'inline' }],
1107
- code: /* GraphQL */ `
1108
- " Description "
1109
- type someTypeName {
1110
- # ...
1111
- }
1112
- `,
1113
- },
1114
- ],
1115
- description: 'Require all comments to follow the same style (either block or inline).',
1116
- category: 'Stylistic Issues',
1117
- url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/description-style.md',
1118
- },
1119
- schema: [
1120
- {
1121
- type: 'object',
1122
- properties: {
1123
- style: {
1124
- type: 'string',
1125
- enum: ['block', 'inline'],
1126
- default: 'inline',
1127
- },
1128
- },
1129
- additionalProperties: false,
1130
- },
1131
- ],
1132
- },
1133
- create(context) {
1134
- const { style } = context.options[0] || { style: 'inline' };
1135
- const wrongDescriptionType = style === 'block' ? 'inline' : 'block';
1136
- return {
1137
- '[description.type="StringValue"]': node => {
1138
- if (node.description.block !== (style === 'block')) {
1139
- context.report({
1140
- loc: getLocation(node.description.loc),
1141
- message: `Unexpected ${wrongDescriptionType} description`,
1142
- });
1143
- }
1144
- },
1145
- };
1146
- },
1147
- };
1148
-
1149
- const isObjectType = (node) => [Kind.OBJECT_TYPE_DEFINITION, Kind.OBJECT_TYPE_EXTENSION].includes(node.type);
1150
- const isQueryType = (node) => isObjectType(node) && node.name.value === 'Query';
1151
- const isMutationType = (node) => isObjectType(node) && node.name.value === 'Mutation';
1152
- const rule$6 = {
1153
- meta: {
1154
- type: 'suggestion',
1155
- docs: {
1156
- 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.',
1157
- category: 'Stylistic Issues',
1158
- url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/input-name.md',
1159
- examples: [
1160
- {
1161
- title: 'Incorrect',
1162
- usage: [{ checkInputType: true }],
1163
- code: /* GraphQL */ `
1164
- type Mutation {
1165
- SetMessage(message: InputMessage): String
1166
- }
1167
- `,
1168
- },
1169
- {
1170
- title: 'Correct (with checkInputType)',
1171
- usage: [{ checkInputType: true }],
1172
- code: /* GraphQL */ `
1173
- type Mutation {
1174
- SetMessage(input: SetMessageInput): String
1175
- }
1176
- `,
1177
- },
1178
- {
1179
- title: 'Correct (without checkInputType)',
1180
- usage: [{ checkInputType: false }],
1181
- code: /* GraphQL */ `
1182
- type Mutation {
1183
- SetMessage(input: AnyInputTypeName): String
1184
- }
1185
- `,
1186
- },
1187
- ],
1188
- },
1189
- schema: [
1190
- {
1191
- type: 'object',
1192
- additionalProperties: false,
1193
- properties: {
1194
- checkInputType: {
1195
- type: 'boolean',
1196
- default: false,
1197
- description: 'Check that the input type name follows the convention <mutationName>Input',
1198
- },
1199
- caseSensitiveInputType: {
1200
- type: 'boolean',
1201
- default: true,
1202
- description: 'Allow for case discrepancies in the input type name',
1203
- },
1204
- checkQueries: {
1205
- type: 'boolean',
1206
- default: false,
1207
- description: 'Apply the rule to Queries',
1208
- },
1209
- checkMutations: {
1210
- type: 'boolean',
1211
- default: true,
1212
- description: 'Apply the rule to Mutations',
980
+ checkQueries: {
981
+ type: 'boolean',
982
+ default: false,
983
+ description: 'Apply the rule to Queries',
984
+ },
985
+ checkMutations: {
986
+ type: 'boolean',
987
+ default: true,
988
+ description: 'Apply the rule to Mutations',
1213
989
  },
1214
990
  },
1215
991
  },
@@ -1275,11 +1051,11 @@ const CASE_STYLES = [
1275
1051
  const schemaOption = {
1276
1052
  oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
1277
1053
  };
1278
- const rule$7 = {
1054
+ const rule$3 = {
1279
1055
  meta: {
1280
1056
  type: 'suggestion',
1281
1057
  docs: {
1282
- category: 'Best Practices',
1058
+ category: 'Operations',
1283
1059
  description: 'This rule allows you to enforce that the file name should match the operation name.',
1284
1060
  url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/match-document-filename.md`,
1285
1061
  examples: [
@@ -1353,6 +1129,14 @@ const rule$7 = {
1353
1129
  `,
1354
1130
  },
1355
1131
  ],
1132
+ configOptions: [
1133
+ {
1134
+ query: CaseStyle.kebabCase,
1135
+ mutation: CaseStyle.kebabCase,
1136
+ subscription: CaseStyle.kebabCase,
1137
+ fragment: CaseStyle.kebabCase,
1138
+ },
1139
+ ],
1356
1140
  },
1357
1141
  messages: {
1358
1142
  [MATCH_EXTENSION]: `File extension "{{ fileExtension }}" don't match extension "{{ expectedFileExtension }}"`,
@@ -1361,27 +1145,29 @@ const rule$7 = {
1361
1145
  schema: {
1362
1146
  definitions: {
1363
1147
  asString: {
1364
- type: 'string',
1365
- description: `One of: ${CASE_STYLES.map(t => `\`${t}\``).join(', ')}`,
1366
1148
  enum: CASE_STYLES,
1149
+ description: `One of: ${CASE_STYLES.map(t => `\`${t}\``).join(', ')}`,
1367
1150
  },
1368
1151
  asObject: {
1369
1152
  type: 'object',
1153
+ additionalProperties: false,
1370
1154
  properties: {
1371
1155
  style: {
1372
- type: 'string',
1373
1156
  enum: CASE_STYLES,
1374
1157
  },
1158
+ suffix: {
1159
+ type: 'string',
1160
+ },
1375
1161
  },
1376
1162
  },
1377
1163
  },
1378
- $schema: 'http://json-schema.org/draft-04/schema#',
1379
1164
  type: 'array',
1165
+ maxItems: 1,
1380
1166
  items: {
1381
1167
  type: 'object',
1168
+ additionalProperties: false,
1382
1169
  properties: {
1383
1170
  fileExtension: {
1384
- type: 'string',
1385
1171
  enum: ACCEPTED_EXTENSIONS,
1386
1172
  },
1387
1173
  query: schemaOption,
@@ -1456,13 +1242,7 @@ const rule$7 = {
1456
1242
  },
1457
1243
  };
1458
1244
 
1459
- const FIELDS_KINDS = [
1460
- Kind.FIELD_DEFINITION,
1461
- Kind.INPUT_VALUE_DEFINITION,
1462
- Kind.VARIABLE_DEFINITION,
1463
- Kind.ARGUMENT,
1464
- Kind.DIRECTIVE_DEFINITION,
1465
- ];
1245
+ const FIELDS_KINDS = [Kind.FIELD_DEFINITION, Kind.INPUT_VALUE_DEFINITION, Kind.ARGUMENT, Kind.DIRECTIVE_DEFINITION];
1466
1246
  const KindToDisplayName = {
1467
1247
  // types
1468
1248
  [Kind.OBJECT_TYPE_DEFINITION]: 'Type',
@@ -1474,13 +1254,13 @@ const KindToDisplayName = {
1474
1254
  // fields
1475
1255
  [Kind.FIELD_DEFINITION]: 'Field',
1476
1256
  [Kind.INPUT_VALUE_DEFINITION]: 'Input property',
1477
- [Kind.VARIABLE_DEFINITION]: 'Variable',
1478
1257
  [Kind.ARGUMENT]: 'Argument',
1479
1258
  [Kind.DIRECTIVE_DEFINITION]: 'Directive',
1480
1259
  // rest
1481
1260
  [Kind.ENUM_VALUE_DEFINITION]: 'Enumeration value',
1482
1261
  [Kind.OPERATION_DEFINITION]: 'Operation',
1483
1262
  [Kind.FRAGMENT_DEFINITION]: 'Fragment',
1263
+ [Kind.VARIABLE_DEFINITION]: 'Variable',
1484
1264
  };
1485
1265
  const StyleToRegex = {
1486
1266
  camelCase: /^[a-z][\dA-Za-z]*$/,
@@ -1493,12 +1273,12 @@ const ALLOWED_STYLES = Object.keys(StyleToRegex);
1493
1273
  const schemaOption$1 = {
1494
1274
  oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
1495
1275
  };
1496
- const rule$8 = {
1276
+ const rule$4 = {
1497
1277
  meta: {
1498
1278
  type: 'suggestion',
1499
1279
  docs: {
1500
1280
  description: 'Require names to follow specified conventions.',
1501
- category: 'Best Practices',
1281
+ category: ['Schema', 'Operations'],
1502
1282
  recommended: true,
1503
1283
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/naming-convention.md',
1504
1284
  examples: [
@@ -1509,6 +1289,24 @@ const rule$8 = {
1509
1289
  type user {
1510
1290
  first_name: String!
1511
1291
  }
1292
+ `,
1293
+ },
1294
+ {
1295
+ title: 'Incorrect',
1296
+ usage: [{ FragmentDefinition: { style: 'PascalCase', forbiddenSuffixes: ['Fragment'] } }],
1297
+ code: /* GraphQL */ `
1298
+ fragment UserFragment on User {
1299
+ # ...
1300
+ }
1301
+ `,
1302
+ },
1303
+ {
1304
+ title: 'Incorrect',
1305
+ usage: [{ 'FieldDefinition[parent.name.value=Query]': { forbiddenPrefixes: ['get'] } }],
1306
+ code: /* GraphQL */ `
1307
+ type Query {
1308
+ getUsers: [User!]!
1309
+ }
1512
1310
  `,
1513
1311
  },
1514
1312
  {
@@ -1520,23 +1318,31 @@ const rule$8 = {
1520
1318
  }
1521
1319
  `,
1522
1320
  },
1523
- ],
1524
- optionsForConfig: [
1525
1321
  {
1526
- types: 'PascalCase',
1527
- fields: 'camelCase',
1528
- overrides: {
1322
+ title: 'Correct',
1323
+ usage: [{ FragmentDefinition: { style: 'PascalCase', forbiddenSuffixes: ['Fragment'] } }],
1324
+ code: /* GraphQL */ `
1325
+ fragment UserFields on User {
1326
+ # ...
1327
+ }
1328
+ `,
1329
+ },
1330
+ {
1331
+ title: 'Correct',
1332
+ usage: [{ 'FieldDefinition[parent.name.value=Query]': { forbiddenPrefixes: ['get'] } }],
1333
+ code: /* GraphQL */ `
1334
+ type Query {
1335
+ users: [User!]!
1336
+ }
1337
+ `,
1338
+ },
1339
+ ],
1340
+ configOptions: {
1341
+ schema: [
1342
+ {
1343
+ types: 'PascalCase',
1344
+ fields: 'camelCase',
1529
1345
  EnumValueDefinition: 'UPPER_CASE',
1530
- OperationDefinition: {
1531
- style: 'PascalCase',
1532
- forbiddenPrefixes: ['Query', 'Mutation', 'Subscription', 'Get'],
1533
- forbiddenSuffixes: ['Query', 'Mutation', 'Subscription'],
1534
- },
1535
- FragmentDefinition: {
1536
- style: 'PascalCase',
1537
- forbiddenPrefixes: ['Fragment'],
1538
- forbiddenSuffixes: ['Fragment'],
1539
- },
1540
1346
  'FieldDefinition[parent.name.value=Query]': {
1541
1347
  forbiddenPrefixes: ['query', 'get'],
1542
1348
  forbiddenSuffixes: ['Query'],
@@ -1550,8 +1356,24 @@ const rule$8 = {
1550
1356
  forbiddenSuffixes: ['Subscription'],
1551
1357
  },
1552
1358
  },
1553
- },
1554
- ],
1359
+ ],
1360
+ operations: [
1361
+ {
1362
+ Argument: 'camelCase',
1363
+ VariableDefinition: 'camelCase',
1364
+ OperationDefinition: {
1365
+ style: 'PascalCase',
1366
+ forbiddenPrefixes: ['Query', 'Mutation', 'Subscription', 'Get'],
1367
+ forbiddenSuffixes: ['Query', 'Mutation', 'Subscription'],
1368
+ },
1369
+ FragmentDefinition: {
1370
+ style: 'PascalCase',
1371
+ forbiddenPrefixes: ['Fragment'],
1372
+ forbiddenSuffixes: ['Fragment'],
1373
+ },
1374
+ },
1375
+ ],
1376
+ },
1555
1377
  },
1556
1378
  schema: {
1557
1379
  definitions: {
@@ -1589,12 +1411,19 @@ const rule$8 = {
1589
1411
  properties: {
1590
1412
  types: {
1591
1413
  ...schemaOption$1,
1592
- description: `Includes:\n\n${TYPES_KINDS.map(kind => `- [${kind}](https://spec.graphql.org/October2021/#${kind})`).join('\n')}`,
1414
+ description: `Includes:\n\n${TYPES_KINDS.map(kind => `- \`${kind}\``).join('\n')}`,
1593
1415
  },
1594
1416
  fields: {
1595
1417
  ...schemaOption$1,
1596
- description: `Includes:\n\n${FIELDS_KINDS.map(kind => `- [${kind}](https://spec.graphql.org/October2021/#${kind})`).join('\n')}`,
1418
+ description: `Includes:\n\n${FIELDS_KINDS.map(kind => `- \`${kind}\``).join('\n')}`,
1597
1419
  },
1420
+ ...Object.fromEntries(ALLOWED_KINDS.map(kind => [
1421
+ kind,
1422
+ {
1423
+ ...schemaOption$1,
1424
+ description: `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`,
1425
+ },
1426
+ ])),
1598
1427
  allowLeadingUnderscore: {
1599
1428
  type: 'boolean',
1600
1429
  default: false,
@@ -1603,35 +1432,27 @@ const rule$8 = {
1603
1432
  type: 'boolean',
1604
1433
  default: false,
1605
1434
  },
1606
- overrides: {
1607
- type: 'object',
1608
- additionalProperties: false,
1609
- description: [
1610
- 'May contain the following `ASTNode` names:',
1611
- '',
1612
- ...ALLOWED_KINDS.map(kind => `- [${kind}](https://spec.graphql.org/October2021/#${kind})`),
1613
- '',
1614
- "> It's also possible to use a [`selector`](https://eslint.org/docs/developer-guide/selectors) that starts with `ASTNode` name",
1615
- '>',
1616
- '> Example: pattern property `FieldDefinition[parent.name.value=Query]` will match only fields for type `Query`',
1617
- ].join('\n'),
1618
- patternProperties: {
1619
- [`^(${ALLOWED_KINDS.join('|')})(.+)?$`]: schemaOption$1,
1620
- },
1621
- },
1622
1435
  },
1436
+ patternProperties: {
1437
+ [`^(${ALLOWED_KINDS.join('|')})(.+)?$`]: schemaOption$1,
1438
+ },
1439
+ description: [
1440
+ "> It's possible to use a [`selector`](https://eslint.org/docs/developer-guide/selectors) that starts with allowed `ASTNode` names which are described below.",
1441
+ '>',
1442
+ '> Paste or drop code into the editor in [ASTExplorer](https://astexplorer.net) and inspect the generated AST to compose your selector.',
1443
+ '>',
1444
+ '> Example: pattern property `FieldDefinition[parent.name.value=Query]` will match only fields for type `Query`.',
1445
+ ].join('\n'),
1623
1446
  },
1624
1447
  },
1625
1448
  },
1626
1449
  create(context) {
1627
- const options = {
1628
- overrides: {},
1629
- ...context.options[0],
1630
- };
1450
+ const options = context.options[0] || {};
1451
+ const { allowLeadingUnderscore, allowTrailingUnderscore, types, fields, ...restOptions } = options;
1631
1452
  function normalisePropertyOption(kind) {
1632
- let style = options.overrides[kind];
1453
+ let style = options[kind];
1633
1454
  if (!style) {
1634
- style = TYPES_KINDS.includes(kind) ? options.types : options.fields;
1455
+ style = TYPES_KINDS.includes(kind) ? types : fields;
1635
1456
  }
1636
1457
  return typeof style === 'object' ? style : { style };
1637
1458
  }
@@ -1652,10 +1473,10 @@ const rule$8 = {
1652
1473
  }
1653
1474
  function getErrorMessage() {
1654
1475
  let name = nodeName;
1655
- if (options.allowLeadingUnderscore) {
1476
+ if (allowLeadingUnderscore) {
1656
1477
  name = name.replace(/^_*/, '');
1657
1478
  }
1658
- if (options.allowTrailingUnderscore) {
1479
+ if (allowTrailingUnderscore) {
1659
1480
  name = name.replace(/_*$/, '');
1660
1481
  }
1661
1482
  if (prefix && !name.startsWith(prefix)) {
@@ -1689,15 +1510,13 @@ const rule$8 = {
1689
1510
  });
1690
1511
  };
1691
1512
  const listeners = {};
1692
- if (!options.allowLeadingUnderscore) {
1513
+ if (!allowLeadingUnderscore) {
1693
1514
  listeners['Name[value=/^_/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore;
1694
1515
  }
1695
- if (!options.allowTrailingUnderscore) {
1516
+ if (!allowTrailingUnderscore) {
1696
1517
  listeners['Name[value=/_$/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore;
1697
1518
  }
1698
- const selectors = new Set([options.types && TYPES_KINDS, options.fields && FIELDS_KINDS, Object.keys(options.overrides)]
1699
- .flat()
1700
- .filter(Boolean));
1519
+ const selectors = new Set([types && TYPES_KINDS, fields && FIELDS_KINDS, Object.keys(restOptions)].flat().filter(Boolean));
1701
1520
  for (const selector of selectors) {
1702
1521
  listeners[selector] = checkNode(selector);
1703
1522
  }
@@ -1706,11 +1525,11 @@ const rule$8 = {
1706
1525
  };
1707
1526
 
1708
1527
  const NO_ANONYMOUS_OPERATIONS = 'NO_ANONYMOUS_OPERATIONS';
1709
- const rule$9 = {
1528
+ const rule$5 = {
1710
1529
  meta: {
1711
1530
  type: 'suggestion',
1712
1531
  docs: {
1713
- category: 'Best Practices',
1532
+ category: 'Operations',
1714
1533
  description: 'Require name for your GraphQL operations. This is useful since most GraphQL client libraries are using the operation name for caching purposes.',
1715
1534
  recommended: true,
1716
1535
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-anonymous-operations.md',
@@ -1754,12 +1573,12 @@ const rule$9 = {
1754
1573
  };
1755
1574
 
1756
1575
  const ERROR_MESSAGE_ID = 'NO_CASE_INSENSITIVE_ENUM_VALUES_DUPLICATES';
1757
- const rule$a = {
1576
+ const rule$6 = {
1758
1577
  meta: {
1759
1578
  type: 'suggestion',
1760
1579
  docs: {
1761
1580
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-case-insensitive-enum-values-duplicates.md',
1762
- category: 'Best Practices',
1581
+ category: 'Schema',
1763
1582
  recommended: true,
1764
1583
  description: 'Disallow case-insensitive enum values duplicates.',
1765
1584
  examples: [
@@ -1810,11 +1629,11 @@ const rule$a = {
1810
1629
  };
1811
1630
 
1812
1631
  const NO_DEPRECATED = 'NO_DEPRECATED';
1813
- const rule$b = {
1632
+ const rule$7 = {
1814
1633
  meta: {
1815
1634
  type: 'suggestion',
1816
1635
  docs: {
1817
- category: 'Best Practices',
1636
+ category: 'Operations',
1818
1637
  description: `Enforce that deprecated fields or enum values are not in use by operations.`,
1819
1638
  url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-deprecated.md`,
1820
1639
  requiresSchema: true,
@@ -1877,48 +1696,150 @@ const rule$b = {
1877
1696
  fullName
1878
1697
  }
1879
1698
  }
1880
- `,
1881
- },
1882
- ],
1883
- },
1884
- messages: {
1885
- [NO_DEPRECATED]: `This {{ type }} is marked as deprecated in your GraphQL schema {{ reason }}`,
1886
- },
1887
- schema: [],
1888
- },
1889
- create(context) {
1699
+ `,
1700
+ },
1701
+ ],
1702
+ recommended: true,
1703
+ },
1704
+ messages: {
1705
+ [NO_DEPRECATED]: `This {{ type }} is marked as deprecated in your GraphQL schema {{ reason }}`,
1706
+ },
1707
+ schema: [],
1708
+ },
1709
+ create(context) {
1710
+ return {
1711
+ EnumValue(node) {
1712
+ requireGraphQLSchemaFromContext('no-deprecated', context);
1713
+ const typeInfo = node.typeInfo();
1714
+ if (typeInfo && typeInfo.enumValue) {
1715
+ if (typeInfo.enumValue.deprecationReason) {
1716
+ const enumValueName = node.value;
1717
+ context.report({
1718
+ loc: getLocation(node.loc, enumValueName),
1719
+ messageId: NO_DEPRECATED,
1720
+ data: {
1721
+ type: 'enum value',
1722
+ reason: typeInfo.enumValue.deprecationReason ? `(reason: ${typeInfo.enumValue.deprecationReason})` : '',
1723
+ },
1724
+ });
1725
+ }
1726
+ }
1727
+ },
1728
+ Field(node) {
1729
+ requireGraphQLSchemaFromContext('no-deprecated', context);
1730
+ const typeInfo = node.typeInfo();
1731
+ if (typeInfo && typeInfo.fieldDef) {
1732
+ if (typeInfo.fieldDef.deprecationReason) {
1733
+ const fieldName = node.name.value;
1734
+ context.report({
1735
+ loc: getLocation(node.loc, fieldName),
1736
+ messageId: NO_DEPRECATED,
1737
+ data: {
1738
+ type: 'field',
1739
+ reason: typeInfo.fieldDef.deprecationReason ? `(reason: ${typeInfo.fieldDef.deprecationReason})` : '',
1740
+ },
1741
+ });
1742
+ }
1743
+ }
1744
+ },
1745
+ };
1746
+ },
1747
+ };
1748
+
1749
+ const NO_DUPLICATE_FIELDS = 'NO_DUPLICATE_FIELDS';
1750
+ const rule$8 = {
1751
+ meta: {
1752
+ type: 'suggestion',
1753
+ docs: {
1754
+ description: `Checks for duplicate fields in selection set, variables in operation definition, or in arguments set of a field.`,
1755
+ category: 'Operations',
1756
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-duplicate-fields.md',
1757
+ recommended: true,
1758
+ examples: [
1759
+ {
1760
+ title: 'Incorrect',
1761
+ code: /* GraphQL */ `
1762
+ query {
1763
+ user {
1764
+ name
1765
+ email
1766
+ name # duplicate field
1767
+ }
1768
+ }
1769
+ `,
1770
+ },
1771
+ {
1772
+ title: 'Incorrect',
1773
+ code: /* GraphQL */ `
1774
+ query {
1775
+ users(
1776
+ first: 100
1777
+ skip: 50
1778
+ after: "cji629tngfgou0b73kt7vi5jo"
1779
+ first: 100 # duplicate argument
1780
+ ) {
1781
+ id
1782
+ }
1783
+ }
1784
+ `,
1785
+ },
1786
+ {
1787
+ title: 'Incorrect',
1788
+ code: /* GraphQL */ `
1789
+ query (
1790
+ $first: Int!
1791
+ $first: Int! # duplicate variable
1792
+ ) {
1793
+ users(first: $first, skip: 50) {
1794
+ id
1795
+ }
1796
+ }
1797
+ `,
1798
+ },
1799
+ ],
1800
+ },
1801
+ messages: {
1802
+ [NO_DUPLICATE_FIELDS]: `{{ type }} "{{ fieldName }}" defined multiple times`,
1803
+ },
1804
+ schema: [],
1805
+ },
1806
+ create(context) {
1807
+ function checkNode(usedFields, fieldName, type, node) {
1808
+ if (usedFields.has(fieldName)) {
1809
+ context.report({
1810
+ loc: getLocation((node.kind === Kind.FIELD && node.alias ? node.alias : node).loc, fieldName, {
1811
+ offsetEnd: node.kind === Kind.VARIABLE_DEFINITION ? 0 : 1,
1812
+ }),
1813
+ messageId: NO_DUPLICATE_FIELDS,
1814
+ data: {
1815
+ type,
1816
+ fieldName,
1817
+ },
1818
+ });
1819
+ }
1820
+ else {
1821
+ usedFields.add(fieldName);
1822
+ }
1823
+ }
1890
1824
  return {
1891
- EnumValue(node) {
1892
- requireGraphQLSchemaFromContext('no-deprecated', context);
1893
- const typeInfo = node.typeInfo();
1894
- if (typeInfo && typeInfo.enumValue) {
1895
- if (typeInfo.enumValue.deprecationReason) {
1896
- const enumValueName = node.value;
1897
- context.report({
1898
- loc: getLocation(node.loc, enumValueName),
1899
- messageId: NO_DEPRECATED,
1900
- data: {
1901
- type: 'enum value',
1902
- reason: typeInfo.enumValue.deprecationReason ? `(reason: ${typeInfo.enumValue.deprecationReason})` : '',
1903
- },
1904
- });
1905
- }
1825
+ OperationDefinition(node) {
1826
+ const set = new Set();
1827
+ for (const varDef of node.variableDefinitions) {
1828
+ checkNode(set, varDef.variable.name.value, 'Operation variable', varDef);
1906
1829
  }
1907
1830
  },
1908
1831
  Field(node) {
1909
- requireGraphQLSchemaFromContext('no-deprecated', context);
1910
- const typeInfo = node.typeInfo();
1911
- if (typeInfo && typeInfo.fieldDef) {
1912
- if (typeInfo.fieldDef.deprecationReason) {
1913
- const fieldName = node.name.value;
1914
- context.report({
1915
- loc: getLocation(node.loc, fieldName),
1916
- messageId: NO_DEPRECATED,
1917
- data: {
1918
- type: 'field',
1919
- reason: typeInfo.fieldDef.deprecationReason ? `(reason: ${typeInfo.fieldDef.deprecationReason})` : '',
1920
- },
1921
- });
1832
+ const set = new Set();
1833
+ for (const arg of node.arguments) {
1834
+ checkNode(set, arg.name.value, 'Field argument', arg);
1835
+ }
1836
+ },
1837
+ SelectionSet(node) {
1838
+ var _a;
1839
+ const set = new Set();
1840
+ for (const selection of node.selections) {
1841
+ if (selection.kind === Kind.FIELD) {
1842
+ checkNode(set, ((_a = selection.alias) === null || _a === void 0 ? void 0 : _a.value) || selection.name.value, 'Field', selection);
1922
1843
  }
1923
1844
  }
1924
1845
  },
@@ -1927,14 +1848,14 @@ const rule$b = {
1927
1848
  };
1928
1849
 
1929
1850
  const HASHTAG_COMMENT = 'HASHTAG_COMMENT';
1930
- const rule$c = {
1851
+ const rule$9 = {
1931
1852
  meta: {
1932
1853
  messages: {
1933
1854
  [HASHTAG_COMMENT]: 'Using hashtag (#) for adding GraphQL descriptions is not allowed. Prefer using """ for multiline, or " for a single line description.',
1934
1855
  },
1935
1856
  docs: {
1936
1857
  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.',
1937
- category: 'Best Practices',
1858
+ category: 'Schema',
1938
1859
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-hashtag-description.md',
1939
1860
  examples: [
1940
1861
  {
@@ -1971,6 +1892,7 @@ const rule$c = {
1971
1892
  `,
1972
1893
  },
1973
1894
  ],
1895
+ recommended: true,
1974
1896
  },
1975
1897
  type: 'suggestion',
1976
1898
  schema: [],
@@ -1999,85 +1921,18 @@ const rule$c = {
1999
1921
  },
2000
1922
  };
2001
1923
 
2002
- const NO_OPERATION_NAME_SUFFIX = 'NO_OPERATION_NAME_SUFFIX';
2003
- const rule$d = {
2004
- meta: {
2005
- fixable: 'code',
2006
- type: 'suggestion',
2007
- docs: {
2008
- category: 'Stylistic Issues',
2009
- recommended: true,
2010
- description: `Makes sure you are not adding the operation type to the name of the operation.`,
2011
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-operation-name-suffix.md`,
2012
- examples: [
2013
- {
2014
- title: 'Incorrect',
2015
- code: /* GraphQL */ `
2016
- query userQuery {
2017
- # ...
2018
- }
2019
- `,
2020
- },
2021
- {
2022
- title: 'Correct',
2023
- code: /* GraphQL */ `
2024
- query user {
2025
- # ...
2026
- }
2027
- `,
2028
- },
2029
- ],
2030
- },
2031
- messages: {
2032
- [NO_OPERATION_NAME_SUFFIX]: `Unnecessary "{{ invalidSuffix }}" suffix in your operation name!`,
2033
- },
2034
- schema: [],
2035
- },
2036
- create(context) {
2037
- return {
2038
- 'OperationDefinition, FragmentDefinition'(node) {
2039
- var _a;
2040
- const name = ((_a = node.name) === null || _a === void 0 ? void 0 : _a.value) || '';
2041
- if (name.length > 0) {
2042
- const invalidSuffix = 'operation' in node ? node.operation : 'fragment';
2043
- if (name.toLowerCase().endsWith(invalidSuffix)) {
2044
- const { start, end } = node.name.loc;
2045
- context.report({
2046
- loc: {
2047
- start: {
2048
- column: start.column - 1 + name.length - invalidSuffix.length,
2049
- line: start.line,
2050
- },
2051
- end: {
2052
- column: end.column - 1 + name.length,
2053
- line: end.line,
2054
- },
2055
- },
2056
- data: {
2057
- invalidSuffix,
2058
- },
2059
- fix: fixer => fixer.removeRange([node.name.range[1] - invalidSuffix.length, node.name.range[1]]),
2060
- messageId: NO_OPERATION_NAME_SUFFIX,
2061
- });
2062
- }
2063
- }
2064
- },
2065
- };
2066
- },
2067
- };
2068
-
2069
- const ROOT_TYPES = ['query', 'mutation', 'subscription'];
2070
- const rule$e = {
1924
+ const ROOT_TYPES = ['mutation', 'subscription'];
1925
+ const rule$a = {
2071
1926
  meta: {
2072
1927
  type: 'suggestion',
2073
1928
  docs: {
2074
- category: 'Validation',
2075
- description: 'Disallow using root types for `read-only` or `write-only` schemas.',
1929
+ category: 'Schema',
1930
+ description: 'Disallow using root types `mutation` and/or `subscription`.',
2076
1931
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-root-type.md',
2077
1932
  requiresSchema: true,
2078
1933
  examples: [
2079
1934
  {
2080
- title: 'Incorrect (`read-only` schema)',
1935
+ title: 'Incorrect',
2081
1936
  usage: [{ disallow: ['mutation', 'subscription'] }],
2082
1937
  code: /* GraphQL */ `
2083
1938
  type Mutation {
@@ -2086,16 +1941,7 @@ const rule$e = {
2086
1941
  `,
2087
1942
  },
2088
1943
  {
2089
- title: 'Incorrect (`write-only` schema)',
2090
- usage: [{ disallow: ['query'] }],
2091
- code: /* GraphQL */ `
2092
- type Query {
2093
- users: [User!]!
2094
- }
2095
- `,
2096
- },
2097
- {
2098
- title: 'Correct (`read-only` schema)',
1944
+ title: 'Correct',
2099
1945
  usage: [{ disallow: ['mutation', 'subscription'] }],
2100
1946
  code: /* GraphQL */ `
2101
1947
  type Query {
@@ -2104,7 +1950,6 @@ const rule$e = {
2104
1950
  `,
2105
1951
  },
2106
1952
  ],
2107
- optionsForConfig: [{ disallow: ['subscription'] }],
2108
1953
  },
2109
1954
  schema: {
2110
1955
  type: 'array',
@@ -2131,7 +1976,6 @@ const rule$e = {
2131
1976
  const schema = requireGraphQLSchemaFromContext('no-root-type', context);
2132
1977
  const disallow = new Set(context.options[0].disallow);
2133
1978
  const rootTypeNames = [
2134
- disallow.has('query') && schema.getQueryType(),
2135
1979
  disallow.has('mutation') && schema.getMutationType(),
2136
1980
  disallow.has('subscription') && schema.getSubscriptionType(),
2137
1981
  ]
@@ -2157,16 +2001,128 @@ const rule$e = {
2157
2001
  },
2158
2002
  };
2159
2003
 
2004
+ const rule$b = {
2005
+ meta: {
2006
+ type: 'suggestion',
2007
+ docs: {
2008
+ category: 'Schema',
2009
+ description: 'Avoid scalar result type on mutation type to make sure to return a valid state.',
2010
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-scalar-result-type-on-mutation.md',
2011
+ requiresSchema: true,
2012
+ examples: [
2013
+ {
2014
+ title: 'Incorrect',
2015
+ code: /* GraphQL */ `
2016
+ type Mutation {
2017
+ createUser: Boolean
2018
+ }
2019
+ `,
2020
+ },
2021
+ {
2022
+ title: 'Correct',
2023
+ code: /* GraphQL */ `
2024
+ type Mutation {
2025
+ createUser: User!
2026
+ }
2027
+ `,
2028
+ },
2029
+ ],
2030
+ },
2031
+ schema: [],
2032
+ },
2033
+ create(context) {
2034
+ const schema = requireGraphQLSchemaFromContext('no-scalar-result-type-on-mutation', context);
2035
+ const mutationType = schema.getMutationType();
2036
+ if (!mutationType) {
2037
+ return {};
2038
+ }
2039
+ const selector = [
2040
+ `:matches(${Kind.OBJECT_TYPE_DEFINITION}, ${Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}]`,
2041
+ '>',
2042
+ Kind.FIELD_DEFINITION,
2043
+ Kind.NAMED_TYPE,
2044
+ ].join(' ');
2045
+ return {
2046
+ [selector](node) {
2047
+ const typeName = node.name.value;
2048
+ const graphQLType = schema.getType(typeName);
2049
+ if (isScalarType(graphQLType)) {
2050
+ context.report({
2051
+ loc: getLocation(node.loc, typeName),
2052
+ message: `Unexpected scalar result type "${typeName}"`,
2053
+ });
2054
+ }
2055
+ },
2056
+ };
2057
+ },
2058
+ };
2059
+
2060
+ const NO_TYPENAME_PREFIX = 'NO_TYPENAME_PREFIX';
2061
+ const rule$c = {
2062
+ meta: {
2063
+ type: 'suggestion',
2064
+ docs: {
2065
+ category: 'Schema',
2066
+ description: 'Enforces users to avoid using the type name in a field name while defining your schema.',
2067
+ recommended: true,
2068
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-typename-prefix.md',
2069
+ examples: [
2070
+ {
2071
+ title: 'Incorrect',
2072
+ code: /* GraphQL */ `
2073
+ type User {
2074
+ userId: ID!
2075
+ }
2076
+ `,
2077
+ },
2078
+ {
2079
+ title: 'Correct',
2080
+ code: /* GraphQL */ `
2081
+ type User {
2082
+ id: ID!
2083
+ }
2084
+ `,
2085
+ },
2086
+ ],
2087
+ },
2088
+ messages: {
2089
+ [NO_TYPENAME_PREFIX]: `Field "{{ fieldName }}" starts with the name of the parent type "{{ typeName }}"`,
2090
+ },
2091
+ schema: [],
2092
+ },
2093
+ create(context) {
2094
+ return {
2095
+ 'ObjectTypeDefinition, ObjectTypeExtension, InterfaceTypeDefinition, InterfaceTypeExtension'(node) {
2096
+ const typeName = node.name.value;
2097
+ const lowerTypeName = typeName.toLowerCase();
2098
+ for (const field of node.fields) {
2099
+ const fieldName = field.name.value;
2100
+ if (fieldName.toLowerCase().startsWith(lowerTypeName)) {
2101
+ context.report({
2102
+ data: {
2103
+ fieldName,
2104
+ typeName,
2105
+ },
2106
+ messageId: NO_TYPENAME_PREFIX,
2107
+ loc: getLocation(field.loc, lowerTypeName),
2108
+ });
2109
+ }
2110
+ }
2111
+ },
2112
+ };
2113
+ },
2114
+ };
2115
+
2160
2116
  const UNREACHABLE_TYPE = 'UNREACHABLE_TYPE';
2161
2117
  const RULE_NAME = 'no-unreachable-types';
2162
- const rule$f = {
2118
+ const rule$d = {
2163
2119
  meta: {
2164
2120
  messages: {
2165
- [UNREACHABLE_TYPE]: `Type "{{ typeName }}" is unreachable`,
2121
+ [UNREACHABLE_TYPE]: 'Type "{{ typeName }}" is unreachable',
2166
2122
  },
2167
2123
  docs: {
2168
2124
  description: `Requires all types to be reachable at some level by root level fields.`,
2169
- category: 'Best Practices',
2125
+ category: 'Schema',
2170
2126
  url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME}.md`,
2171
2127
  requiresSchema: true,
2172
2128
  examples: [
@@ -2197,6 +2153,7 @@ const rule$f = {
2197
2153
  `,
2198
2154
  },
2199
2155
  ],
2156
+ recommended: true,
2200
2157
  },
2201
2158
  fixable: 'code',
2202
2159
  type: 'suggestion',
@@ -2235,14 +2192,14 @@ const rule$f = {
2235
2192
 
2236
2193
  const UNUSED_FIELD = 'UNUSED_FIELD';
2237
2194
  const RULE_NAME$1 = 'no-unused-fields';
2238
- const rule$g = {
2195
+ const rule$e = {
2239
2196
  meta: {
2240
2197
  messages: {
2241
2198
  [UNUSED_FIELD]: `Field "{{fieldName}}" is unused`,
2242
2199
  },
2243
2200
  docs: {
2244
2201
  description: `Requires all fields to be used at some level by siblings operations.`,
2245
- category: 'Best Practices',
2202
+ category: 'Schema',
2246
2203
  url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$1}.md`,
2247
2204
  requiresSiblings: true,
2248
2205
  requiresSchema: true,
@@ -2424,11 +2381,11 @@ const MESSAGE_REQUIRE_DATE = 'MESSAGE_REQUIRE_DATE';
2424
2381
  const MESSAGE_INVALID_FORMAT = 'MESSAGE_INVALID_FORMAT';
2425
2382
  const MESSAGE_INVALID_DATE = 'MESSAGE_INVALID_DATE';
2426
2383
  const MESSAGE_CAN_BE_REMOVED = 'MESSAGE_CAN_BE_REMOVED';
2427
- const rule$h = {
2384
+ const rule$f = {
2428
2385
  meta: {
2429
2386
  type: 'suggestion',
2430
2387
  docs: {
2431
- category: 'Best Practices',
2388
+ category: 'Schema',
2432
2389
  description: 'Require deletion date on `@deprecated` directive. Suggest removing deprecated things after deprecated date.',
2433
2390
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-deprecation-date.md',
2434
2391
  examples: [
@@ -2527,11 +2484,11 @@ const rule$h = {
2527
2484
  },
2528
2485
  };
2529
2486
 
2530
- const rule$i = {
2487
+ const rule$g = {
2531
2488
  meta: {
2532
2489
  docs: {
2533
2490
  description: `Require all deprecation directives to specify a reason.`,
2534
- category: 'Best Practices',
2491
+ category: 'Schema',
2535
2492
  url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-deprecation-reason.md`,
2536
2493
  recommended: true,
2537
2494
  examples: [
@@ -2589,16 +2546,16 @@ const ALLOWED_KINDS$1 = [
2589
2546
  Kind.ENUM_VALUE_DEFINITION,
2590
2547
  Kind.DIRECTIVE_DEFINITION,
2591
2548
  ];
2592
- const rule$j = {
2549
+ const rule$h = {
2593
2550
  meta: {
2594
2551
  docs: {
2595
- category: 'Best Practices',
2552
+ category: 'Schema',
2596
2553
  description: 'Enforce descriptions in your type definitions.',
2597
2554
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-description.md',
2598
2555
  examples: [
2599
2556
  {
2600
2557
  title: 'Incorrect',
2601
- usage: [{ types: true, overrides: { FieldDefinition: true } }],
2558
+ usage: [{ types: true, FieldDefinition: true }],
2602
2559
  code: /* GraphQL */ `
2603
2560
  type someTypeName {
2604
2561
  name: String
@@ -2607,7 +2564,7 @@ const rule$j = {
2607
2564
  },
2608
2565
  {
2609
2566
  title: 'Correct',
2610
- usage: [{ types: true, overrides: { FieldDefinition: true } }],
2567
+ usage: [{ types: true, FieldDefinition: true }],
2611
2568
  code: /* GraphQL */ `
2612
2569
  """
2613
2570
  Some type description
@@ -2621,14 +2578,13 @@ const rule$j = {
2621
2578
  `,
2622
2579
  },
2623
2580
  ],
2624
- optionsForConfig: [
2581
+ configOptions: [
2625
2582
  {
2626
2583
  types: true,
2627
- overrides: {
2628
- [Kind.DIRECTIVE_DEFINITION]: true,
2629
- },
2584
+ [Kind.DIRECTIVE_DEFINITION]: true,
2630
2585
  },
2631
2586
  ],
2587
+ recommended: true,
2632
2588
  },
2633
2589
  type: 'suggestion',
2634
2590
  messages: {
@@ -2645,22 +2601,23 @@ const rule$j = {
2645
2601
  properties: {
2646
2602
  types: {
2647
2603
  type: 'boolean',
2648
- description: `Includes:\n\n${TYPES_KINDS.map(kind => `- [${kind}](https://spec.graphql.org/October2021/#${kind})`).join('\n')}`,
2649
- },
2650
- overrides: {
2651
- type: 'object',
2652
- description: 'Configuration for precise `ASTNode`',
2653
- additionalProperties: false,
2654
- properties: Object.fromEntries(ALLOWED_KINDS$1.map(kind => [kind, { type: 'boolean' }])),
2604
+ description: `Includes:\n\n${TYPES_KINDS.map(kind => `- \`${kind}\``).join('\n')}`,
2655
2605
  },
2606
+ ...Object.fromEntries([...ALLOWED_KINDS$1].sort().map(kind => [
2607
+ kind,
2608
+ {
2609
+ type: 'boolean',
2610
+ description: `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`,
2611
+ },
2612
+ ])),
2656
2613
  },
2657
2614
  },
2658
2615
  },
2659
2616
  },
2660
2617
  create(context) {
2661
- const { types, overrides = {} } = context.options[0];
2618
+ const { types, ...restOptions } = context.options[0];
2662
2619
  const kinds = new Set(types ? TYPES_KINDS : []);
2663
- for (const [kind, isEnabled] of Object.entries(overrides)) {
2620
+ for (const [kind, isEnabled] of Object.entries(restOptions)) {
2664
2621
  if (isEnabled) {
2665
2622
  kinds.add(kind);
2666
2623
  }
@@ -2688,11 +2645,11 @@ const rule$j = {
2688
2645
  };
2689
2646
 
2690
2647
  const RULE_NAME$2 = 'require-field-of-type-query-in-mutation-result';
2691
- const rule$k = {
2648
+ const rule$i = {
2692
2649
  meta: {
2693
2650
  type: 'suggestion',
2694
2651
  docs: {
2695
- category: 'Best Practices',
2652
+ category: 'Schema',
2696
2653
  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`.',
2697
2654
  url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$2}.md`,
2698
2655
  requiresSchema: true,
@@ -2856,13 +2813,13 @@ const convertNode = (typeInfo) => (node, key, parent) => {
2856
2813
 
2857
2814
  const REQUIRE_ID_WHEN_AVAILABLE = 'REQUIRE_ID_WHEN_AVAILABLE';
2858
2815
  const DEFAULT_ID_FIELD_NAME = 'id';
2859
- const rule$l = {
2816
+ const rule$j = {
2860
2817
  meta: {
2861
2818
  type: 'suggestion',
2862
2819
  docs: {
2863
- category: 'Best Practices',
2864
- description: `Enforce selecting specific fields when they are available on the GraphQL type.`,
2865
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-id-when-available.md`,
2820
+ category: 'Operations',
2821
+ description: 'Enforce selecting specific fields when they are available on the GraphQL type.',
2822
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-id-when-available.md',
2866
2823
  requiresSchema: true,
2867
2824
  requiresSiblings: true,
2868
2825
  examples: [
@@ -2902,17 +2859,17 @@ const rule$l = {
2902
2859
  `,
2903
2860
  },
2904
2861
  ],
2862
+ recommended: true,
2905
2863
  },
2906
2864
  messages: {
2907
- [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 }}".`,
2865
+ [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 }}".`,
2908
2866
  },
2909
2867
  schema: {
2910
2868
  type: 'array',
2911
- additionalItems: false,
2912
- minItems: 0,
2913
2869
  maxItems: 1,
2914
2870
  items: {
2915
2871
  type: 'object',
2872
+ additionalProperties: false,
2916
2873
  properties: {
2917
2874
  fieldName: {
2918
2875
  type: 'string',
@@ -2992,12 +2949,12 @@ const rule$l = {
2992
2949
  },
2993
2950
  };
2994
2951
 
2995
- const rule$m = {
2952
+ const rule$k = {
2996
2953
  meta: {
2997
2954
  docs: {
2998
- category: 'Best Practices',
2955
+ category: 'Operations',
2999
2956
  description: `Limit the complexity of the GraphQL operations solely by their depth. Based on [graphql-depth-limit](https://github.com/stems/graphql-depth-limit).`,
3000
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/selection-set-depth.md`,
2957
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/selection-set-depth.md',
3001
2958
  requiresSiblings: true,
3002
2959
  examples: [
3003
2960
  {
@@ -3040,22 +2997,26 @@ const rule$m = {
3040
2997
  `,
3041
2998
  },
3042
2999
  ],
3000
+ recommended: true,
3001
+ configOptions: [{ maxDepth: 7 }],
3043
3002
  },
3044
3003
  type: 'suggestion',
3045
3004
  schema: {
3046
3005
  type: 'array',
3047
- additionalItems: false,
3048
3006
  minItems: 1,
3049
3007
  maxItems: 1,
3050
3008
  items: {
3051
3009
  type: 'object',
3052
- require: ['maxDepth'],
3010
+ additionalProperties: false,
3011
+ required: ['maxDepth'],
3053
3012
  properties: {
3054
3013
  maxDepth: {
3055
3014
  type: 'number',
3056
3015
  },
3057
3016
  ignore: {
3058
3017
  type: 'array',
3018
+ uniqueItems: true,
3019
+ minItems: 1,
3059
3020
  items: {
3060
3021
  type: 'string',
3061
3022
  },
@@ -3114,12 +3075,12 @@ const shouldIgnoreNode = ({ node, exceptions }) => {
3114
3075
  }
3115
3076
  return false;
3116
3077
  };
3117
- const rule$n = {
3078
+ const rule$l = {
3118
3079
  meta: {
3119
3080
  type: 'suggestion',
3120
3081
  docs: {
3121
3082
  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.',
3122
- category: 'Best Practices',
3083
+ category: 'Schema',
3123
3084
  recommended: true,
3124
3085
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/strict-id-in-types.md',
3125
3086
  examples: [
@@ -3179,13 +3140,15 @@ const rule$n = {
3179
3140
  ],
3180
3141
  },
3181
3142
  schema: {
3182
- $schema: 'http://json-schema.org/draft-04/schema#',
3183
3143
  type: 'array',
3144
+ maxItems: 1,
3184
3145
  items: {
3185
3146
  type: 'object',
3147
+ additionalProperties: false,
3186
3148
  properties: {
3187
3149
  acceptedIdNames: {
3188
3150
  type: 'array',
3151
+ uniqueItems: true,
3189
3152
  items: {
3190
3153
  type: 'string',
3191
3154
  },
@@ -3193,6 +3156,7 @@ const rule$n = {
3193
3156
  },
3194
3157
  acceptedIdTypes: {
3195
3158
  type: 'array',
3159
+ uniqueItems: true,
3196
3160
  items: {
3197
3161
  type: 'string',
3198
3162
  },
@@ -3203,19 +3167,21 @@ const rule$n = {
3203
3167
  properties: {
3204
3168
  types: {
3205
3169
  type: 'array',
3170
+ uniqueItems: true,
3171
+ minItems: 1,
3206
3172
  description: 'This is used to exclude types with names that match one of the specified values.',
3207
3173
  items: {
3208
3174
  type: 'string',
3209
3175
  },
3210
- default: [],
3211
3176
  },
3212
3177
  suffixes: {
3213
3178
  type: 'array',
3179
+ uniqueItems: true,
3180
+ minItems: 1,
3214
3181
  description: 'This is used to exclude types with names with suffixes that match one of the specified values.',
3215
3182
  items: {
3216
3183
  type: 'string',
3217
3184
  },
3218
- default: [],
3219
3185
  },
3220
3186
  },
3221
3187
  },
@@ -3291,11 +3257,11 @@ const checkNode = (context, node, ruleName, messageId) => {
3291
3257
  });
3292
3258
  }
3293
3259
  };
3294
- const rule$o = {
3260
+ const rule$m = {
3295
3261
  meta: {
3296
3262
  type: 'suggestion',
3297
3263
  docs: {
3298
- category: 'Best Practices',
3264
+ category: 'Operations',
3299
3265
  description: `Enforce unique fragment names across your project.`,
3300
3266
  url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$3}.md`,
3301
3267
  requiresSiblings: true,
@@ -3350,11 +3316,11 @@ const rule$o = {
3350
3316
 
3351
3317
  const RULE_NAME$4 = 'unique-operation-name';
3352
3318
  const UNIQUE_OPERATION_NAME = 'UNIQUE_OPERATION_NAME';
3353
- const rule$p = {
3319
+ const rule$n = {
3354
3320
  meta: {
3355
3321
  type: 'suggestion',
3356
3322
  docs: {
3357
- category: 'Best Practices',
3323
+ category: 'Operations',
3358
3324
  description: `Enforce unique operation names across your project.`,
3359
3325
  url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$4}.md`,
3360
3326
  requiresSiblings: true,
@@ -3417,31 +3383,29 @@ const rule$p = {
3417
3383
  const rules = {
3418
3384
  ...GRAPHQL_JS_VALIDATIONS,
3419
3385
  alphabetize: rule,
3420
- 'avoid-duplicate-fields': rule$1,
3421
- 'avoid-operation-name-prefix': rule$2,
3422
- 'avoid-scalar-result-type-on-mutation': rule$3,
3423
- 'avoid-typename-prefix': rule$4,
3424
- 'description-style': rule$5,
3425
- 'input-name': rule$6,
3426
- 'match-document-filename': rule$7,
3427
- 'naming-convention': rule$8,
3428
- 'no-anonymous-operations': rule$9,
3429
- 'no-case-insensitive-enum-values-duplicates': rule$a,
3430
- 'no-deprecated': rule$b,
3431
- 'no-hashtag-description': rule$c,
3432
- 'no-operation-name-suffix': rule$d,
3433
- 'no-root-type': rule$e,
3434
- 'no-unreachable-types': rule$f,
3435
- 'no-unused-fields': rule$g,
3436
- 'require-deprecation-date': rule$h,
3437
- 'require-deprecation-reason': rule$i,
3438
- 'require-description': rule$j,
3439
- 'require-field-of-type-query-in-mutation-result': rule$k,
3440
- 'require-id-when-available': rule$l,
3441
- 'selection-set-depth': rule$m,
3442
- 'strict-id-in-types': rule$n,
3443
- 'unique-fragment-name': rule$o,
3444
- 'unique-operation-name': rule$p,
3386
+ 'description-style': rule$1,
3387
+ 'input-name': rule$2,
3388
+ 'match-document-filename': rule$3,
3389
+ 'naming-convention': rule$4,
3390
+ 'no-anonymous-operations': rule$5,
3391
+ 'no-case-insensitive-enum-values-duplicates': rule$6,
3392
+ 'no-deprecated': rule$7,
3393
+ 'no-duplicate-fields': rule$8,
3394
+ 'no-hashtag-description': rule$9,
3395
+ 'no-root-type': rule$a,
3396
+ 'no-scalar-result-type-on-mutation': rule$b,
3397
+ 'no-typename-prefix': rule$c,
3398
+ 'no-unreachable-types': rule$d,
3399
+ 'no-unused-fields': rule$e,
3400
+ 'require-deprecation-date': rule$f,
3401
+ 'require-deprecation-reason': rule$g,
3402
+ 'require-description': rule$h,
3403
+ 'require-field-of-type-query-in-mutation-result': rule$i,
3404
+ 'require-id-when-available': rule$j,
3405
+ 'selection-set-depth': rule$k,
3406
+ 'strict-id-in-types': rule$l,
3407
+ 'unique-fragment-name': rule$m,
3408
+ 'unique-operation-name': rule$n,
3445
3409
  };
3446
3410
 
3447
3411
  const RELEVANT_KEYWORDS = ['gql', 'graphql', '/* GraphQL */'];