@graphql-eslint/eslint-plugin 3.14.0-alpha-20221222211539-5e993f5 → 3.14.0-alpha-20221223011223-bd3e820

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 (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,86 @@
1
+ import { TokenKind } from 'graphql';
2
+ const HASHTAG_COMMENT = 'HASHTAG_COMMENT';
3
+ export const rule = {
4
+ meta: {
5
+ type: 'suggestion',
6
+ hasSuggestions: true,
7
+ schema: [],
8
+ messages: {
9
+ [HASHTAG_COMMENT]: 'Using hashtag `#` for adding GraphQL descriptions is not allowed. Prefer using `"""` for multiline, or `"` for a single line description.',
10
+ },
11
+ docs: {
12
+ 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.',
13
+ category: 'Schema',
14
+ url: 'https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/no-hashtag-description.md',
15
+ examples: [
16
+ {
17
+ title: 'Incorrect',
18
+ code: /* GraphQL */ `
19
+ # Represents a user
20
+ type User {
21
+ id: ID!
22
+ name: String
23
+ }
24
+ `,
25
+ },
26
+ {
27
+ title: 'Correct',
28
+ code: /* GraphQL */ `
29
+ " Represents a user "
30
+ type User {
31
+ id: ID!
32
+ name: String
33
+ }
34
+ `,
35
+ },
36
+ {
37
+ title: 'Correct',
38
+ code: /* GraphQL */ `
39
+ # This file defines the basic User type.
40
+ # This comment is valid because it's not attached specifically to an AST object.
41
+
42
+ " Represents a user "
43
+ type User {
44
+ id: ID! # This one is also valid, since it comes after the AST object
45
+ name: String
46
+ }
47
+ `,
48
+ },
49
+ ],
50
+ recommended: true,
51
+ },
52
+ },
53
+ create(context) {
54
+ const selector = 'Document[definitions.0.kind!=/^(OperationDefinition|FragmentDefinition)$/]';
55
+ return {
56
+ [selector](node) {
57
+ const rawNode = node.rawNode();
58
+ let token = rawNode.loc.startToken;
59
+ while (token) {
60
+ const { kind, prev, next, value, line, column } = token;
61
+ if (kind === TokenKind.COMMENT && prev && next) {
62
+ const isEslintComment = value.trimStart().startsWith('eslint');
63
+ const linesAfter = next.line - line;
64
+ if (!isEslintComment &&
65
+ line !== prev.line &&
66
+ next.kind === TokenKind.NAME &&
67
+ linesAfter < 2) {
68
+ context.report({
69
+ messageId: HASHTAG_COMMENT,
70
+ loc: {
71
+ line,
72
+ column: column - 1,
73
+ },
74
+ suggest: ['"""', '"'].map(descriptionSyntax => ({
75
+ desc: `Replace with \`${descriptionSyntax}\` description syntax`,
76
+ fix: fixer => fixer.replaceTextRange([token.start, token.end], [descriptionSyntax, value.trim(), descriptionSyntax].join('')),
77
+ })),
78
+ });
79
+ }
80
+ }
81
+ token = next;
82
+ }
83
+ },
84
+ };
85
+ },
86
+ };
@@ -0,0 +1,80 @@
1
+ import { CWD, requireSiblingsOperations } from '../utils.js';
2
+ import { relative } from 'path';
3
+ import { visit } from 'graphql';
4
+ const RULE_ID = 'no-one-place-fragments';
5
+ export const rule = {
6
+ meta: {
7
+ type: 'suggestion',
8
+ docs: {
9
+ category: 'Operations',
10
+ description: 'Disallow fragments that are used only in one place.',
11
+ url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
12
+ examples: [
13
+ {
14
+ title: 'Incorrect',
15
+ code: /* GraphQL */ `
16
+ fragment UserFields on User {
17
+ id
18
+ }
19
+
20
+ {
21
+ user {
22
+ ...UserFields
23
+ friends {
24
+ ...UserFields
25
+ }
26
+ }
27
+ }
28
+ `,
29
+ },
30
+ {
31
+ title: 'Correct',
32
+ code: /* GraphQL */ `
33
+ fragment UserFields on User {
34
+ id
35
+ }
36
+
37
+ {
38
+ user {
39
+ ...UserFields
40
+ }
41
+ }
42
+ `,
43
+ },
44
+ ],
45
+ requiresSiblings: true,
46
+ },
47
+ messages: {
48
+ [RULE_ID]: 'Fragment `{{fragmentName}}` used only once. Inline him in "{{filePath}}".',
49
+ },
50
+ schema: [],
51
+ },
52
+ create(context) {
53
+ const operations = requireSiblingsOperations(RULE_ID, context);
54
+ const allDocuments = [...operations.getOperations(), ...operations.getFragments()];
55
+ const usedFragmentsMap = Object.create(null);
56
+ for (const { document, filePath } of allDocuments) {
57
+ const relativeFilePath = relative(CWD, filePath);
58
+ visit(document, {
59
+ FragmentSpread({ name }) {
60
+ const spreadName = name.value;
61
+ usedFragmentsMap[spreadName] || (usedFragmentsMap[spreadName] = []);
62
+ usedFragmentsMap[spreadName].push(relativeFilePath);
63
+ },
64
+ });
65
+ }
66
+ return {
67
+ 'FragmentDefinition > Name'(node) {
68
+ const fragmentName = node.value;
69
+ const fragmentUsage = usedFragmentsMap[fragmentName];
70
+ if (fragmentUsage.length === 1) {
71
+ context.report({
72
+ node,
73
+ messageId: RULE_ID,
74
+ data: { fragmentName, filePath: fragmentUsage[0] },
75
+ });
76
+ }
77
+ },
78
+ };
79
+ },
80
+ };
@@ -0,0 +1,83 @@
1
+ import { ARRAY_DEFAULT_OPTIONS, requireGraphQLSchemaFromContext } from '../utils.js';
2
+ const schema = {
3
+ type: 'array',
4
+ minItems: 1,
5
+ maxItems: 1,
6
+ items: {
7
+ type: 'object',
8
+ additionalProperties: false,
9
+ required: ['disallow'],
10
+ properties: {
11
+ disallow: {
12
+ ...ARRAY_DEFAULT_OPTIONS,
13
+ items: {
14
+ enum: ['mutation', 'subscription'],
15
+ },
16
+ },
17
+ },
18
+ },
19
+ };
20
+ export const rule = {
21
+ meta: {
22
+ type: 'suggestion',
23
+ hasSuggestions: true,
24
+ docs: {
25
+ category: 'Schema',
26
+ description: 'Disallow using root types `mutation` and/or `subscription`.',
27
+ url: 'https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/no-root-type.md',
28
+ requiresSchema: true,
29
+ isDisabledForAllConfig: true,
30
+ examples: [
31
+ {
32
+ title: 'Incorrect',
33
+ usage: [{ disallow: ['mutation', 'subscription'] }],
34
+ code: /* GraphQL */ `
35
+ type Mutation {
36
+ createUser(input: CreateUserInput!): User!
37
+ }
38
+ `,
39
+ },
40
+ {
41
+ title: 'Correct',
42
+ usage: [{ disallow: ['mutation', 'subscription'] }],
43
+ code: /* GraphQL */ `
44
+ type Query {
45
+ users: [User!]!
46
+ }
47
+ `,
48
+ },
49
+ ],
50
+ },
51
+ schema,
52
+ },
53
+ create(context) {
54
+ const schema = requireGraphQLSchemaFromContext('no-root-type', context);
55
+ const disallow = new Set(context.options[0].disallow);
56
+ const rootTypeNames = [
57
+ disallow.has('mutation') && schema.getMutationType(),
58
+ disallow.has('subscription') && schema.getSubscriptionType(),
59
+ ]
60
+ .filter(Boolean)
61
+ .map(type => type.name)
62
+ .join('|');
63
+ if (!rootTypeNames) {
64
+ return {};
65
+ }
66
+ const selector = `:matches(ObjectTypeDefinition, ObjectTypeExtension) > .name[value=/^(${rootTypeNames})$/]`;
67
+ return {
68
+ [selector](node) {
69
+ const typeName = node.value;
70
+ context.report({
71
+ node,
72
+ message: `Root type \`${typeName}\` is forbidden.`,
73
+ suggest: [
74
+ {
75
+ desc: `Remove \`${typeName}\` type`,
76
+ fix: fixer => fixer.remove(node.parent),
77
+ },
78
+ ],
79
+ });
80
+ },
81
+ };
82
+ },
83
+ };
@@ -0,0 +1,63 @@
1
+ import { isScalarType } from 'graphql';
2
+ import { requireGraphQLSchemaFromContext } from '../utils.js';
3
+ const RULE_ID = 'no-scalar-result-type-on-mutation';
4
+ export const rule = {
5
+ meta: {
6
+ type: 'suggestion',
7
+ hasSuggestions: true,
8
+ docs: {
9
+ category: 'Schema',
10
+ description: 'Avoid scalar result type on mutation type to make sure to return a valid state.',
11
+ url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
12
+ requiresSchema: true,
13
+ examples: [
14
+ {
15
+ title: 'Incorrect',
16
+ code: /* GraphQL */ `
17
+ type Mutation {
18
+ createUser: Boolean
19
+ }
20
+ `,
21
+ },
22
+ {
23
+ title: 'Correct',
24
+ code: /* GraphQL */ `
25
+ type Mutation {
26
+ createUser: User!
27
+ }
28
+ `,
29
+ },
30
+ ],
31
+ },
32
+ schema: [],
33
+ },
34
+ create(context) {
35
+ const schema = requireGraphQLSchemaFromContext(RULE_ID, context);
36
+ const mutationType = schema.getMutationType();
37
+ if (!mutationType) {
38
+ return {};
39
+ }
40
+ const selector = [
41
+ `:matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=${mutationType.name}]`,
42
+ '> FieldDefinition > .gqlType Name',
43
+ ].join(' ');
44
+ return {
45
+ [selector](node) {
46
+ const typeName = node.value;
47
+ const graphQLType = schema.getType(typeName);
48
+ if (isScalarType(graphQLType)) {
49
+ context.report({
50
+ node,
51
+ message: `Unexpected scalar result type \`${typeName}\`.`,
52
+ suggest: [
53
+ {
54
+ desc: `Remove \`${typeName}\``,
55
+ fix: fixer => fixer.remove(node),
56
+ },
57
+ ],
58
+ });
59
+ }
60
+ },
61
+ };
62
+ },
63
+ };
@@ -0,0 +1,62 @@
1
+ const NO_TYPENAME_PREFIX = 'NO_TYPENAME_PREFIX';
2
+ export const rule = {
3
+ meta: {
4
+ type: 'suggestion',
5
+ hasSuggestions: true,
6
+ docs: {
7
+ category: 'Schema',
8
+ description: 'Enforces users to avoid using the type name in a field name while defining your schema.',
9
+ recommended: true,
10
+ url: 'https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/no-typename-prefix.md',
11
+ examples: [
12
+ {
13
+ title: 'Incorrect',
14
+ code: /* GraphQL */ `
15
+ type User {
16
+ userId: ID!
17
+ }
18
+ `,
19
+ },
20
+ {
21
+ title: 'Correct',
22
+ code: /* GraphQL */ `
23
+ type User {
24
+ id: ID!
25
+ }
26
+ `,
27
+ },
28
+ ],
29
+ },
30
+ messages: {
31
+ [NO_TYPENAME_PREFIX]: 'Field "{{ fieldName }}" starts with the name of the parent type "{{ typeName }}"',
32
+ },
33
+ schema: [],
34
+ },
35
+ create(context) {
36
+ return {
37
+ 'ObjectTypeDefinition, ObjectTypeExtension, InterfaceTypeDefinition, InterfaceTypeExtension'(node) {
38
+ const typeName = node.name.value;
39
+ const lowerTypeName = typeName.toLowerCase();
40
+ for (const field of node.fields) {
41
+ const fieldName = field.name.value;
42
+ if (fieldName.toLowerCase().startsWith(lowerTypeName)) {
43
+ context.report({
44
+ data: {
45
+ fieldName,
46
+ typeName,
47
+ },
48
+ messageId: NO_TYPENAME_PREFIX,
49
+ node: field.name,
50
+ suggest: [
51
+ {
52
+ desc: `Remove \`${fieldName.slice(0, typeName.length)}\` prefix`,
53
+ fix: fixer => fixer.replaceText(field.name, fieldName.replace(new RegExp(`^${typeName}`, 'i'), '')),
54
+ },
55
+ ],
56
+ });
57
+ }
58
+ }
59
+ },
60
+ };
61
+ },
62
+ };
@@ -0,0 +1,154 @@
1
+ import { isInterfaceType, Kind, visit, DirectiveLocation, } from 'graphql';
2
+ import lowerCase from 'lodash.lowercase';
3
+ import { getTypeName, requireGraphQLSchemaFromContext } from '../utils.js';
4
+ const RULE_ID = 'no-unreachable-types';
5
+ const KINDS = [
6
+ Kind.DIRECTIVE_DEFINITION,
7
+ Kind.OBJECT_TYPE_DEFINITION,
8
+ Kind.OBJECT_TYPE_EXTENSION,
9
+ Kind.INTERFACE_TYPE_DEFINITION,
10
+ Kind.INTERFACE_TYPE_EXTENSION,
11
+ Kind.SCALAR_TYPE_DEFINITION,
12
+ Kind.SCALAR_TYPE_EXTENSION,
13
+ Kind.INPUT_OBJECT_TYPE_DEFINITION,
14
+ Kind.INPUT_OBJECT_TYPE_EXTENSION,
15
+ Kind.UNION_TYPE_DEFINITION,
16
+ Kind.UNION_TYPE_EXTENSION,
17
+ Kind.ENUM_TYPE_DEFINITION,
18
+ Kind.ENUM_TYPE_EXTENSION,
19
+ ];
20
+ let reachableTypesCache;
21
+ const RequestDirectiveLocations = new Set([
22
+ DirectiveLocation.QUERY,
23
+ DirectiveLocation.MUTATION,
24
+ DirectiveLocation.SUBSCRIPTION,
25
+ DirectiveLocation.FIELD,
26
+ DirectiveLocation.FRAGMENT_DEFINITION,
27
+ DirectiveLocation.FRAGMENT_SPREAD,
28
+ DirectiveLocation.INLINE_FRAGMENT,
29
+ DirectiveLocation.VARIABLE_DEFINITION,
30
+ ]);
31
+ function getReachableTypes(schema) {
32
+ // We don't want cache reachableTypes on test environment
33
+ // Otherwise reachableTypes will be same for all tests
34
+ if (process.env.NODE_ENV !== 'test' && reachableTypesCache) {
35
+ return reachableTypesCache;
36
+ }
37
+ const reachableTypes = new Set();
38
+ const collect = (node) => {
39
+ const typeName = getTypeName(node);
40
+ if (reachableTypes.has(typeName)) {
41
+ return;
42
+ }
43
+ reachableTypes.add(typeName);
44
+ const type = schema.getType(typeName) || schema.getDirective(typeName);
45
+ if (isInterfaceType(type)) {
46
+ const { objects, interfaces } = schema.getImplementations(type);
47
+ for (const { astNode } of [...objects, ...interfaces]) {
48
+ visit(astNode, visitor);
49
+ }
50
+ }
51
+ else if (type.astNode) {
52
+ // astNode can be undefined for ID, String, Boolean
53
+ visit(type.astNode, visitor);
54
+ }
55
+ };
56
+ const visitor = {
57
+ InterfaceTypeDefinition: collect,
58
+ ObjectTypeDefinition: collect,
59
+ InputValueDefinition: collect,
60
+ UnionTypeDefinition: collect,
61
+ FieldDefinition: collect,
62
+ Directive: collect,
63
+ NamedType: collect,
64
+ };
65
+ for (const type of [
66
+ schema,
67
+ schema.getQueryType(),
68
+ schema.getMutationType(),
69
+ schema.getSubscriptionType(),
70
+ ]) {
71
+ // if schema don't have Query type, schema.astNode will be undefined
72
+ if (type === null || type === void 0 ? void 0 : type.astNode) {
73
+ visit(type.astNode, visitor);
74
+ }
75
+ }
76
+ for (const node of schema.getDirectives()) {
77
+ if (node.locations.some(location => RequestDirectiveLocations.has(location))) {
78
+ reachableTypes.add(node.name);
79
+ }
80
+ }
81
+ reachableTypesCache = reachableTypes;
82
+ return reachableTypesCache;
83
+ }
84
+ export const rule = {
85
+ meta: {
86
+ messages: {
87
+ [RULE_ID]: '{{ type }} `{{ typeName }}` is unreachable.',
88
+ },
89
+ docs: {
90
+ description: 'Requires all types to be reachable at some level by root level fields.',
91
+ category: 'Schema',
92
+ url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
93
+ requiresSchema: true,
94
+ examples: [
95
+ {
96
+ title: 'Incorrect',
97
+ code: /* GraphQL */ `
98
+ type User {
99
+ id: ID!
100
+ name: String
101
+ }
102
+
103
+ type Query {
104
+ me: String
105
+ }
106
+ `,
107
+ },
108
+ {
109
+ title: 'Correct',
110
+ code: /* GraphQL */ `
111
+ type User {
112
+ id: ID!
113
+ name: String
114
+ }
115
+
116
+ type Query {
117
+ me: User
118
+ }
119
+ `,
120
+ },
121
+ ],
122
+ recommended: true,
123
+ },
124
+ type: 'suggestion',
125
+ schema: [],
126
+ hasSuggestions: true,
127
+ },
128
+ create(context) {
129
+ const schema = requireGraphQLSchemaFromContext(RULE_ID, context);
130
+ const reachableTypes = getReachableTypes(schema);
131
+ return {
132
+ [`:matches(${KINDS}) > .name`](node) {
133
+ const typeName = node.value;
134
+ if (!reachableTypes.has(typeName)) {
135
+ const type = lowerCase(node.parent.kind.replace(/(Extension|Definition)$/, ''));
136
+ context.report({
137
+ node,
138
+ messageId: RULE_ID,
139
+ data: {
140
+ type: type[0].toUpperCase() + type.slice(1),
141
+ typeName,
142
+ },
143
+ suggest: [
144
+ {
145
+ desc: `Remove \`${typeName}\``,
146
+ fix: fixer => fixer.remove(node.parent),
147
+ },
148
+ ],
149
+ });
150
+ }
151
+ },
152
+ };
153
+ },
154
+ };
@@ -0,0 +1,127 @@
1
+ import { TypeInfo, visit, visitWithTypeInfo } from 'graphql';
2
+ import { requireGraphQLSchemaFromContext, requireSiblingsOperations } from '../utils.js';
3
+ const RULE_ID = 'no-unused-fields';
4
+ let usedFieldsCache;
5
+ function getUsedFields(schema, operations) {
6
+ // We don't want cache usedFields on test environment
7
+ // Otherwise usedFields will be same for all tests
8
+ if (process.env.NODE_ENV !== 'test' && usedFieldsCache) {
9
+ return usedFieldsCache;
10
+ }
11
+ const usedFields = Object.create(null);
12
+ const typeInfo = new TypeInfo(schema);
13
+ const visitor = visitWithTypeInfo(typeInfo, {
14
+ Field(node) {
15
+ var _a;
16
+ const fieldDef = typeInfo.getFieldDef();
17
+ if (!fieldDef) {
18
+ // skip visiting this node if field is not defined in schema
19
+ return false;
20
+ }
21
+ const parentTypeName = typeInfo.getParentType().name;
22
+ const fieldName = node.name.value;
23
+ (_a = usedFields[parentTypeName]) !== null && _a !== void 0 ? _a : (usedFields[parentTypeName] = new Set());
24
+ usedFields[parentTypeName].add(fieldName);
25
+ },
26
+ });
27
+ const allDocuments = [...operations.getOperations(), ...operations.getFragments()];
28
+ for (const { document } of allDocuments) {
29
+ visit(document, visitor);
30
+ }
31
+ usedFieldsCache = usedFields;
32
+ return usedFieldsCache;
33
+ }
34
+ export const rule = {
35
+ meta: {
36
+ messages: {
37
+ [RULE_ID]: 'Field "{{fieldName}}" is unused',
38
+ },
39
+ docs: {
40
+ description: 'Requires all fields to be used at some level by siblings operations.',
41
+ category: 'Schema',
42
+ url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
43
+ requiresSiblings: true,
44
+ requiresSchema: true,
45
+ isDisabledForAllConfig: true,
46
+ examples: [
47
+ {
48
+ title: 'Incorrect',
49
+ code: /* GraphQL */ `
50
+ type User {
51
+ id: ID!
52
+ name: String
53
+ someUnusedField: String
54
+ }
55
+
56
+ type Query {
57
+ me: User
58
+ }
59
+
60
+ query {
61
+ me {
62
+ id
63
+ name
64
+ }
65
+ }
66
+ `,
67
+ },
68
+ {
69
+ title: 'Correct',
70
+ code: /* GraphQL */ `
71
+ type User {
72
+ id: ID!
73
+ name: String
74
+ }
75
+
76
+ type Query {
77
+ me: User
78
+ }
79
+
80
+ query {
81
+ me {
82
+ id
83
+ name
84
+ }
85
+ }
86
+ `,
87
+ },
88
+ ],
89
+ },
90
+ type: 'suggestion',
91
+ schema: [],
92
+ hasSuggestions: true,
93
+ },
94
+ create(context) {
95
+ const schema = requireGraphQLSchemaFromContext(RULE_ID, context);
96
+ const siblingsOperations = requireSiblingsOperations(RULE_ID, context);
97
+ const usedFields = getUsedFields(schema, siblingsOperations);
98
+ return {
99
+ FieldDefinition(node) {
100
+ var _a;
101
+ const fieldName = node.name.value;
102
+ const parentTypeName = node.parent.name.value;
103
+ const isUsed = (_a = usedFields[parentTypeName]) === null || _a === void 0 ? void 0 : _a.has(fieldName);
104
+ if (isUsed) {
105
+ return;
106
+ }
107
+ context.report({
108
+ node: node.name,
109
+ messageId: RULE_ID,
110
+ data: { fieldName },
111
+ suggest: [
112
+ {
113
+ desc: `Remove \`${fieldName}\` field`,
114
+ fix(fixer) {
115
+ const sourceCode = context.getSourceCode();
116
+ const tokenBefore = sourceCode.getTokenBefore(node);
117
+ const tokenAfter = sourceCode.getTokenAfter(node);
118
+ const isEmptyType = tokenBefore.type === '{' && tokenAfter.type === '}';
119
+ return fixer.remove((isEmptyType ? node.parent : node));
120
+ },
121
+ },
122
+ ],
123
+ });
124
+ },
125
+ };
126
+ },
127
+ };