@graphql-eslint/eslint-plugin 3.14.0-alpha-20221222211346-788e7eb → 3.14.0-alpha-20221223011223-bd3e820

Sign up to get free protection for your applications and to get access to all the features.
Files changed (185) hide show
  1. package/cjs/configs/index.js +10 -10
  2. package/cjs/documents.js +5 -5
  3. package/cjs/estree-converter/converter.js +2 -2
  4. package/cjs/estree-converter/index.js +3 -3
  5. package/cjs/estree-converter/utils.js +2 -2
  6. package/cjs/flat-configs.js +36 -0
  7. package/cjs/index.js +16 -14
  8. package/cjs/parser.js +13 -13
  9. package/cjs/processor.js +2 -2
  10. package/cjs/rules/alphabetize.js +7 -7
  11. package/cjs/rules/graphql-js-validation.js +9 -9
  12. package/cjs/rules/index.js +66 -66
  13. package/cjs/rules/lone-executable-definition.js +4 -4
  14. package/cjs/rules/match-document-filename.js +4 -4
  15. package/cjs/rules/naming-convention.js +6 -6
  16. package/cjs/rules/no-anonymous-operations.js +2 -2
  17. package/cjs/rules/no-deprecated.js +2 -2
  18. package/cjs/rules/no-one-place-fragments.js +3 -4
  19. package/cjs/rules/no-root-type.js +3 -3
  20. package/cjs/rules/no-scalar-result-type-on-mutation.js +2 -2
  21. package/cjs/rules/no-unreachable-types.js +3 -3
  22. package/cjs/rules/no-unused-fields.js +3 -3
  23. package/cjs/rules/relay-arguments.js +2 -2
  24. package/cjs/rules/relay-edge-types.js +6 -6
  25. package/cjs/rules/relay-page-info.js +5 -5
  26. package/cjs/rules/require-deprecation-date.js +2 -2
  27. package/cjs/rules/require-deprecation-reason.js +2 -2
  28. package/cjs/rules/require-description.js +8 -8
  29. package/cjs/rules/require-field-of-type-query-in-mutation-result.js +3 -3
  30. package/cjs/rules/require-id-when-available.js +8 -8
  31. package/cjs/rules/selection-set-depth.js +5 -5
  32. package/cjs/rules/strict-id-in-types.js +7 -7
  33. package/cjs/rules/unique-fragment-name.js +4 -4
  34. package/cjs/rules/unique-operation-name.js +2 -2
  35. package/cjs/schema.js +2 -2
  36. package/esm/cache.js +25 -0
  37. package/esm/configs/base.js +4 -0
  38. package/esm/configs/index.js +12 -0
  39. package/esm/configs/operations-all.js +29 -0
  40. package/esm/configs/operations-recommended.js +53 -0
  41. package/esm/configs/relay.js +9 -0
  42. package/esm/configs/schema-all.js +22 -0
  43. package/esm/configs/schema-recommended.js +49 -0
  44. package/esm/documents.js +144 -0
  45. package/esm/estree-converter/converter.js +58 -0
  46. package/esm/estree-converter/index.js +3 -0
  47. package/esm/estree-converter/types.js +1 -0
  48. package/esm/estree-converter/utils.js +102 -0
  49. package/esm/flat-configs.js +33 -0
  50. package/esm/graphql-config.js +49 -0
  51. package/esm/index.js +9 -0
  52. package/esm/package.json +1 -0
  53. package/esm/parser.js +56 -0
  54. package/esm/processor.js +75 -0
  55. package/esm/rules/alphabetize.js +344 -0
  56. package/esm/rules/description-style.js +75 -0
  57. package/esm/rules/graphql-js-validation.js +498 -0
  58. package/esm/rules/index.js +71 -0
  59. package/esm/rules/input-name.js +133 -0
  60. package/esm/rules/lone-executable-definition.js +85 -0
  61. package/esm/rules/match-document-filename.js +232 -0
  62. package/esm/rules/naming-convention.js +307 -0
  63. package/esm/rules/no-anonymous-operations.js +64 -0
  64. package/esm/rules/no-case-insensitive-enum-values-duplicates.js +58 -0
  65. package/esm/rules/no-deprecated.js +121 -0
  66. package/esm/rules/no-duplicate-fields.js +109 -0
  67. package/esm/rules/no-hashtag-description.js +86 -0
  68. package/esm/rules/no-one-place-fragments.js +80 -0
  69. package/esm/rules/no-root-type.js +83 -0
  70. package/esm/rules/no-scalar-result-type-on-mutation.js +63 -0
  71. package/esm/rules/no-typename-prefix.js +62 -0
  72. package/esm/rules/no-unreachable-types.js +154 -0
  73. package/esm/rules/no-unused-fields.js +127 -0
  74. package/esm/rules/relay-arguments.js +118 -0
  75. package/esm/rules/relay-connection-types.js +104 -0
  76. package/esm/rules/relay-edge-types.js +186 -0
  77. package/esm/rules/relay-page-info.js +97 -0
  78. package/esm/rules/require-deprecation-date.js +120 -0
  79. package/esm/rules/require-deprecation-reason.js +53 -0
  80. package/esm/rules/require-description.js +190 -0
  81. package/esm/rules/require-field-of-type-query-in-mutation-result.js +69 -0
  82. package/esm/rules/require-id-when-available.js +196 -0
  83. package/esm/rules/require-nullable-fields-with-oneof.js +58 -0
  84. package/esm/rules/require-type-pattern-with-oneof.js +57 -0
  85. package/esm/rules/selection-set-depth.js +131 -0
  86. package/esm/rules/strict-id-in-types.js +159 -0
  87. package/esm/rules/unique-fragment-name.js +86 -0
  88. package/esm/rules/unique-operation-name.js +62 -0
  89. package/esm/schema.js +37 -0
  90. package/esm/testkit.js +181 -0
  91. package/esm/types.js +1 -0
  92. package/esm/utils.js +83 -0
  93. package/package.json +10 -1
  94. package/typings/estree-converter/converter.d.cts +1 -1
  95. package/typings/estree-converter/converter.d.ts +1 -1
  96. package/typings/estree-converter/index.d.cts +3 -3
  97. package/typings/estree-converter/index.d.ts +3 -3
  98. package/typings/estree-converter/types.d.cts +3 -3
  99. package/typings/estree-converter/types.d.ts +3 -3
  100. package/typings/estree-converter/utils.d.cts +2 -2
  101. package/typings/estree-converter/utils.d.ts +2 -2
  102. package/typings/flat-configs.d.cts +248 -0
  103. package/typings/flat-configs.d.ts +248 -0
  104. package/typings/graphql-config.d.cts +1 -1
  105. package/typings/graphql-config.d.ts +1 -1
  106. package/typings/index.d.cts +8 -7
  107. package/typings/index.d.ts +8 -7
  108. package/typings/parser.d.cts +1 -1
  109. package/typings/parser.d.ts +1 -1
  110. package/typings/rules/alphabetize.d.cts +1 -1
  111. package/typings/rules/alphabetize.d.ts +1 -1
  112. package/typings/rules/description-style.d.cts +1 -1
  113. package/typings/rules/description-style.d.ts +1 -1
  114. package/typings/rules/graphql-js-validation.d.cts +1 -1
  115. package/typings/rules/graphql-js-validation.d.ts +1 -1
  116. package/typings/rules/index.d.cts +45 -45
  117. package/typings/rules/index.d.ts +45 -45
  118. package/typings/rules/input-name.d.cts +1 -1
  119. package/typings/rules/input-name.d.ts +1 -1
  120. package/typings/rules/lone-executable-definition.d.cts +1 -1
  121. package/typings/rules/lone-executable-definition.d.ts +1 -1
  122. package/typings/rules/match-document-filename.d.cts +2 -2
  123. package/typings/rules/match-document-filename.d.ts +2 -2
  124. package/typings/rules/naming-convention.d.cts +1 -1
  125. package/typings/rules/naming-convention.d.ts +1 -1
  126. package/typings/rules/no-anonymous-operations.d.cts +1 -1
  127. package/typings/rules/no-anonymous-operations.d.ts +1 -1
  128. package/typings/rules/no-case-insensitive-enum-values-duplicates.d.cts +1 -1
  129. package/typings/rules/no-case-insensitive-enum-values-duplicates.d.ts +1 -1
  130. package/typings/rules/no-deprecated.d.cts +1 -1
  131. package/typings/rules/no-deprecated.d.ts +1 -1
  132. package/typings/rules/no-duplicate-fields.d.cts +1 -1
  133. package/typings/rules/no-duplicate-fields.d.ts +1 -1
  134. package/typings/rules/no-hashtag-description.d.cts +1 -1
  135. package/typings/rules/no-hashtag-description.d.ts +1 -1
  136. package/typings/rules/no-one-place-fragments.d.cts +1 -1
  137. package/typings/rules/no-one-place-fragments.d.ts +1 -1
  138. package/typings/rules/no-root-type.d.cts +1 -1
  139. package/typings/rules/no-root-type.d.ts +1 -1
  140. package/typings/rules/no-scalar-result-type-on-mutation.d.cts +1 -1
  141. package/typings/rules/no-scalar-result-type-on-mutation.d.ts +1 -1
  142. package/typings/rules/no-typename-prefix.d.cts +1 -1
  143. package/typings/rules/no-typename-prefix.d.ts +1 -1
  144. package/typings/rules/no-unreachable-types.d.cts +1 -1
  145. package/typings/rules/no-unreachable-types.d.ts +1 -1
  146. package/typings/rules/no-unused-fields.d.cts +1 -1
  147. package/typings/rules/no-unused-fields.d.ts +1 -1
  148. package/typings/rules/relay-arguments.d.cts +1 -1
  149. package/typings/rules/relay-arguments.d.ts +1 -1
  150. package/typings/rules/relay-connection-types.d.cts +1 -1
  151. package/typings/rules/relay-connection-types.d.ts +1 -1
  152. package/typings/rules/relay-edge-types.d.cts +1 -1
  153. package/typings/rules/relay-edge-types.d.ts +1 -1
  154. package/typings/rules/relay-page-info.d.cts +1 -1
  155. package/typings/rules/relay-page-info.d.ts +1 -1
  156. package/typings/rules/require-deprecation-date.d.cts +1 -1
  157. package/typings/rules/require-deprecation-date.d.ts +1 -1
  158. package/typings/rules/require-deprecation-reason.d.cts +1 -1
  159. package/typings/rules/require-deprecation-reason.d.ts +1 -1
  160. package/typings/rules/require-description.d.cts +1 -1
  161. package/typings/rules/require-description.d.ts +1 -1
  162. package/typings/rules/require-field-of-type-query-in-mutation-result.d.cts +1 -1
  163. package/typings/rules/require-field-of-type-query-in-mutation-result.d.ts +1 -1
  164. package/typings/rules/require-id-when-available.d.cts +1 -1
  165. package/typings/rules/require-id-when-available.d.ts +1 -1
  166. package/typings/rules/require-nullable-fields-with-oneof.d.cts +1 -1
  167. package/typings/rules/require-nullable-fields-with-oneof.d.ts +1 -1
  168. package/typings/rules/require-type-pattern-with-oneof.d.cts +1 -1
  169. package/typings/rules/require-type-pattern-with-oneof.d.ts +1 -1
  170. package/typings/rules/selection-set-depth.d.cts +1 -1
  171. package/typings/rules/selection-set-depth.d.ts +1 -1
  172. package/typings/rules/strict-id-in-types.d.cts +1 -1
  173. package/typings/rules/strict-id-in-types.d.ts +1 -1
  174. package/typings/rules/unique-fragment-name.d.cts +2 -2
  175. package/typings/rules/unique-fragment-name.d.ts +2 -2
  176. package/typings/rules/unique-operation-name.d.cts +1 -1
  177. package/typings/rules/unique-operation-name.d.ts +1 -1
  178. package/typings/schema.d.cts +1 -1
  179. package/typings/schema.d.ts +1 -1
  180. package/typings/testkit.d.cts +3 -3
  181. package/typings/testkit.d.ts +3 -3
  182. package/typings/types.d.cts +2 -2
  183. package/typings/types.d.ts +2 -2
  184. package/typings/utils.d.cts +2 -2
  185. package/typings/utils.d.ts +2 -2
@@ -0,0 +1,190 @@
1
+ import { Kind, TokenKind } from 'graphql';
2
+ import { getLocation, requireGraphQLSchemaFromContext, TYPES_KINDS } from '../utils.js';
3
+ import { getRootTypeNames } from '@graphql-tools/utils';
4
+ const RULE_ID = 'require-description';
5
+ const ALLOWED_KINDS = [
6
+ ...TYPES_KINDS,
7
+ Kind.DIRECTIVE_DEFINITION,
8
+ Kind.FIELD_DEFINITION,
9
+ Kind.INPUT_VALUE_DEFINITION,
10
+ Kind.ENUM_VALUE_DEFINITION,
11
+ Kind.OPERATION_DEFINITION,
12
+ ];
13
+ function getNodeName(node) {
14
+ const DisplayNodeNameMap = {
15
+ [Kind.OBJECT_TYPE_DEFINITION]: 'type',
16
+ [Kind.INTERFACE_TYPE_DEFINITION]: 'interface',
17
+ [Kind.ENUM_TYPE_DEFINITION]: 'enum',
18
+ [Kind.SCALAR_TYPE_DEFINITION]: 'scalar',
19
+ [Kind.INPUT_OBJECT_TYPE_DEFINITION]: 'input',
20
+ [Kind.UNION_TYPE_DEFINITION]: 'union',
21
+ [Kind.DIRECTIVE_DEFINITION]: 'directive',
22
+ };
23
+ switch (node.kind) {
24
+ case Kind.OBJECT_TYPE_DEFINITION:
25
+ case Kind.INTERFACE_TYPE_DEFINITION:
26
+ case Kind.ENUM_TYPE_DEFINITION:
27
+ case Kind.SCALAR_TYPE_DEFINITION:
28
+ case Kind.INPUT_OBJECT_TYPE_DEFINITION:
29
+ case Kind.UNION_TYPE_DEFINITION:
30
+ return `${DisplayNodeNameMap[node.kind]} ${node.name.value}`;
31
+ case Kind.DIRECTIVE_DEFINITION:
32
+ return `${DisplayNodeNameMap[node.kind]} @${node.name.value}`;
33
+ case Kind.FIELD_DEFINITION:
34
+ case Kind.INPUT_VALUE_DEFINITION:
35
+ case Kind.ENUM_VALUE_DEFINITION:
36
+ return `${node.parent.name.value}.${node.name.value}`;
37
+ case Kind.OPERATION_DEFINITION:
38
+ return node.name ? `${node.operation} ${node.name.value}` : node.operation;
39
+ }
40
+ }
41
+ const schema = {
42
+ type: 'array',
43
+ minItems: 1,
44
+ maxItems: 1,
45
+ items: {
46
+ type: 'object',
47
+ additionalProperties: false,
48
+ minProperties: 1,
49
+ properties: {
50
+ types: {
51
+ type: 'boolean',
52
+ description: `Includes:\n${TYPES_KINDS.map(kind => `- \`${kind}\``).join('\n')}`,
53
+ },
54
+ rootField: {
55
+ type: 'boolean',
56
+ description: 'Definitions within `Query`, `Mutation`, and `Subscription` root types.',
57
+ },
58
+ ...Object.fromEntries([...ALLOWED_KINDS].sort().map(kind => {
59
+ let description = `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`;
60
+ if (kind === Kind.OPERATION_DEFINITION) {
61
+ description +=
62
+ '\n> You must use only comment syntax `#` and not description syntax `"""` or `"`.';
63
+ }
64
+ return [kind, { type: 'boolean', description }];
65
+ })),
66
+ },
67
+ },
68
+ };
69
+ export const rule = {
70
+ meta: {
71
+ docs: {
72
+ category: 'Schema',
73
+ description: 'Enforce descriptions in type definitions and operations.',
74
+ url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
75
+ examples: [
76
+ {
77
+ title: 'Incorrect',
78
+ usage: [{ types: true, FieldDefinition: true }],
79
+ code: /* GraphQL */ `
80
+ type someTypeName {
81
+ name: String
82
+ }
83
+ `,
84
+ },
85
+ {
86
+ title: 'Correct',
87
+ usage: [{ types: true, FieldDefinition: true }],
88
+ code: /* GraphQL */ `
89
+ """
90
+ Some type description
91
+ """
92
+ type someTypeName {
93
+ """
94
+ Name description
95
+ """
96
+ name: String
97
+ }
98
+ `,
99
+ },
100
+ {
101
+ title: 'Correct',
102
+ usage: [{ OperationDefinition: true }],
103
+ code: /* GraphQL */ `
104
+ # Create a new user
105
+ mutation createUser {
106
+ # ...
107
+ }
108
+ `,
109
+ },
110
+ {
111
+ title: 'Correct',
112
+ usage: [{ rootField: true }],
113
+ code: /* GraphQL */ `
114
+ type Mutation {
115
+ "Create a new user"
116
+ createUser: User
117
+ }
118
+
119
+ type User {
120
+ name: String
121
+ }
122
+ `,
123
+ },
124
+ ],
125
+ configOptions: [
126
+ {
127
+ types: true,
128
+ [Kind.DIRECTIVE_DEFINITION]: true,
129
+ // rootField: true TODO enable in graphql-eslint v4
130
+ },
131
+ ],
132
+ recommended: true,
133
+ },
134
+ type: 'suggestion',
135
+ messages: {
136
+ [RULE_ID]: 'Description is required for `{{ nodeName }}`.',
137
+ },
138
+ schema,
139
+ },
140
+ create(context) {
141
+ const { types, rootField, ...restOptions } = context.options[0] || {};
142
+ const kinds = new Set(types ? TYPES_KINDS : []);
143
+ for (const [kind, isEnabled] of Object.entries(restOptions)) {
144
+ if (isEnabled) {
145
+ kinds.add(kind);
146
+ }
147
+ else {
148
+ kinds.delete(kind);
149
+ }
150
+ }
151
+ if (rootField) {
152
+ const schema = requireGraphQLSchemaFromContext(RULE_ID, context);
153
+ const rootTypeNames = getRootTypeNames(schema);
154
+ kinds.add(`:matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=/^(${[
155
+ ...rootTypeNames,
156
+ ].join(',')})$/] > FieldDefinition`);
157
+ }
158
+ const selector = [...kinds].join(',');
159
+ return {
160
+ [selector](node) {
161
+ var _a;
162
+ let description = '';
163
+ const isOperation = node.kind === Kind.OPERATION_DEFINITION;
164
+ if (isOperation) {
165
+ const rawNode = node.rawNode();
166
+ const { prev, line } = rawNode.loc.startToken;
167
+ if (prev.kind === TokenKind.COMMENT) {
168
+ const value = prev.value.trim();
169
+ const linesBefore = line - prev.line;
170
+ if (!value.startsWith('eslint') && linesBefore === 1) {
171
+ description = value;
172
+ }
173
+ }
174
+ }
175
+ else {
176
+ description = ((_a = node.description) === null || _a === void 0 ? void 0 : _a.value.trim()) || '';
177
+ }
178
+ if (description.length === 0) {
179
+ context.report({
180
+ loc: isOperation ? getLocation(node.loc.start, node.operation) : node.name.loc,
181
+ messageId: RULE_ID,
182
+ data: {
183
+ nodeName: getNodeName(node),
184
+ },
185
+ });
186
+ }
187
+ },
188
+ };
189
+ },
190
+ };
@@ -0,0 +1,69 @@
1
+ import { isObjectType } from 'graphql';
2
+ import { requireGraphQLSchemaFromContext, getTypeName } from '../utils.js';
3
+ const RULE_ID = 'require-field-of-type-query-in-mutation-result';
4
+ export const rule = {
5
+ meta: {
6
+ type: 'suggestion',
7
+ docs: {
8
+ category: 'Schema',
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`.',
10
+ url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
11
+ requiresSchema: true,
12
+ examples: [
13
+ {
14
+ title: 'Incorrect',
15
+ code: /* GraphQL */ `
16
+ type User { ... }
17
+
18
+ type Mutation {
19
+ createUser: User!
20
+ }
21
+ `,
22
+ },
23
+ {
24
+ title: 'Correct',
25
+ code: /* GraphQL */ `
26
+ type User { ... }
27
+
28
+ type Query { ... }
29
+
30
+ type CreateUserPayload {
31
+ user: User!
32
+ query: Query!
33
+ }
34
+
35
+ type Mutation {
36
+ createUser: CreateUserPayload!
37
+ }
38
+ `,
39
+ },
40
+ ],
41
+ },
42
+ schema: [],
43
+ },
44
+ create(context) {
45
+ const schema = requireGraphQLSchemaFromContext(RULE_ID, context);
46
+ const mutationType = schema.getMutationType();
47
+ const queryType = schema.getQueryType();
48
+ if (!mutationType || !queryType) {
49
+ return {};
50
+ }
51
+ const selector = `:matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=${mutationType.name}] > FieldDefinition > .gqlType Name`;
52
+ return {
53
+ [selector](node) {
54
+ const typeName = node.value;
55
+ const graphQLType = schema.getType(typeName);
56
+ if (isObjectType(graphQLType)) {
57
+ const { fields } = graphQLType.astNode;
58
+ const hasQueryType = fields.some(field => getTypeName(field) === queryType.name);
59
+ if (!hasQueryType) {
60
+ context.report({
61
+ node,
62
+ message: `Mutation result type "${graphQLType.name}" must contain field of type "${queryType.name}"`,
63
+ });
64
+ }
65
+ }
66
+ },
67
+ };
68
+ },
69
+ };
@@ -0,0 +1,196 @@
1
+ import { GraphQLInterfaceType, GraphQLObjectType, Kind, TypeInfo, visit, visitWithTypeInfo, } from 'graphql';
2
+ import { asArray } from '@graphql-tools/utils';
3
+ import { ARRAY_DEFAULT_OPTIONS, requireGraphQLSchemaFromContext, requireSiblingsOperations, englishJoinWords, } from '../utils.js';
4
+ import { getBaseType } from '../estree-converter/index.js';
5
+ const RULE_ID = 'require-id-when-available';
6
+ const DEFAULT_ID_FIELD_NAME = 'id';
7
+ const schema = {
8
+ definitions: {
9
+ asString: {
10
+ type: 'string',
11
+ },
12
+ asArray: ARRAY_DEFAULT_OPTIONS,
13
+ },
14
+ type: 'array',
15
+ maxItems: 1,
16
+ items: {
17
+ type: 'object',
18
+ additionalProperties: false,
19
+ properties: {
20
+ fieldName: {
21
+ oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asArray' }],
22
+ default: DEFAULT_ID_FIELD_NAME,
23
+ },
24
+ },
25
+ },
26
+ };
27
+ export const rule = {
28
+ meta: {
29
+ type: 'suggestion',
30
+ hasSuggestions: true,
31
+ docs: {
32
+ category: 'Operations',
33
+ description: 'Enforce selecting specific fields when they are available on the GraphQL type.',
34
+ url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
35
+ requiresSchema: true,
36
+ requiresSiblings: true,
37
+ examples: [
38
+ {
39
+ title: 'Incorrect',
40
+ code: /* GraphQL */ `
41
+ # In your schema
42
+ type User {
43
+ id: ID!
44
+ name: String!
45
+ }
46
+
47
+ # Query
48
+ query {
49
+ user {
50
+ name
51
+ }
52
+ }
53
+ `,
54
+ },
55
+ {
56
+ title: 'Correct',
57
+ code: /* GraphQL */ `
58
+ # In your schema
59
+ type User {
60
+ id: ID!
61
+ name: String!
62
+ }
63
+
64
+ # Query
65
+ query {
66
+ user {
67
+ id
68
+ name
69
+ }
70
+ }
71
+
72
+ # Selecting \`id\` with an alias is also valid
73
+ query {
74
+ user {
75
+ id: name
76
+ }
77
+ }
78
+ `,
79
+ },
80
+ ],
81
+ recommended: true,
82
+ },
83
+ messages: {
84
+ [RULE_ID]: "Field{{ pluralSuffix }} {{ fieldName }} must be selected when it's available on a type.\nInclude it in your selection set{{ addition }}.",
85
+ },
86
+ schema,
87
+ },
88
+ create(context) {
89
+ const schema = requireGraphQLSchemaFromContext(RULE_ID, context);
90
+ const siblings = requireSiblingsOperations(RULE_ID, context);
91
+ const { fieldName = DEFAULT_ID_FIELD_NAME } = context.options[0] || {};
92
+ const idNames = asArray(fieldName);
93
+ // Check selections only in OperationDefinition,
94
+ // skip selections of OperationDefinition and InlineFragment
95
+ const selector = 'OperationDefinition SelectionSet[parent.kind!=/(^OperationDefinition|InlineFragment)$/]';
96
+ const typeInfo = new TypeInfo(schema);
97
+ function checkFragments(node) {
98
+ for (const selection of node.selections) {
99
+ if (selection.kind !== Kind.FRAGMENT_SPREAD) {
100
+ continue;
101
+ }
102
+ const [foundSpread] = siblings.getFragment(selection.name.value);
103
+ if (!foundSpread) {
104
+ continue;
105
+ }
106
+ const checkedFragmentSpreads = new Set();
107
+ const visitor = visitWithTypeInfo(typeInfo, {
108
+ SelectionSet(node, key, parent) {
109
+ if (parent.kind === Kind.FRAGMENT_DEFINITION) {
110
+ checkedFragmentSpreads.add(parent.name.value);
111
+ }
112
+ else if (parent.kind !== Kind.INLINE_FRAGMENT) {
113
+ checkSelections(node, typeInfo.getType(), selection.loc.start, parent, checkedFragmentSpreads);
114
+ }
115
+ },
116
+ });
117
+ visit(foundSpread.document, visitor);
118
+ }
119
+ }
120
+ function checkSelections(node, type,
121
+ // Fragment can be placed in separate file
122
+ // Provide actual fragment spread location instead of location in fragment
123
+ loc,
124
+ // Can't access to node.parent in GraphQL AST.Node, so pass as argument
125
+ parent, checkedFragmentSpreads = new Set()) {
126
+ const rawType = getBaseType(type);
127
+ const isObjectType = rawType instanceof GraphQLObjectType;
128
+ const isInterfaceType = rawType instanceof GraphQLInterfaceType;
129
+ if (!isObjectType && !isInterfaceType) {
130
+ return;
131
+ }
132
+ const fields = rawType.getFields();
133
+ const hasIdFieldInType = idNames.some(name => fields[name]);
134
+ if (!hasIdFieldInType) {
135
+ return;
136
+ }
137
+ function hasIdField({ selections }) {
138
+ return selections.some(selection => {
139
+ if (selection.kind === Kind.FIELD) {
140
+ if (selection.alias && idNames.includes(selection.alias.value)) {
141
+ return true;
142
+ }
143
+ return idNames.includes(selection.name.value);
144
+ }
145
+ if (selection.kind === Kind.INLINE_FRAGMENT) {
146
+ return hasIdField(selection.selectionSet);
147
+ }
148
+ if (selection.kind === Kind.FRAGMENT_SPREAD) {
149
+ const [foundSpread] = siblings.getFragment(selection.name.value);
150
+ if (foundSpread) {
151
+ const fragmentSpread = foundSpread.document;
152
+ checkedFragmentSpreads.add(fragmentSpread.name.value);
153
+ return hasIdField(fragmentSpread.selectionSet);
154
+ }
155
+ }
156
+ return false;
157
+ });
158
+ }
159
+ const hasId = hasIdField(node);
160
+ checkFragments(node);
161
+ if (hasId) {
162
+ return;
163
+ }
164
+ const pluralSuffix = idNames.length > 1 ? 's' : '';
165
+ const fieldName = englishJoinWords(idNames.map(name => `\`${(parent.alias || parent.name).value}.${name}\``));
166
+ const addition = checkedFragmentSpreads.size === 0
167
+ ? ''
168
+ : ` or add to used fragment${checkedFragmentSpreads.size > 1 ? 's' : ''} ${englishJoinWords([...checkedFragmentSpreads].map(name => `\`${name}\``))}`;
169
+ const problem = {
170
+ loc,
171
+ messageId: RULE_ID,
172
+ data: {
173
+ pluralSuffix,
174
+ fieldName,
175
+ addition,
176
+ },
177
+ };
178
+ // Don't provide suggestions for selections in fragments as fragment can be in a separate file
179
+ if ('type' in node) {
180
+ problem.suggest = idNames.map(idName => ({
181
+ desc: `Add \`${idName}\` selection`,
182
+ fix: fixer => fixer.insertTextBefore(node.selections[0], `${idName} `),
183
+ }));
184
+ }
185
+ context.report(problem);
186
+ }
187
+ return {
188
+ [selector](node) {
189
+ const typeInfo = node.typeInfo();
190
+ if (typeInfo.gqlType) {
191
+ checkSelections(node, typeInfo.gqlType, node.loc.start, node.parent);
192
+ }
193
+ },
194
+ };
195
+ },
196
+ };
@@ -0,0 +1,58 @@
1
+ import { Kind } from 'graphql';
2
+ const RULE_ID = 'require-nullable-fields-with-oneof';
3
+ export const rule = {
4
+ meta: {
5
+ type: 'suggestion',
6
+ docs: {
7
+ category: 'Schema',
8
+ description: 'Require are `input` or `type` fields be non nullable with `@oneOf` directive.',
9
+ url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
10
+ examples: [
11
+ {
12
+ title: 'Incorrect',
13
+ code: /* GraphQL */ `
14
+ input Input @oneOf {
15
+ foo: String!
16
+ b: Int
17
+ }
18
+ `,
19
+ },
20
+ {
21
+ title: 'Correct',
22
+ code: /* GraphQL */ `
23
+ input Input @oneOf {
24
+ foo: String
25
+ bar: Int
26
+ }
27
+ `,
28
+ },
29
+ ],
30
+ },
31
+ messages: {
32
+ [RULE_ID]: 'Field `{{fieldName}}` must be nullable.',
33
+ },
34
+ schema: [],
35
+ },
36
+ create(context) {
37
+ return {
38
+ 'Directive[name.value=oneOf]'({ parent, }) {
39
+ const isTypeOrInput = [
40
+ Kind.OBJECT_TYPE_DEFINITION,
41
+ Kind.INPUT_OBJECT_TYPE_DEFINITION,
42
+ ].includes(parent.kind);
43
+ if (!isTypeOrInput) {
44
+ return;
45
+ }
46
+ for (const field of parent.fields) {
47
+ if (field.gqlType.kind === Kind.NON_NULL_TYPE) {
48
+ context.report({
49
+ node: field.name,
50
+ messageId: RULE_ID,
51
+ data: { fieldName: field.name.value },
52
+ });
53
+ }
54
+ }
55
+ },
56
+ };
57
+ },
58
+ };
@@ -0,0 +1,57 @@
1
+ const RULE_ID = 'require-type-pattern-with-oneof';
2
+ export const rule = {
3
+ meta: {
4
+ type: 'suggestion',
5
+ docs: {
6
+ category: 'Schema',
7
+ description: 'Enforce types with `@oneOf` directive have `error` and `ok` fields.',
8
+ url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
9
+ examples: [
10
+ {
11
+ title: 'Correct',
12
+ code: /* GraphQL */ `
13
+ type Mutation {
14
+ doSomething: DoSomethingMutationResult!
15
+ }
16
+
17
+ interface Error {
18
+ message: String!
19
+ }
20
+
21
+ type DoSomethingMutationResult @oneOf {
22
+ ok: DoSomethingSuccess
23
+ error: Error
24
+ }
25
+
26
+ type DoSomethingSuccess {
27
+ # ...
28
+ }
29
+ `,
30
+ },
31
+ ],
32
+ },
33
+ messages: {
34
+ [RULE_ID]: 'Type `{{typeName}}` should have `{{fieldName}}` field.',
35
+ },
36
+ schema: [],
37
+ },
38
+ create(context) {
39
+ return {
40
+ 'Directive[name.value=oneOf][parent.kind=ObjectTypeDefinition]'({ parent, }) {
41
+ const requiredFields = ['error', 'ok'];
42
+ for (const fieldName of requiredFields) {
43
+ if (!parent.fields.some(field => field.name.value === fieldName)) {
44
+ context.report({
45
+ node: parent.name,
46
+ messageId: RULE_ID,
47
+ data: {
48
+ typeName: parent.name.value,
49
+ fieldName,
50
+ },
51
+ });
52
+ }
53
+ }
54
+ },
55
+ };
56
+ },
57
+ };