@graphql-eslint/eslint-plugin 4.3.0 → 4.3.1-alpha-20241209185034-de2d7397da8c26620a8930dd12b6dff42e43f537

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 (100) hide show
  1. package/{index.browser.js → browser.js} +1756 -1104
  2. package/cjs/cache.js +6 -2
  3. package/cjs/configs/operations-all.js +2 -2
  4. package/cjs/configs/schema-all.js +2 -2
  5. package/cjs/configs/schema-recommended.js +1 -1
  6. package/cjs/documents.js +13 -7
  7. package/cjs/estree-converter/converter.js +17 -8
  8. package/cjs/estree-converter/utils.js +22 -9
  9. package/cjs/graphql-config.js +13 -6
  10. package/cjs/meta.js +1 -1
  11. package/cjs/parser.js +36 -9
  12. package/cjs/processor.js +48 -20
  13. package/cjs/rules/alphabetize/index.js +99 -47
  14. package/cjs/rules/description-style/index.js +10 -6
  15. package/cjs/rules/graphql-js-validation.js +142 -108
  16. package/cjs/rules/input-name/index.js +51 -38
  17. package/cjs/rules/lone-executable-definition/index.js +15 -6
  18. package/cjs/rules/match-document-filename/index.js +57 -32
  19. package/cjs/rules/naming-convention/index.js +76 -37
  20. package/cjs/rules/no-anonymous-operations/index.js +8 -5
  21. package/cjs/rules/no-deprecated/index.js +27 -13
  22. package/cjs/rules/no-duplicate-fields/index.js +15 -8
  23. package/cjs/rules/no-hashtag-description/index.js +18 -10
  24. package/cjs/rules/no-one-place-fragments/index.js +17 -10
  25. package/cjs/rules/no-root-type/index.js +15 -8
  26. package/cjs/rules/no-scalar-result-type-on-mutation/index.js +20 -12
  27. package/cjs/rules/no-typename-prefix/index.js +25 -21
  28. package/cjs/rules/no-unreachable-types/index.js +34 -17
  29. package/cjs/rules/no-unused-fields/index.js +56 -30
  30. package/cjs/rules/relay-arguments/index.js +31 -13
  31. package/cjs/rules/relay-connection-types/index.js +31 -9
  32. package/cjs/rules/relay-edge-types/index.js +84 -41
  33. package/cjs/rules/relay-page-info/index.js +31 -14
  34. package/cjs/rules/require-deprecation-date/index.js +20 -9
  35. package/cjs/rules/require-deprecation-reason/index.js +8 -5
  36. package/cjs/rules/require-description/index.js +60 -42
  37. package/cjs/rules/require-field-of-type-query-in-mutation-result/index.js +21 -10
  38. package/cjs/rules/require-import-fragment/index.js +20 -11
  39. package/cjs/rules/require-nullable-fields-with-oneof/index.js +12 -5
  40. package/cjs/rules/require-nullable-result-in-root/index.js +32 -27
  41. package/cjs/rules/require-selections/index.js +88 -46
  42. package/cjs/rules/require-type-pattern-with-oneof/index.js +14 -10
  43. package/cjs/rules/selection-set-depth/index.js +19 -10
  44. package/cjs/rules/strict-id-in-types/index.js +32 -19
  45. package/cjs/rules/unique-enum-value-names/index.js +4 -3
  46. package/cjs/rules/unique-fragment-name/index.js +25 -18
  47. package/cjs/rules/unique-operation-name/index.js +5 -5
  48. package/cjs/schema.js +14 -8
  49. package/cjs/siblings.js +60 -32
  50. package/cjs/utils.js +23 -9
  51. package/esm/cache.js +6 -2
  52. package/esm/configs/operations-all.js +2 -2
  53. package/esm/configs/schema-all.js +2 -2
  54. package/esm/configs/schema-recommended.js +1 -1
  55. package/esm/documents.js +13 -7
  56. package/esm/estree-converter/converter.js +17 -8
  57. package/esm/estree-converter/utils.js +22 -9
  58. package/esm/graphql-config.js +13 -6
  59. package/esm/meta.js +1 -1
  60. package/esm/parser.js +36 -9
  61. package/esm/processor.js +48 -20
  62. package/esm/rules/alphabetize/index.js +99 -47
  63. package/esm/rules/description-style/index.js +10 -6
  64. package/esm/rules/graphql-js-validation.js +142 -108
  65. package/esm/rules/input-name/index.js +51 -38
  66. package/esm/rules/lone-executable-definition/index.js +15 -6
  67. package/esm/rules/match-document-filename/index.js +57 -32
  68. package/esm/rules/naming-convention/index.js +76 -37
  69. package/esm/rules/no-anonymous-operations/index.js +8 -5
  70. package/esm/rules/no-deprecated/index.js +27 -13
  71. package/esm/rules/no-duplicate-fields/index.js +15 -8
  72. package/esm/rules/no-hashtag-description/index.js +18 -10
  73. package/esm/rules/no-one-place-fragments/index.js +17 -10
  74. package/esm/rules/no-root-type/index.js +15 -8
  75. package/esm/rules/no-scalar-result-type-on-mutation/index.js +20 -12
  76. package/esm/rules/no-typename-prefix/index.js +25 -21
  77. package/esm/rules/no-unreachable-types/index.js +34 -17
  78. package/esm/rules/no-unused-fields/index.js +56 -30
  79. package/esm/rules/relay-arguments/index.js +31 -13
  80. package/esm/rules/relay-connection-types/index.js +31 -9
  81. package/esm/rules/relay-edge-types/index.js +84 -41
  82. package/esm/rules/relay-page-info/index.js +31 -14
  83. package/esm/rules/require-deprecation-date/index.js +20 -9
  84. package/esm/rules/require-deprecation-reason/index.js +8 -5
  85. package/esm/rules/require-description/index.js +60 -42
  86. package/esm/rules/require-field-of-type-query-in-mutation-result/index.js +21 -10
  87. package/esm/rules/require-import-fragment/index.js +20 -11
  88. package/esm/rules/require-nullable-fields-with-oneof/index.js +12 -5
  89. package/esm/rules/require-nullable-result-in-root/index.js +32 -27
  90. package/esm/rules/require-selections/index.js +88 -46
  91. package/esm/rules/require-type-pattern-with-oneof/index.js +14 -10
  92. package/esm/rules/selection-set-depth/index.js +19 -10
  93. package/esm/rules/strict-id-in-types/index.js +32 -19
  94. package/esm/rules/unique-enum-value-names/index.js +4 -3
  95. package/esm/rules/unique-fragment-name/index.js +25 -18
  96. package/esm/rules/unique-operation-name/index.js +5 -5
  97. package/esm/schema.js +15 -8
  98. package/esm/siblings.js +60 -32
  99. package/esm/utils.js +23 -9
  100. package/package.json +11 -1
@@ -8,57 +8,60 @@ import {
8
8
  requireGraphQLSchema,
9
9
  TYPES_KINDS
10
10
  } from "../../utils.js";
11
- const RULE_ID = "require-description", ALLOWED_KINDS = [
11
+ const RULE_ID = "require-description";
12
+ const ALLOWED_KINDS = [
12
13
  ...TYPES_KINDS,
13
14
  Kind.DIRECTIVE_DEFINITION,
14
15
  Kind.FIELD_DEFINITION,
15
16
  Kind.INPUT_VALUE_DEFINITION,
16
17
  Kind.ENUM_VALUE_DEFINITION,
17
18
  Kind.OPERATION_DEFINITION
18
- ], schema = {
19
+ ];
20
+ const schema = {
19
21
  type: "array",
20
22
  minItems: 1,
21
23
  maxItems: 1,
22
24
  items: {
23
25
  type: "object",
24
- additionalProperties: !1,
26
+ additionalProperties: false,
25
27
  minProperties: 1,
26
28
  properties: {
27
29
  types: {
28
30
  type: "boolean",
29
- enum: [!0],
31
+ enum: [true],
30
32
  description: `Includes:
31
- ${TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
32
- `)}`
33
+ ${TYPES_KINDS.map((kind) => `- \`${kind}\``).join("\n")}`
33
34
  },
34
35
  rootField: {
35
36
  type: "boolean",
36
- enum: [!0],
37
+ enum: [true],
37
38
  description: "Definitions within `Query`, `Mutation`, and `Subscription` root types."
38
39
  },
39
40
  ignoredSelectors: {
40
41
  ...ARRAY_DEFAULT_OPTIONS,
41
- description: ["Ignore specific selectors", eslintSelectorsTip].join(`
42
- `)
42
+ description: ["Ignore specific selectors", eslintSelectorsTip].join("\n")
43
43
  },
44
44
  ...Object.fromEntries(
45
45
  [...ALLOWED_KINDS].sort().map((kind) => {
46
46
  let description = `> [!NOTE]
47
47
  >
48
48
  > Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`;
49
- return kind === Kind.OPERATION_DEFINITION && (description += [
50
- "",
51
- "",
52
- "> [!WARNING]",
53
- ">",
54
- '> You must use only comment syntax `#` and not description syntax `"""` or `"`.'
55
- ].join(`
56
- `)), [kind, { type: "boolean", description }];
49
+ if (kind === Kind.OPERATION_DEFINITION) {
50
+ description += [
51
+ "",
52
+ "",
53
+ "> [!WARNING]",
54
+ ">",
55
+ '> You must use only comment syntax `#` and not description syntax `"""` or `"`.'
56
+ ].join("\n");
57
+ }
58
+ return [kind, { type: "boolean", description }];
57
59
  })
58
60
  )
59
61
  }
60
62
  }
61
- }, rule = {
63
+ };
64
+ const rule = {
62
65
  meta: {
63
66
  docs: {
64
67
  category: "Schema",
@@ -67,7 +70,7 @@ ${TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
67
70
  examples: [
68
71
  {
69
72
  title: "Incorrect",
70
- usage: [{ types: !0, FieldDefinition: !0 }],
73
+ usage: [{ types: true, FieldDefinition: true }],
71
74
  code: (
72
75
  /* GraphQL */
73
76
  `
@@ -79,7 +82,7 @@ ${TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
79
82
  },
80
83
  {
81
84
  title: "Correct",
82
- usage: [{ types: !0, FieldDefinition: !0 }],
85
+ usage: [{ types: true, FieldDefinition: true }],
83
86
  code: (
84
87
  /* GraphQL */
85
88
  `
@@ -97,7 +100,7 @@ ${TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
97
100
  },
98
101
  {
99
102
  title: "Correct",
100
- usage: [{ OperationDefinition: !0 }],
103
+ usage: [{ OperationDefinition: true }],
101
104
  code: (
102
105
  /* GraphQL */
103
106
  `
@@ -110,7 +113,7 @@ ${TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
110
113
  },
111
114
  {
112
115
  title: "Correct",
113
- usage: [{ rootField: !0 }],
116
+ usage: [{ rootField: true }],
114
117
  code: (
115
118
  /* GraphQL */
116
119
  `
@@ -158,12 +161,12 @@ ${TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
158
161
  ],
159
162
  configOptions: [
160
163
  {
161
- types: !0,
162
- [Kind.DIRECTIVE_DEFINITION]: !0,
163
- rootField: !0
164
+ types: true,
165
+ [Kind.DIRECTIVE_DEFINITION]: true,
166
+ rootField: true
164
167
  }
165
168
  ],
166
- recommended: !0
169
+ recommended: true
167
170
  },
168
171
  type: "suggestion",
169
172
  messages: {
@@ -172,11 +175,18 @@ ${TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
172
175
  schema
173
176
  },
174
177
  create(context) {
175
- const { types, rootField, ignoredSelectors = [], ...restOptions } = context.options[0] || {}, kinds = new Set(types ? TYPES_KINDS : []);
176
- for (const [kind, isEnabled] of Object.entries(restOptions))
177
- isEnabled ? kinds.add(kind) : kinds.delete(kind);
178
+ const { types, rootField, ignoredSelectors = [], ...restOptions } = context.options[0] || {};
179
+ const kinds = new Set(types ? TYPES_KINDS : []);
180
+ for (const [kind, isEnabled] of Object.entries(restOptions)) {
181
+ if (isEnabled) {
182
+ kinds.add(kind);
183
+ } else {
184
+ kinds.delete(kind);
185
+ }
186
+ }
178
187
  if (rootField) {
179
- const schema2 = requireGraphQLSchema(RULE_ID, context), rootTypeNames = getRootTypeNames(schema2);
188
+ const schema2 = requireGraphQLSchema(RULE_ID, context);
189
+ const rootTypeNames = getRootTypeNames(schema2);
180
190
  kinds.add(
181
191
  `:matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=/^(${[
182
192
  ...rootTypeNames
@@ -184,27 +194,35 @@ ${TYPES_KINDS.map((kind) => `- \`${kind}\``).join(`
184
194
  );
185
195
  }
186
196
  let selector = `:matches(${[...kinds]})`;
187
- for (const str of ignoredSelectors)
197
+ for (const str of ignoredSelectors) {
188
198
  selector += `:not(${str})`;
199
+ }
189
200
  return {
190
201
  [selector](node) {
191
202
  let description = "";
192
203
  const isOperation = node.kind === Kind.OPERATION_DEFINITION;
193
204
  if (isOperation) {
194
- const rawNode = node.rawNode(), { prev, line } = rawNode.loc.startToken;
205
+ const rawNode = node.rawNode();
206
+ const { prev, line } = rawNode.loc.startToken;
195
207
  if (prev?.kind === TokenKind.COMMENT) {
196
- const value = prev.value.trim(), linesBefore = line - prev.line;
197
- !value.startsWith("eslint") && linesBefore === 1 && (description = value);
208
+ const value = prev.value.trim();
209
+ const linesBefore = line - prev.line;
210
+ if (!value.startsWith("eslint") && linesBefore === 1) {
211
+ description = value;
212
+ }
198
213
  }
199
- } else
214
+ } else {
200
215
  description = node.description?.value.trim() || "";
201
- description.length === 0 && context.report({
202
- loc: isOperation ? getLocation(node.loc.start, node.operation) : node.name.loc,
203
- messageId: RULE_ID,
204
- data: {
205
- nodeName: getNodeName(node)
206
- }
207
- });
216
+ }
217
+ if (description.length === 0) {
218
+ context.report({
219
+ loc: isOperation ? getLocation(node.loc.start, node.operation) : node.name.loc,
220
+ messageId: RULE_ID,
221
+ data: {
222
+ nodeName: getNodeName(node)
223
+ }
224
+ });
225
+ }
208
226
  }
209
227
  };
210
228
  }
@@ -1,13 +1,14 @@
1
1
  import { isObjectType } from "graphql";
2
2
  import { getTypeName, requireGraphQLSchema } from "../../utils.js";
3
- const RULE_ID = "require-field-of-type-query-in-mutation-result", rule = {
3
+ const RULE_ID = "require-field-of-type-query-in-mutation-result";
4
+ const rule = {
4
5
  meta: {
5
6
  type: "suggestion",
6
7
  docs: {
7
8
  category: "Schema",
8
9
  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`.",
9
10
  url: `https://the-guild.dev/graphql/eslint/rules/${RULE_ID}`,
10
- requiresSchema: !0,
11
+ requiresSchema: true,
11
12
  examples: [
12
13
  {
13
14
  title: "Incorrect",
@@ -47,16 +48,26 @@ const RULE_ID = "require-field-of-type-query-in-mutation-result", rule = {
47
48
  schema: []
48
49
  },
49
50
  create(context) {
50
- const schema = requireGraphQLSchema(RULE_ID, context), mutationType = schema.getMutationType(), queryType = schema.getQueryType();
51
- return !mutationType || !queryType ? {} : {
52
- [`:matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=${mutationType.name}] > FieldDefinition > .gqlType Name`](node) {
53
- const typeName = node.value, graphQLType = schema.getType(typeName);
51
+ const schema = requireGraphQLSchema(RULE_ID, context);
52
+ const mutationType = schema.getMutationType();
53
+ const queryType = schema.getQueryType();
54
+ if (!mutationType || !queryType) {
55
+ return {};
56
+ }
57
+ const selector = `:matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=${mutationType.name}] > FieldDefinition > .gqlType Name`;
58
+ return {
59
+ [selector](node) {
60
+ const typeName = node.value;
61
+ const graphQLType = schema.getType(typeName);
54
62
  if (isObjectType(graphQLType)) {
55
63
  const { fields } = graphQLType.astNode;
56
- fields?.some((field) => getTypeName(field) === queryType.name) || context.report({
57
- node,
58
- message: `Mutation result type "${graphQLType.name}" must contain field of type "${queryType.name}"`
59
- });
64
+ const hasQueryType = fields?.some((field) => getTypeName(field) === queryType.name);
65
+ if (!hasQueryType) {
66
+ context.report({
67
+ node,
68
+ message: `Mutation result type "${graphQLType.name}" must contain field of type "${queryType.name}"`
69
+ });
70
+ }
60
71
  }
61
72
  }
62
73
  };
@@ -1,6 +1,8 @@
1
1
  import path from "node:path";
2
2
  import { requireGraphQLOperations, slash } from "../../utils.js";
3
- const RULE_ID = "require-import-fragment", SUGGESTION_ID = "add-import-expression", rule = {
3
+ const RULE_ID = "require-import-fragment";
4
+ const SUGGESTION_ID = "add-import-expression";
5
+ const rule = {
4
6
  meta: {
5
7
  type: "suggestion",
6
8
  docs: {
@@ -64,9 +66,9 @@ const RULE_ID = "require-import-fragment", SUGGESTION_ID = "add-import-expressio
64
66
  )
65
67
  }
66
68
  ],
67
- requiresSiblings: !0
69
+ requiresSiblings: true
68
70
  },
69
- hasSuggestions: !0,
71
+ hasSuggestions: true,
70
72
  messages: {
71
73
  [RULE_ID]: 'Expected "{{fragmentName}}" fragment to be imported.',
72
74
  [SUGGESTION_ID]: 'Add import expression for "{{fragmentName}}".'
@@ -74,24 +76,31 @@ const RULE_ID = "require-import-fragment", SUGGESTION_ID = "add-import-expressio
74
76
  schema: []
75
77
  },
76
78
  create(context) {
77
- const comments = context.getSourceCode().getAllComments(), siblings = requireGraphQLOperations(RULE_ID, context), filePath = context.filename;
79
+ const comments = context.getSourceCode().getAllComments();
80
+ const siblings = requireGraphQLOperations(RULE_ID, context);
81
+ const filePath = context.filename;
78
82
  return {
79
83
  "FragmentSpread > .name"(node) {
80
- const fragmentName = node.value, fragmentsFromSiblings = siblings.getFragment(fragmentName);
84
+ const fragmentName = node.value;
85
+ const fragmentsFromSiblings = siblings.getFragment(fragmentName);
81
86
  for (const comment of comments) {
82
- if (comment.type !== "Line" || !new RegExp(
87
+ if (comment.type !== "Line") continue;
88
+ const isPossibleImported = new RegExp(
83
89
  `^\\s*import\\s+(${fragmentName}\\s+from\\s+)?['"]`
84
- ).test(comment.value)) continue;
90
+ ).test(comment.value);
91
+ if (!isPossibleImported) continue;
85
92
  const extractedImportPath = comment.value.match(/(["'])((?:\1|.)*?)\1/)?.[2];
86
93
  if (!extractedImportPath) continue;
87
94
  const importPath = path.join(filePath, "..", extractedImportPath);
88
- if (fragmentsFromSiblings.some(
95
+ const hasInSiblings = fragmentsFromSiblings.some(
89
96
  (source) => source.filePath === importPath
90
- )) return;
97
+ );
98
+ if (hasInSiblings) return;
91
99
  }
92
- if (fragmentsFromSiblings.some(
100
+ const fragmentInSameFile = fragmentsFromSiblings.some(
93
101
  (source) => source.filePath === filePath
94
- )) return;
102
+ );
103
+ if (fragmentInSameFile) return;
95
104
  const suggestedFilePaths = fragmentsFromSiblings.length ? fragmentsFromSiblings.map(
96
105
  (o) => (
97
106
  // Use always forward slash for suggested import path
@@ -1,6 +1,7 @@
1
1
  import { Kind } from "graphql";
2
2
  import { getNodeName } from "../../utils.js";
3
- const RULE_ID = "require-nullable-fields-with-oneof", rule = {
3
+ const RULE_ID = "require-nullable-fields-with-oneof";
4
+ const rule = {
4
5
  meta: {
5
6
  type: "suggestion",
6
7
  docs: {
@@ -42,16 +43,22 @@ const RULE_ID = "require-nullable-fields-with-oneof", rule = {
42
43
  create(context) {
43
44
  return {
44
45
  "Directive[name.value=oneOf]"({ parent }) {
45
- if ([
46
+ const isTypeOrInput = [
46
47
  Kind.OBJECT_TYPE_DEFINITION,
47
48
  Kind.INPUT_OBJECT_TYPE_DEFINITION
48
- ].includes(parent.kind))
49
- for (const field of parent.fields || [])
50
- field.gqlType.kind === Kind.NON_NULL_TYPE && context.report({
49
+ ].includes(parent.kind);
50
+ if (!isTypeOrInput) {
51
+ return;
52
+ }
53
+ for (const field of parent.fields || []) {
54
+ if (field.gqlType.kind === Kind.NON_NULL_TYPE) {
55
+ context.report({
51
56
  node: field.name,
52
57
  messageId: RULE_ID,
53
58
  data: { nodeName: getNodeName(field) }
54
59
  });
60
+ }
61
+ }
55
62
  }
56
63
  };
57
64
  }
@@ -1,14 +1,15 @@
1
1
  import { Kind } from "graphql";
2
2
  import { getNodeName, requireGraphQLSchema } from "../../utils.js";
3
- const RULE_ID = "require-nullable-result-in-root", rule = {
3
+ const RULE_ID = "require-nullable-result-in-root";
4
+ const rule = {
4
5
  meta: {
5
6
  type: "suggestion",
6
- hasSuggestions: !0,
7
+ hasSuggestions: true,
7
8
  docs: {
8
9
  category: "Schema",
9
10
  description: "Require nullable fields in root types.",
10
11
  url: `https://the-guild.dev/graphql/eslint/rules/${RULE_ID}`,
11
- requiresSchema: !0,
12
+ requiresSchema: true,
12
13
  examples: [
13
14
  {
14
15
  title: "Incorrect",
@@ -42,34 +43,38 @@ const RULE_ID = "require-nullable-result-in-root", rule = {
42
43
  schema: []
43
44
  },
44
45
  create(context) {
45
- const schema = requireGraphQLSchema(RULE_ID, context), rootTypeNames = new Set(
46
+ const schema = requireGraphQLSchema(RULE_ID, context);
47
+ const rootTypeNames = new Set(
46
48
  [schema.getQueryType(), schema.getMutationType()].filter((v) => !!v).map((type) => type.name)
47
- ), sourceCode = context.getSourceCode();
49
+ );
50
+ const sourceCode = context.getSourceCode();
48
51
  return {
49
52
  "ObjectTypeDefinition,ObjectTypeExtension"(node) {
50
- if (rootTypeNames.has(node.name.value))
51
- for (const field of node.fields || []) {
52
- if (field.gqlType.type !== Kind.NON_NULL_TYPE || field.gqlType.gqlType.type !== Kind.NAMED_TYPE)
53
- continue;
54
- const name = field.gqlType.gqlType.name.value, type = schema.getType(name), resultType = type?.astNode ? getNodeName(type.astNode) : type?.name;
55
- context.report({
56
- node: field.gqlType,
57
- messageId: RULE_ID,
58
- data: {
59
- resultType: resultType || "",
60
- rootType: getNodeName(node)
61
- },
62
- suggest: [
63
- {
64
- desc: `Make ${resultType} nullable`,
65
- fix(fixer) {
66
- const text = sourceCode.getText(field.gqlType);
67
- return fixer.replaceText(field.gqlType, text.replace("!", ""));
68
- }
53
+ if (!rootTypeNames.has(node.name.value)) return;
54
+ for (const field of node.fields || []) {
55
+ if (field.gqlType.type !== Kind.NON_NULL_TYPE || field.gqlType.gqlType.type !== Kind.NAMED_TYPE)
56
+ continue;
57
+ const name = field.gqlType.gqlType.name.value;
58
+ const type = schema.getType(name);
59
+ const resultType = type?.astNode ? getNodeName(type.astNode) : type?.name;
60
+ context.report({
61
+ node: field.gqlType,
62
+ messageId: RULE_ID,
63
+ data: {
64
+ resultType: resultType || "",
65
+ rootType: getNodeName(node)
66
+ },
67
+ suggest: [
68
+ {
69
+ desc: `Make ${resultType} nullable`,
70
+ fix(fixer) {
71
+ const text = sourceCode.getText(field.gqlType);
72
+ return fixer.replaceText(field.gqlType, text.replace("!", ""));
69
73
  }
70
- ]
71
- });
72
- }
74
+ }
75
+ ]
76
+ });
77
+ }
73
78
  }
74
79
  };
75
80
  }
@@ -15,7 +15,9 @@ import {
15
15
  requireGraphQLOperations,
16
16
  requireGraphQLSchema
17
17
  } from "../../utils.js";
18
- const RULE_ID = "require-selections", DEFAULT_ID_FIELD_NAME = "id", schema = {
18
+ const RULE_ID = "require-selections";
19
+ const DEFAULT_ID_FIELD_NAME = "id";
20
+ const schema = {
19
21
  definitions: {
20
22
  asString: {
21
23
  type: "string"
@@ -26,7 +28,7 @@ const RULE_ID = "require-selections", DEFAULT_ID_FIELD_NAME = "id", schema = {
26
28
  maxItems: 1,
27
29
  items: {
28
30
  type: "object",
29
- additionalProperties: !1,
31
+ additionalProperties: false,
30
32
  properties: {
31
33
  fieldName: {
32
34
  oneOf: [{ $ref: "#/definitions/asString" }, { $ref: "#/definitions/asArray" }],
@@ -38,16 +40,17 @@ const RULE_ID = "require-selections", DEFAULT_ID_FIELD_NAME = "id", schema = {
38
40
  }
39
41
  }
40
42
  }
41
- }, rule = {
43
+ };
44
+ const rule = {
42
45
  meta: {
43
46
  type: "suggestion",
44
- hasSuggestions: !0,
47
+ hasSuggestions: true,
45
48
  docs: {
46
49
  category: "Operations",
47
50
  description: "Enforce selecting specific fields when they are available on the GraphQL type.",
48
51
  url: `https://the-guild.dev/graphql/eslint/rules/${RULE_ID}`,
49
- requiresSchema: !0,
50
- requiresSiblings: !0,
52
+ requiresSchema: true,
53
+ requiresSiblings: true,
51
54
  examples: [
52
55
  {
53
56
  title: "Incorrect",
@@ -98,34 +101,45 @@ const RULE_ID = "require-selections", DEFAULT_ID_FIELD_NAME = "id", schema = {
98
101
  )
99
102
  }
100
103
  ],
101
- recommended: !0,
104
+ recommended: true,
102
105
  whenNotToUseIt: "Relay Compiler automatically adds an `id` field to any type that has an `id` field, even if it hasn't been explicitly requested. Requesting a field that is not used directly in the code can conflict with another Relay rule: `relay/unused-fields`."
103
106
  },
104
107
  messages: {
105
- [RULE_ID]: `Field{{ pluralSuffix }} {{ fieldName }} must be selected when it's available on a type.
106
- Include it in your selection set{{ addition }}.`
108
+ [RULE_ID]: "Field{{ pluralSuffix }} {{ fieldName }} must be selected when it's available on a type.\nInclude it in your selection set{{ addition }}."
107
109
  },
108
110
  schema
109
111
  },
110
112
  create(context) {
111
- const schema2 = requireGraphQLSchema(RULE_ID, context), siblings = requireGraphQLOperations(RULE_ID, context), { fieldName = DEFAULT_ID_FIELD_NAME, requireAllFields } = context.options[0] || {}, idNames = asArray(fieldName), selector = "SelectionSet[parent.kind!=/(^OperationDefinition|InlineFragment)$/]", typeInfo = new TypeInfo(schema2);
113
+ const schema2 = requireGraphQLSchema(RULE_ID, context);
114
+ const siblings = requireGraphQLOperations(RULE_ID, context);
115
+ const { fieldName = DEFAULT_ID_FIELD_NAME, requireAllFields } = context.options[0] || {};
116
+ const idNames = asArray(fieldName);
117
+ const selector = "SelectionSet[parent.kind!=/(^OperationDefinition|InlineFragment)$/]";
118
+ const typeInfo = new TypeInfo(schema2);
112
119
  function checkFragments(node) {
113
120
  for (const selection of node.selections) {
114
- if (selection.kind !== Kind.FRAGMENT_SPREAD)
121
+ if (selection.kind !== Kind.FRAGMENT_SPREAD) {
115
122
  continue;
123
+ }
116
124
  const [foundSpread] = siblings.getFragment(selection.name.value);
117
- if (!foundSpread)
125
+ if (!foundSpread) {
118
126
  continue;
119
- const checkedFragmentSpreads = /* @__PURE__ */ new Set(), visitor = visitWithTypeInfo(typeInfo, {
127
+ }
128
+ const checkedFragmentSpreads = /* @__PURE__ */ new Set();
129
+ const visitor = visitWithTypeInfo(typeInfo, {
120
130
  SelectionSet(node2, key, _parent) {
121
131
  const parent = _parent;
122
- parent.kind === Kind.FRAGMENT_DEFINITION ? checkedFragmentSpreads.add(parent.name.value) : parent.kind !== Kind.INLINE_FRAGMENT && checkSelections(
123
- node2,
124
- typeInfo.getType(),
125
- selection.loc.start,
126
- parent,
127
- checkedFragmentSpreads
128
- );
132
+ if (parent.kind === Kind.FRAGMENT_DEFINITION) {
133
+ checkedFragmentSpreads.add(parent.name.value);
134
+ } else if (parent.kind !== Kind.INLINE_FRAGMENT) {
135
+ checkSelections(
136
+ node2,
137
+ typeInfo.getType(),
138
+ selection.loc.start,
139
+ parent,
140
+ checkedFragmentSpreads
141
+ );
142
+ }
129
143
  }
130
144
  });
131
145
  visit(foundSpread.document, visitor);
@@ -133,52 +147,74 @@ Include it in your selection set{{ addition }}.`
133
147
  }
134
148
  function checkSelections(node, type, loc, parent, checkedFragmentSpreads = /* @__PURE__ */ new Set()) {
135
149
  const rawType = getBaseType(type);
136
- if (rawType instanceof GraphQLObjectType || rawType instanceof GraphQLInterfaceType)
150
+ if (rawType instanceof GraphQLObjectType || rawType instanceof GraphQLInterfaceType) {
137
151
  checkFields(rawType);
138
- else if (rawType instanceof GraphQLUnionType)
152
+ } else if (rawType instanceof GraphQLUnionType) {
139
153
  for (const selection of node.selections) {
140
154
  const types = rawType.getTypes();
141
155
  if (selection.kind === Kind.INLINE_FRAGMENT) {
142
156
  const t = types.find((t2) => t2.name === selection.typeCondition.name.value);
143
- t && checkFields(t);
157
+ if (t) {
158
+ checkFields(t);
159
+ }
144
160
  } else if (selection.kind === Kind.FRAGMENT_SPREAD) {
145
161
  const [foundSpread] = siblings.getFragment(selection.name.value);
146
162
  if (!foundSpread) return;
147
- const fragmentSpread = foundSpread.document, t = fragmentSpread.typeCondition.name.value === rawType.name ? rawType : types.find((t2) => t2.name === fragmentSpread.typeCondition.name.value);
148
- checkedFragmentSpreads.add(fragmentSpread.name.value), checkSelections(fragmentSpread.selectionSet, t, loc, parent, checkedFragmentSpreads);
163
+ const fragmentSpread = foundSpread.document;
164
+ const t = fragmentSpread.typeCondition.name.value === rawType.name ? rawType : types.find((t2) => t2.name === fragmentSpread.typeCondition.name.value);
165
+ checkedFragmentSpreads.add(fragmentSpread.name.value);
166
+ checkSelections(fragmentSpread.selectionSet, t, loc, parent, checkedFragmentSpreads);
149
167
  }
150
168
  }
169
+ }
151
170
  function checkFields(rawType2) {
152
171
  const fields = rawType2.getFields();
153
- if (idNames.some((name) => fields[name]))
154
- if (checkFragments(node), requireAllFields)
155
- for (const idName of idNames)
156
- report([idName]);
157
- else
158
- report(idNames);
172
+ const hasIdFieldInType = idNames.some((name) => fields[name]);
173
+ if (!hasIdFieldInType) {
174
+ return;
175
+ }
176
+ checkFragments(node);
177
+ if (requireAllFields) {
178
+ for (const idName of idNames) {
179
+ report([idName]);
180
+ }
181
+ } else {
182
+ report(idNames);
183
+ }
159
184
  }
160
185
  function report(idNames2) {
161
186
  function hasIdField({ selections }) {
162
187
  return selections.some((selection) => {
163
- if (selection.kind === Kind.FIELD)
164
- return selection.alias && idNames2.includes(selection.alias.value) ? !0 : idNames2.includes(selection.name.value);
165
- if (selection.kind === Kind.INLINE_FRAGMENT)
188
+ if (selection.kind === Kind.FIELD) {
189
+ if (selection.alias && idNames2.includes(selection.alias.value)) {
190
+ return true;
191
+ }
192
+ return idNames2.includes(selection.name.value);
193
+ }
194
+ if (selection.kind === Kind.INLINE_FRAGMENT) {
166
195
  return hasIdField(selection.selectionSet);
196
+ }
167
197
  if (selection.kind === Kind.FRAGMENT_SPREAD) {
168
198
  const [foundSpread] = siblings.getFragment(selection.name.value);
169
199
  if (foundSpread) {
170
200
  const fragmentSpread = foundSpread.document;
171
- return checkedFragmentSpreads.add(fragmentSpread.name.value), hasIdField(fragmentSpread.selectionSet);
201
+ checkedFragmentSpreads.add(fragmentSpread.name.value);
202
+ return hasIdField(fragmentSpread.selectionSet);
172
203
  }
173
204
  }
174
- return !1;
205
+ return false;
175
206
  });
176
207
  }
177
- if (hasIdField(node))
208
+ const hasId = hasIdField(node);
209
+ if (hasId) {
178
210
  return;
211
+ }
179
212
  const fieldName2 = englishJoinWords(
180
213
  idNames2.map((name) => `\`${(parent.alias || parent.name).value}.${name}\``)
181
- ), pluralSuffix = idNames2.length > 1 ? "s" : "", addition = checkedFragmentSpreads.size === 0 ? "" : ` or add to used fragment${checkedFragmentSpreads.size > 1 ? "s" : ""} ${englishJoinWords([...checkedFragmentSpreads].map((name) => `\`${name}\``))}`, problem = {
214
+ );
215
+ const pluralSuffix = idNames2.length > 1 ? "s" : "";
216
+ const addition = checkedFragmentSpreads.size === 0 ? "" : ` or add to used fragment${checkedFragmentSpreads.size > 1 ? "s" : ""} ${englishJoinWords([...checkedFragmentSpreads].map((name) => `\`${name}\``))}`;
217
+ const problem = {
182
218
  loc,
183
219
  messageId: RULE_ID,
184
220
  data: {
@@ -187,19 +223,25 @@ Include it in your selection set{{ addition }}.`
187
223
  addition
188
224
  }
189
225
  };
190
- "type" in node && (problem.suggest = idNames2.map((idName) => ({
191
- desc: `Add \`${idName}\` selection`,
192
- fix: (fixer) => {
193
- let insertNode = node.selections[0];
194
- return insertNode = insertNode.kind === Kind.INLINE_FRAGMENT ? insertNode.selectionSet.selections[0] : insertNode, fixer.insertTextBefore(insertNode, `${idName} `);
195
- }
196
- }))), context.report(problem);
226
+ if ("type" in node) {
227
+ problem.suggest = idNames2.map((idName) => ({
228
+ desc: `Add \`${idName}\` selection`,
229
+ fix: (fixer) => {
230
+ let insertNode = node.selections[0];
231
+ insertNode = insertNode.kind === Kind.INLINE_FRAGMENT ? insertNode.selectionSet.selections[0] : insertNode;
232
+ return fixer.insertTextBefore(insertNode, `${idName} `);
233
+ }
234
+ }));
235
+ }
236
+ context.report(problem);
197
237
  }
198
238
  }
199
239
  return {
200
240
  [selector](node) {
201
241
  const typeInfo2 = node.typeInfo();
202
- typeInfo2.gqlType && checkSelections(node, typeInfo2.gqlType, node.loc.start, node.parent);
242
+ if (typeInfo2.gqlType) {
243
+ checkSelections(node, typeInfo2.gqlType, node.loc.start, node.parent);
244
+ }
203
245
  }
204
246
  };
205
247
  }