@graphql-eslint/eslint-plugin 2.1.0-alpha-b4cd82d.0 → 2.3.0-alpha-6ba4002.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.
- package/README.md +38 -18
- package/configs/all.d.ts +61 -0
- package/configs/index.d.ts +107 -0
- package/configs/recommended.d.ts +44 -0
- package/docs/README.md +64 -60
- package/docs/rules/avoid-scalar-result-type-on-mutation.md +30 -0
- package/docs/rules/avoid-typename-prefix.md +2 -0
- package/docs/rules/executable-definitions.md +3 -1
- package/docs/rules/fields-on-correct-type.md +3 -1
- package/docs/rules/fragments-on-composite-type.md +3 -1
- package/docs/rules/known-argument-names.md +3 -1
- package/docs/rules/known-directives.md +3 -1
- package/docs/rules/known-fragment-names.md +3 -1
- package/docs/rules/known-type-names.md +3 -1
- package/docs/rules/lone-anonymous-operation.md +3 -1
- package/docs/rules/lone-schema-definition.md +3 -1
- package/docs/rules/match-document-filename.md +1 -1
- package/docs/rules/naming-convention.md +2 -0
- package/docs/rules/no-anonymous-operations.md +2 -0
- package/docs/rules/no-case-insensitive-enum-values-duplicates.md +5 -1
- package/docs/rules/no-fragment-cycles.md +3 -1
- package/docs/rules/no-operation-name-suffix.md +4 -0
- package/docs/rules/no-undefined-variables.md +3 -1
- package/docs/rules/no-unreachable-types.md +2 -0
- package/docs/rules/no-unused-fields.md +2 -0
- package/docs/rules/no-unused-fragments.md +3 -1
- package/docs/rules/no-unused-variables.md +3 -1
- package/docs/rules/one-field-subscriptions.md +3 -1
- package/docs/rules/overlapping-fields-can-be-merged.md +3 -1
- package/docs/rules/possible-fragment-spread.md +3 -1
- package/docs/rules/possible-type-extension.md +3 -1
- package/docs/rules/provided-required-arguments.md +3 -1
- package/docs/rules/require-deprecation-date.md +53 -0
- package/docs/rules/require-deprecation-reason.md +2 -0
- package/docs/rules/require-field-of-type-query-in-mutation-result.md +42 -0
- package/docs/rules/scalar-leafs.md +3 -1
- package/docs/rules/strict-id-in-types.md +2 -0
- package/docs/rules/unique-argument-names.md +3 -1
- package/docs/rules/unique-directive-names-per-location.md +3 -1
- package/docs/rules/unique-directive-names.md +3 -1
- package/docs/rules/unique-enum-value-names.md +3 -1
- package/docs/rules/unique-field-definition-names.md +3 -1
- package/docs/rules/unique-input-field-names.md +3 -1
- package/docs/rules/unique-operation-types.md +3 -1
- package/docs/rules/unique-type-names.md +3 -1
- package/docs/rules/unique-variable-names.md +3 -1
- package/docs/rules/value-literals-of-correct-type.md +3 -1
- package/docs/rules/variables-are-input-types.md +3 -1
- package/docs/rules/variables-in-allowed-position.md +3 -1
- package/index.d.ts +1 -0
- package/index.js +389 -74
- package/index.mjs +390 -76
- package/package.json +1 -1
- package/rules/avoid-scalar-result-type-on-mutation.d.ts +3 -0
- package/rules/index.d.ts +5 -0
- package/rules/require-deprecation-date.d.ts +5 -0
- package/rules/require-field-of-type-query-in-mutation-result.d.ts +3 -0
- package/utils.d.ts +9 -4
package/index.js
CHANGED
@@ -16,6 +16,86 @@ const graphqlTagPluck = require('@graphql-tools/graphql-tag-pluck');
|
|
16
16
|
const graphqlConfig$1 = require('graphql-config');
|
17
17
|
const codeFileLoader = require('@graphql-tools/code-file-loader');
|
18
18
|
|
19
|
+
/*
|
20
|
+
* 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
|
21
|
+
*/
|
22
|
+
const recommendedConfig = {
|
23
|
+
parser: '@graphql-eslint/eslint-plugin',
|
24
|
+
plugins: ['@graphql-eslint'],
|
25
|
+
rules: {
|
26
|
+
'@graphql-eslint/avoid-typename-prefix': 'error',
|
27
|
+
'@graphql-eslint/executable-definitions': 'error',
|
28
|
+
'@graphql-eslint/fields-on-correct-type': 'error',
|
29
|
+
'@graphql-eslint/fragments-on-composite-type': 'error',
|
30
|
+
'@graphql-eslint/known-argument-names': 'error',
|
31
|
+
'@graphql-eslint/known-directives': 'error',
|
32
|
+
'@graphql-eslint/known-fragment-names': 'error',
|
33
|
+
'@graphql-eslint/known-type-names': 'error',
|
34
|
+
'@graphql-eslint/lone-anonymous-operation': 'error',
|
35
|
+
'@graphql-eslint/lone-schema-definition': 'error',
|
36
|
+
'@graphql-eslint/naming-convention': 'error',
|
37
|
+
'@graphql-eslint/no-anonymous-operations': 'error',
|
38
|
+
'@graphql-eslint/no-case-insensitive-enum-values-duplicates': 'error',
|
39
|
+
'@graphql-eslint/no-fragment-cycles': 'error',
|
40
|
+
'@graphql-eslint/no-operation-name-suffix': 'error',
|
41
|
+
'@graphql-eslint/no-undefined-variables': 'error',
|
42
|
+
'@graphql-eslint/no-unused-fragments': 'error',
|
43
|
+
'@graphql-eslint/no-unused-variables': 'error',
|
44
|
+
'@graphql-eslint/one-field-subscriptions': 'error',
|
45
|
+
'@graphql-eslint/overlapping-fields-can-be-merged': 'error',
|
46
|
+
'@graphql-eslint/possible-fragment-spread': 'error',
|
47
|
+
'@graphql-eslint/possible-type-extension': 'error',
|
48
|
+
'@graphql-eslint/provided-required-arguments': 'error',
|
49
|
+
'@graphql-eslint/require-deprecation-reason': 'error',
|
50
|
+
'@graphql-eslint/scalar-leafs': 'error',
|
51
|
+
'@graphql-eslint/strict-id-in-types': 'error',
|
52
|
+
'@graphql-eslint/unique-argument-names': 'error',
|
53
|
+
'@graphql-eslint/unique-directive-names': 'error',
|
54
|
+
'@graphql-eslint/unique-directive-names-per-location': 'error',
|
55
|
+
'@graphql-eslint/unique-enum-value-names': 'error',
|
56
|
+
'@graphql-eslint/unique-field-definition-names': 'error',
|
57
|
+
'@graphql-eslint/unique-input-field-names': 'error',
|
58
|
+
'@graphql-eslint/unique-operation-types': 'error',
|
59
|
+
'@graphql-eslint/unique-type-names': 'error',
|
60
|
+
'@graphql-eslint/unique-variable-names': 'error',
|
61
|
+
'@graphql-eslint/value-literals-of-correct-type': 'error',
|
62
|
+
'@graphql-eslint/variables-are-input-types': 'error',
|
63
|
+
'@graphql-eslint/variables-in-allowed-position': 'error',
|
64
|
+
},
|
65
|
+
};
|
66
|
+
|
67
|
+
/*
|
68
|
+
* 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
|
69
|
+
*/
|
70
|
+
const allConfig = {
|
71
|
+
...recommendedConfig,
|
72
|
+
rules: {
|
73
|
+
...recommendedConfig.rules,
|
74
|
+
'@graphql-eslint/avoid-duplicate-fields': 'error',
|
75
|
+
'@graphql-eslint/avoid-operation-name-prefix': 'error',
|
76
|
+
'@graphql-eslint/avoid-scalar-result-type-on-mutation': 'error',
|
77
|
+
'@graphql-eslint/description-style': 'error',
|
78
|
+
'@graphql-eslint/input-name': 'error',
|
79
|
+
'@graphql-eslint/match-document-filename': 'error',
|
80
|
+
'@graphql-eslint/no-deprecated': 'error',
|
81
|
+
'@graphql-eslint/no-hashtag-description': 'error',
|
82
|
+
'@graphql-eslint/no-unreachable-types': 'error',
|
83
|
+
'@graphql-eslint/no-unused-fields': 'error',
|
84
|
+
'@graphql-eslint/require-deprecation-date': 'error',
|
85
|
+
'@graphql-eslint/require-description': 'error',
|
86
|
+
'@graphql-eslint/require-field-of-type-query-in-mutation-result': 'error',
|
87
|
+
'@graphql-eslint/require-id-when-available': 'error',
|
88
|
+
'@graphql-eslint/selection-set-depth': 'error',
|
89
|
+
'@graphql-eslint/unique-fragment-name': 'error',
|
90
|
+
'@graphql-eslint/unique-operation-name': 'error',
|
91
|
+
},
|
92
|
+
};
|
93
|
+
|
94
|
+
const configs = {
|
95
|
+
all: allConfig,
|
96
|
+
recommended: recommendedConfig,
|
97
|
+
};
|
98
|
+
|
19
99
|
function requireSiblingsOperations(ruleName, context) {
|
20
100
|
if (!context.parserServices) {
|
21
101
|
throw new Error(`Rule '${ruleName}' requires 'parserOptions.operations' to be set and loaded. See http://bit.ly/graphql-eslint-operations for more info`);
|
@@ -101,6 +181,7 @@ const getOnDiskFilepath = (filepath) => {
|
|
101
181
|
}
|
102
182
|
return filepath;
|
103
183
|
};
|
184
|
+
const getTypeName = node => ('type' in node ? getTypeName(node.type) : node.name.value);
|
104
185
|
// Small workaround for the bug in older versions of @graphql-tools/load
|
105
186
|
// Can be removed after graphql-config bumps to a new version
|
106
187
|
const loaderCache = new Proxy(Object.create(null), {
|
@@ -118,7 +199,7 @@ const loaderCache = new Proxy(Object.create(null), {
|
|
118
199
|
return true;
|
119
200
|
},
|
120
201
|
});
|
121
|
-
const isObjectType = (node) => [
|
202
|
+
const isObjectType = (node) => [graphql.Kind.OBJECT_TYPE_DEFINITION, graphql.Kind.OBJECT_TYPE_EXTENSION].includes(node.type);
|
122
203
|
const isQueryType = (node) => isObjectType(node) && node.name.value === 'Query';
|
123
204
|
const isMutationType = (node) => isObjectType(node) && node.name.value === 'Mutation';
|
124
205
|
var CaseStyle;
|
@@ -129,16 +210,18 @@ var CaseStyle;
|
|
129
210
|
CaseStyle["upperCase"] = "UPPER_CASE";
|
130
211
|
CaseStyle["kebabCase"] = "kebab-case";
|
131
212
|
})(CaseStyle || (CaseStyle = {}));
|
213
|
+
const pascalCase = (str) => lowerCase(str)
|
214
|
+
.split(' ')
|
215
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
216
|
+
.join('');
|
217
|
+
const camelCase = (str) => {
|
218
|
+
const result = pascalCase(str);
|
219
|
+
return result.charAt(0).toLowerCase() + result.slice(1);
|
220
|
+
};
|
132
221
|
const convertCase = (style, str) => {
|
133
|
-
const pascalCase = (str) => lowerCase(str)
|
134
|
-
.split(' ')
|
135
|
-
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
136
|
-
.join('');
|
137
222
|
switch (style) {
|
138
|
-
case CaseStyle.camelCase:
|
139
|
-
|
140
|
-
return result.charAt(0).toLowerCase() + result.slice(1);
|
141
|
-
}
|
223
|
+
case CaseStyle.camelCase:
|
224
|
+
return camelCase(str);
|
142
225
|
case CaseStyle.pascalCase:
|
143
226
|
return pascalCase(str);
|
144
227
|
case CaseStyle.snakeCase:
|
@@ -200,9 +283,10 @@ const validationToRule = (name, ruleName, docs, getDocumentNode) => {
|
|
200
283
|
docs: {
|
201
284
|
...docs,
|
202
285
|
category: 'Validation',
|
286
|
+
recommended: true,
|
203
287
|
requiresSchema,
|
204
288
|
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${name}.md`,
|
205
|
-
description: `${docs.description}\n\n> This rule is a wrapper around a \`graphql-js\` validation function. [You can find
|
289
|
+
description: `${docs.description}\n\n> This rule is a wrapper around a \`graphql-js\` validation function. [You can find its source code here](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/${ruleName}Rule.ts).`,
|
206
290
|
},
|
207
291
|
},
|
208
292
|
create(context) {
|
@@ -226,6 +310,14 @@ const validationToRule = (name, ruleName, docs, getDocumentNode) => {
|
|
226
310
|
},
|
227
311
|
};
|
228
312
|
};
|
313
|
+
const importFiles = (context) => {
|
314
|
+
const code = context.getSourceCode().text;
|
315
|
+
if (!isGraphQLImportFile(code)) {
|
316
|
+
return null;
|
317
|
+
}
|
318
|
+
// Import documents because file contains '#import' comments
|
319
|
+
return _import.processImport(context.getFilename());
|
320
|
+
};
|
229
321
|
const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-definitions', 'ExecutableDefinitions', {
|
230
322
|
description: `A GraphQL document is only valid for execution if all definitions are either operation or fragment definitions.`,
|
231
323
|
}), validationToRule('fields-on-correct-type', 'FieldsOnCorrectType', {
|
@@ -302,14 +394,7 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
|
|
302
394
|
\``,
|
303
395
|
},
|
304
396
|
],
|
305
|
-
},
|
306
|
-
const code = context.getSourceCode().text;
|
307
|
-
if (!isGraphQLImportFile(code)) {
|
308
|
-
return null;
|
309
|
-
}
|
310
|
-
// Import documents because file contains '#import' comments
|
311
|
-
return _import.processImport(context.getFilename());
|
312
|
-
}), validationToRule('known-type-names', 'KnownTypeNames', {
|
397
|
+
}, importFiles), validationToRule('known-type-names', 'KnownTypeNames', {
|
313
398
|
description: `A GraphQL document is only valid if referenced types (specifically variable definitions and fragment conditions) are defined by the type schema.`,
|
314
399
|
}), validationToRule('lone-anonymous-operation', 'LoneAnonymousOperation', {
|
315
400
|
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.`,
|
@@ -351,7 +436,7 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
|
|
351
436
|
return getParentNode(context.getFilename());
|
352
437
|
}), validationToRule('no-unused-variables', 'NoUnusedVariables', {
|
353
438
|
description: `A GraphQL operation is only valid if all variables defined by an operation are used, either directly or within a spread fragment.`,
|
354
|
-
}), validationToRule('overlapping-fields-can-be-merged', 'OverlappingFieldsCanBeMerged', {
|
439
|
+
}, importFiles), validationToRule('overlapping-fields-can-be-merged', 'OverlappingFieldsCanBeMerged', {
|
355
440
|
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.`,
|
356
441
|
}), validationToRule('possible-fragment-spread', 'PossibleFragmentSpreads', {
|
357
442
|
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.`,
|
@@ -604,8 +689,59 @@ const rule$1 = {
|
|
604
689
|
},
|
605
690
|
};
|
606
691
|
|
607
|
-
const AVOID_TYPENAME_PREFIX = 'AVOID_TYPENAME_PREFIX';
|
608
692
|
const rule$2 = {
|
693
|
+
meta: {
|
694
|
+
type: 'suggestion',
|
695
|
+
docs: {
|
696
|
+
category: 'Best Practices',
|
697
|
+
description: 'Avoid scalar result type on mutation type to make sure to return a valid state.',
|
698
|
+
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-scalar-result-type-on-mutation.md',
|
699
|
+
requiresSchema: true,
|
700
|
+
examples: [
|
701
|
+
{
|
702
|
+
title: 'Incorrect',
|
703
|
+
code: /* GraphQL */ `
|
704
|
+
type Mutation {
|
705
|
+
createUser: Boolean
|
706
|
+
}
|
707
|
+
`,
|
708
|
+
},
|
709
|
+
{
|
710
|
+
title: 'Correct',
|
711
|
+
code: /* GraphQL */ `
|
712
|
+
type Mutation {
|
713
|
+
createUser: User!
|
714
|
+
}
|
715
|
+
`,
|
716
|
+
},
|
717
|
+
],
|
718
|
+
},
|
719
|
+
},
|
720
|
+
create(context) {
|
721
|
+
const schema = requireGraphQLSchemaFromContext('avoid-scalar-result-type-on-mutation', context);
|
722
|
+
const mutationType = schema.getMutationType();
|
723
|
+
if (!mutationType) {
|
724
|
+
return {};
|
725
|
+
}
|
726
|
+
const selector = `:matches(${graphql.Kind.OBJECT_TYPE_DEFINITION}, ${graphql.Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}] > ${graphql.Kind.FIELD_DEFINITION}`;
|
727
|
+
return {
|
728
|
+
[selector](node) {
|
729
|
+
const rawNode = node.rawNode();
|
730
|
+
const typeName = getTypeName(rawNode);
|
731
|
+
const graphQLType = schema.getType(typeName);
|
732
|
+
if (graphql.isScalarType(graphQLType)) {
|
733
|
+
context.report({
|
734
|
+
node,
|
735
|
+
message: `Unexpected scalar result type "${typeName}".`,
|
736
|
+
});
|
737
|
+
}
|
738
|
+
},
|
739
|
+
};
|
740
|
+
},
|
741
|
+
};
|
742
|
+
|
743
|
+
const AVOID_TYPENAME_PREFIX = 'AVOID_TYPENAME_PREFIX';
|
744
|
+
const rule$3 = {
|
609
745
|
meta: {
|
610
746
|
type: 'suggestion',
|
611
747
|
docs: {
|
@@ -659,7 +795,7 @@ const rule$2 = {
|
|
659
795
|
},
|
660
796
|
};
|
661
797
|
|
662
|
-
const rule$
|
798
|
+
const rule$4 = {
|
663
799
|
meta: {
|
664
800
|
type: 'suggestion',
|
665
801
|
docs: {
|
@@ -719,7 +855,7 @@ const rule$3 = {
|
|
719
855
|
},
|
720
856
|
};
|
721
857
|
|
722
|
-
const rule$
|
858
|
+
const rule$5 = {
|
723
859
|
meta: {
|
724
860
|
type: 'suggestion',
|
725
861
|
docs: {
|
@@ -844,12 +980,12 @@ const CASE_STYLES = [
|
|
844
980
|
const schemaOption = {
|
845
981
|
oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
|
846
982
|
};
|
847
|
-
const rule$
|
983
|
+
const rule$6 = {
|
848
984
|
meta: {
|
849
985
|
type: 'suggestion',
|
850
986
|
docs: {
|
851
987
|
category: 'Best Practices',
|
852
|
-
description: 'This rule allows you to enforce that the file name should match the operation name',
|
988
|
+
description: 'This rule allows you to enforce that the file name should match the operation name.',
|
853
989
|
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/match-document-filename.md`,
|
854
990
|
examples: [
|
855
991
|
{
|
@@ -1056,7 +1192,7 @@ function checkNameFormat(params) {
|
|
1056
1192
|
errorMessage: '{{nodeType}} name "{{nodeName}}" should have "{{suffix}}" suffix',
|
1057
1193
|
};
|
1058
1194
|
}
|
1059
|
-
if (style && !acceptedStyles.
|
1195
|
+
if (style && !acceptedStyles.includes(style)) {
|
1060
1196
|
return {
|
1061
1197
|
ok: false,
|
1062
1198
|
errorMessage: `{{nodeType}} name "{{nodeName}}" should be in one of the following options: ${acceptedStyles.join(',')}`,
|
@@ -1089,7 +1225,7 @@ function checkNameFormat(params) {
|
|
1089
1225
|
const schemaOption$1 = {
|
1090
1226
|
oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
|
1091
1227
|
};
|
1092
|
-
const rule$
|
1228
|
+
const rule$7 = {
|
1093
1229
|
meta: {
|
1094
1230
|
type: 'suggestion',
|
1095
1231
|
docs: {
|
@@ -1318,7 +1454,7 @@ const rule$6 = {
|
|
1318
1454
|
};
|
1319
1455
|
|
1320
1456
|
const NO_ANONYMOUS_OPERATIONS = 'NO_ANONYMOUS_OPERATIONS';
|
1321
|
-
const rule$
|
1457
|
+
const rule$8 = {
|
1322
1458
|
meta: {
|
1323
1459
|
type: 'suggestion',
|
1324
1460
|
docs: {
|
@@ -1376,13 +1512,14 @@ const rule$7 = {
|
|
1376
1512
|
};
|
1377
1513
|
|
1378
1514
|
const ERROR_MESSAGE_ID = 'NO_CASE_INSENSITIVE_ENUM_VALUES_DUPLICATES';
|
1379
|
-
const rule$
|
1515
|
+
const rule$9 = {
|
1380
1516
|
meta: {
|
1381
1517
|
type: 'suggestion',
|
1382
1518
|
docs: {
|
1383
1519
|
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-case-insensitive-enum-values-duplicates.md',
|
1384
1520
|
category: 'Best Practices',
|
1385
1521
|
recommended: true,
|
1522
|
+
description: 'Disallow case-insensitive enum values duplicates.',
|
1386
1523
|
examples: [
|
1387
1524
|
{
|
1388
1525
|
title: 'Incorrect',
|
@@ -1430,7 +1567,7 @@ const rule$8 = {
|
|
1430
1567
|
};
|
1431
1568
|
|
1432
1569
|
const NO_DEPRECATED = 'NO_DEPRECATED';
|
1433
|
-
const rule$
|
1570
|
+
const rule$a = {
|
1434
1571
|
meta: {
|
1435
1572
|
type: 'suggestion',
|
1436
1573
|
docs: {
|
@@ -1544,7 +1681,7 @@ const rule$9 = {
|
|
1544
1681
|
};
|
1545
1682
|
|
1546
1683
|
const HASHTAG_COMMENT = 'HASHTAG_COMMENT';
|
1547
|
-
const rule$
|
1684
|
+
const rule$b = {
|
1548
1685
|
meta: {
|
1549
1686
|
messages: {
|
1550
1687
|
[HASHTAG_COMMENT]: 'Using hashtag (#) for adding GraphQL descriptions is not allowed. Prefer using """ for multiline, or " for a single line description.',
|
@@ -1619,7 +1756,7 @@ const rule$a = {
|
|
1619
1756
|
};
|
1620
1757
|
|
1621
1758
|
const NO_OPERATION_NAME_SUFFIX = 'NO_OPERATION_NAME_SUFFIX';
|
1622
|
-
const rule$
|
1759
|
+
const rule$c = {
|
1623
1760
|
meta: {
|
1624
1761
|
fixable: 'code',
|
1625
1762
|
type: 'suggestion',
|
@@ -1674,7 +1811,7 @@ const rule$b = {
|
|
1674
1811
|
|
1675
1812
|
const UNREACHABLE_TYPE = 'UNREACHABLE_TYPE';
|
1676
1813
|
const RULE_NAME = 'no-unreachable-types';
|
1677
|
-
const rule$
|
1814
|
+
const rule$d = {
|
1678
1815
|
meta: {
|
1679
1816
|
messages: {
|
1680
1817
|
[UNREACHABLE_TYPE]: `Type "{{ typeName }}" is unreachable`,
|
@@ -1749,7 +1886,7 @@ const rule$c = {
|
|
1749
1886
|
|
1750
1887
|
const UNUSED_FIELD = 'UNUSED_FIELD';
|
1751
1888
|
const RULE_NAME$1 = 'no-unused-fields';
|
1752
|
-
const rule$
|
1889
|
+
const rule$e = {
|
1753
1890
|
meta: {
|
1754
1891
|
messages: {
|
1755
1892
|
[UNUSED_FIELD]: `Field "{{fieldName}}" is unused`,
|
@@ -1931,7 +2068,112 @@ function convertDescription(node) {
|
|
1931
2068
|
return [];
|
1932
2069
|
}
|
1933
2070
|
|
1934
|
-
const
|
2071
|
+
const DATE_REGEX = /^\d{2}\/\d{2}\/\d{4}$/;
|
2072
|
+
const MESSAGE_REQUIRE_DATE = 'MESSAGE_REQUIRE_DATE';
|
2073
|
+
const MESSAGE_INVALID_FORMAT = 'MESSAGE_INVALID_FORMAT';
|
2074
|
+
const MESSAGE_INVALID_DATE = 'MESSAGE_INVALID_DATE';
|
2075
|
+
const MESSAGE_CAN_BE_REMOVED = 'MESSAGE_CAN_BE_REMOVED';
|
2076
|
+
const rule$f = {
|
2077
|
+
meta: {
|
2078
|
+
type: 'suggestion',
|
2079
|
+
docs: {
|
2080
|
+
category: 'Best Practices',
|
2081
|
+
description: 'Require deletion date on `@deprecated` directive. Suggest removing deprecated things after deprecated date.',
|
2082
|
+
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-deprecation-date.md',
|
2083
|
+
examples: [
|
2084
|
+
{
|
2085
|
+
title: 'Incorrect',
|
2086
|
+
code: /* GraphQL */ `
|
2087
|
+
type User {
|
2088
|
+
firstname: String @deprecated
|
2089
|
+
firstName: String
|
2090
|
+
}
|
2091
|
+
`,
|
2092
|
+
},
|
2093
|
+
{
|
2094
|
+
title: 'Incorrect',
|
2095
|
+
code: /* GraphQL */ `
|
2096
|
+
type User {
|
2097
|
+
firstname: String @deprecated(reason: "Use 'firstName' instead")
|
2098
|
+
firstName: String
|
2099
|
+
}
|
2100
|
+
`,
|
2101
|
+
},
|
2102
|
+
{
|
2103
|
+
title: 'Correct',
|
2104
|
+
code: /* GraphQL */ `
|
2105
|
+
type User {
|
2106
|
+
firstname: String @deprecated(reason: "Use 'firstName' instead", deletionDate: "25/12/2022")
|
2107
|
+
firstName: String
|
2108
|
+
}
|
2109
|
+
`,
|
2110
|
+
},
|
2111
|
+
],
|
2112
|
+
},
|
2113
|
+
messages: {
|
2114
|
+
[MESSAGE_REQUIRE_DATE]: 'Directive "@deprecated" must have a deletion date.',
|
2115
|
+
[MESSAGE_INVALID_FORMAT]: 'Deletion date must be in format "DD/MM/YYYY".',
|
2116
|
+
[MESSAGE_INVALID_DATE]: 'Invalid "{{ deletionDate }}" deletion date.',
|
2117
|
+
[MESSAGE_CAN_BE_REMOVED]: '"{{ nodeName }}" сan be removed.',
|
2118
|
+
},
|
2119
|
+
schema: [
|
2120
|
+
{
|
2121
|
+
type: 'object',
|
2122
|
+
additionalProperties: false,
|
2123
|
+
properties: {
|
2124
|
+
argumentName: {
|
2125
|
+
type: 'string',
|
2126
|
+
},
|
2127
|
+
},
|
2128
|
+
},
|
2129
|
+
],
|
2130
|
+
},
|
2131
|
+
create(context) {
|
2132
|
+
return {
|
2133
|
+
'Directive[name.value=deprecated]'(node) {
|
2134
|
+
var _a;
|
2135
|
+
const argName = ((_a = context.options[0]) === null || _a === void 0 ? void 0 : _a.argumentName) || 'deletionDate';
|
2136
|
+
const deletionDateNode = node.arguments.find(arg => arg.name.value === argName);
|
2137
|
+
if (!deletionDateNode) {
|
2138
|
+
context.report({ node: node.name, messageId: MESSAGE_REQUIRE_DATE });
|
2139
|
+
return;
|
2140
|
+
}
|
2141
|
+
const deletionDate = valueFromNode(deletionDateNode.value);
|
2142
|
+
const isValidDate = DATE_REGEX.test(deletionDate);
|
2143
|
+
if (!isValidDate) {
|
2144
|
+
context.report({ node: node.name, messageId: MESSAGE_INVALID_FORMAT });
|
2145
|
+
return;
|
2146
|
+
}
|
2147
|
+
let [day, month, year] = deletionDate.split('/');
|
2148
|
+
day = day.toString().padStart(2, '0');
|
2149
|
+
month = month.toString().padStart(2, '0');
|
2150
|
+
const deletionDateInMS = Date.parse(`${year}-${month}-${day}`);
|
2151
|
+
if (Number.isNaN(deletionDateInMS)) {
|
2152
|
+
context.report({
|
2153
|
+
node: node.name,
|
2154
|
+
messageId: MESSAGE_INVALID_DATE,
|
2155
|
+
data: {
|
2156
|
+
deletionDate,
|
2157
|
+
},
|
2158
|
+
});
|
2159
|
+
return;
|
2160
|
+
}
|
2161
|
+
const canRemove = Date.now() > deletionDateInMS;
|
2162
|
+
if (canRemove) {
|
2163
|
+
context.report({
|
2164
|
+
node: node.name,
|
2165
|
+
messageId: MESSAGE_CAN_BE_REMOVED,
|
2166
|
+
data: {
|
2167
|
+
nodeName: node.parent.name.value,
|
2168
|
+
},
|
2169
|
+
});
|
2170
|
+
}
|
2171
|
+
},
|
2172
|
+
};
|
2173
|
+
},
|
2174
|
+
};
|
2175
|
+
|
2176
|
+
const rule$g = {
|
1935
2177
|
meta: {
|
1936
2178
|
docs: {
|
1937
2179
|
description: `Require all deprecation directives to specify a reason.`,
|
@@ -1973,11 +2215,11 @@ const rule$e = {
|
|
1973
2215
|
if (node && node.name && node.name.value === 'deprecated') {
|
1974
2216
|
const args = node.arguments || [];
|
1975
2217
|
const reasonArg = args.find(arg => arg.name && arg.name.value === 'reason');
|
1976
|
-
const value = reasonArg ? String(valueFromNode(reasonArg.value
|
2218
|
+
const value = reasonArg ? String(valueFromNode(reasonArg.value) || '').trim() : null;
|
1977
2219
|
if (!value) {
|
1978
2220
|
context.report({
|
1979
2221
|
node: node.name,
|
1980
|
-
message:
|
2222
|
+
message: 'Directive "@deprecated" must have a reason!',
|
1981
2223
|
});
|
1982
2224
|
}
|
1983
2225
|
}
|
@@ -2021,13 +2263,12 @@ function verifyRule(context, node) {
|
|
2021
2263
|
}
|
2022
2264
|
}
|
2023
2265
|
}
|
2024
|
-
const rule$
|
2266
|
+
const rule$h = {
|
2025
2267
|
meta: {
|
2026
2268
|
docs: {
|
2027
2269
|
category: 'Best Practices',
|
2028
2270
|
description: `Enforce descriptions in your type definitions.`,
|
2029
2271
|
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-description.md`,
|
2030
|
-
recommended: true,
|
2031
2272
|
examples: [
|
2032
2273
|
{
|
2033
2274
|
title: 'Incorrect',
|
@@ -2086,6 +2327,74 @@ const rule$f = {
|
|
2086
2327
|
},
|
2087
2328
|
};
|
2088
2329
|
|
2330
|
+
const RULE_NAME$2 = 'require-field-of-type-query-in-mutation-result';
|
2331
|
+
const rule$i = {
|
2332
|
+
meta: {
|
2333
|
+
type: 'suggestion',
|
2334
|
+
docs: {
|
2335
|
+
category: 'Best Practices',
|
2336
|
+
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`.',
|
2337
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$2}.md`,
|
2338
|
+
requiresSchema: true,
|
2339
|
+
examples: [
|
2340
|
+
{
|
2341
|
+
title: 'Incorrect',
|
2342
|
+
code: /* GraphQL */ `
|
2343
|
+
type User { ... }
|
2344
|
+
|
2345
|
+
type Mutation {
|
2346
|
+
createUser: User!
|
2347
|
+
}
|
2348
|
+
`,
|
2349
|
+
},
|
2350
|
+
{
|
2351
|
+
title: 'Correct',
|
2352
|
+
code: /* GraphQL */ `
|
2353
|
+
type User { ... }
|
2354
|
+
|
2355
|
+
type Query { ... }
|
2356
|
+
|
2357
|
+
type CreateUserPayload {
|
2358
|
+
user: User!
|
2359
|
+
query: Query!
|
2360
|
+
}
|
2361
|
+
|
2362
|
+
type Mutation {
|
2363
|
+
createUser: CreateUserPayload!
|
2364
|
+
}
|
2365
|
+
`,
|
2366
|
+
},
|
2367
|
+
],
|
2368
|
+
},
|
2369
|
+
},
|
2370
|
+
create(context) {
|
2371
|
+
const schema = requireGraphQLSchemaFromContext(RULE_NAME$2, context);
|
2372
|
+
const mutationType = schema.getMutationType();
|
2373
|
+
const queryType = schema.getQueryType();
|
2374
|
+
if (!mutationType || !queryType) {
|
2375
|
+
return {};
|
2376
|
+
}
|
2377
|
+
const selector = `:matches(${graphql.Kind.OBJECT_TYPE_DEFINITION}, ${graphql.Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}] > ${graphql.Kind.FIELD_DEFINITION}`;
|
2378
|
+
return {
|
2379
|
+
[selector](node) {
|
2380
|
+
const rawNode = node.rawNode();
|
2381
|
+
const typeName = getTypeName(rawNode);
|
2382
|
+
const graphQLType = schema.getType(typeName);
|
2383
|
+
if (graphql.isObjectType(graphQLType)) {
|
2384
|
+
const { fields } = graphQLType.astNode;
|
2385
|
+
const hasQueryType = fields.some(field => getTypeName(field) === queryType.name);
|
2386
|
+
if (!hasQueryType) {
|
2387
|
+
context.report({
|
2388
|
+
node,
|
2389
|
+
message: `Mutation result type "${graphQLType.name}" must contain field of type "${queryType.name}".`,
|
2390
|
+
});
|
2391
|
+
}
|
2392
|
+
}
|
2393
|
+
},
|
2394
|
+
};
|
2395
|
+
},
|
2396
|
+
};
|
2397
|
+
|
2089
2398
|
function convertToESTree(node, typeInfo) {
|
2090
2399
|
const visitor = { leave: convertNode(typeInfo) };
|
2091
2400
|
return {
|
@@ -2182,7 +2491,7 @@ const convertNode = (typeInfo) => (node, key, parent) => {
|
|
2182
2491
|
|
2183
2492
|
const REQUIRE_ID_WHEN_AVAILABLE = 'REQUIRE_ID_WHEN_AVAILABLE';
|
2184
2493
|
const DEFAULT_ID_FIELD_NAME = 'id';
|
2185
|
-
const rule$
|
2494
|
+
const rule$j = {
|
2186
2495
|
meta: {
|
2187
2496
|
type: 'suggestion',
|
2188
2497
|
docs: {
|
@@ -2272,13 +2581,13 @@ const rule$g = {
|
|
2272
2581
|
found = true;
|
2273
2582
|
}
|
2274
2583
|
else if (selection.kind === 'InlineFragment') {
|
2275
|
-
found =
|
2584
|
+
found = (((_a = selection.selectionSet) === null || _a === void 0 ? void 0 : _a.selections) || []).some(s => s.kind === 'Field' && s.name.value === fieldName);
|
2276
2585
|
}
|
2277
2586
|
else if (selection.kind === 'FragmentSpread') {
|
2278
2587
|
const foundSpread = siblings.getFragment(selection.name.value);
|
2279
2588
|
if (foundSpread[0]) {
|
2280
2589
|
checkedFragmentSpreads.add(foundSpread[0].document.name.value);
|
2281
|
-
found =
|
2590
|
+
found = (((_b = foundSpread[0].document.selectionSet) === null || _b === void 0 ? void 0 : _b.selections) || []).some(s => s.kind === 'Field' && s.name.value === fieldName);
|
2282
2591
|
}
|
2283
2592
|
}
|
2284
2593
|
if (found) {
|
@@ -2290,7 +2599,7 @@ const rule$g = {
|
|
2290
2599
|
parent.kind === 'InlineFragment' &&
|
2291
2600
|
parent.parent &&
|
2292
2601
|
parent.parent.kind === 'SelectionSet' &&
|
2293
|
-
|
2602
|
+
parent.parent.selections.some(s => s.kind === 'Field' && s.name.value === fieldName);
|
2294
2603
|
if (!found && !hasIdFieldInInterfaceSelectionSet) {
|
2295
2604
|
context.report({
|
2296
2605
|
loc: {
|
@@ -2318,7 +2627,7 @@ const rule$g = {
|
|
2318
2627
|
},
|
2319
2628
|
};
|
2320
2629
|
|
2321
|
-
const rule$
|
2630
|
+
const rule$k = {
|
2322
2631
|
meta: {
|
2323
2632
|
docs: {
|
2324
2633
|
category: 'Best Practices',
|
@@ -2432,7 +2741,7 @@ const rule$h = {
|
|
2432
2741
|
|
2433
2742
|
const shouldIgnoreNode = ({ node, exceptions }) => {
|
2434
2743
|
const rawNode = node.rawNode();
|
2435
|
-
if (exceptions.types && exceptions.types.
|
2744
|
+
if (exceptions.types && exceptions.types.includes(rawNode.name.value)) {
|
2436
2745
|
return true;
|
2437
2746
|
}
|
2438
2747
|
if (exceptions.suffixes && exceptions.suffixes.some(suffix => rawNode.name.value.endsWith(suffix))) {
|
@@ -2440,7 +2749,7 @@ const shouldIgnoreNode = ({ node, exceptions }) => {
|
|
2440
2749
|
}
|
2441
2750
|
return false;
|
2442
2751
|
};
|
2443
|
-
const rule$
|
2752
|
+
const rule$l = {
|
2444
2753
|
meta: {
|
2445
2754
|
type: 'suggestion',
|
2446
2755
|
docs: {
|
@@ -2590,7 +2899,7 @@ const rule$i = {
|
|
2590
2899
|
},
|
2591
2900
|
};
|
2592
2901
|
|
2593
|
-
const RULE_NAME$
|
2902
|
+
const RULE_NAME$3 = 'unique-fragment-name';
|
2594
2903
|
const UNIQUE_FRAGMENT_NAME = 'UNIQUE_FRAGMENT_NAME';
|
2595
2904
|
const checkNode = (context, node, ruleName, messageId) => {
|
2596
2905
|
var _a;
|
@@ -2630,13 +2939,13 @@ const checkNode = (context, node, ruleName, messageId) => {
|
|
2630
2939
|
});
|
2631
2940
|
}
|
2632
2941
|
};
|
2633
|
-
const rule$
|
2942
|
+
const rule$m = {
|
2634
2943
|
meta: {
|
2635
2944
|
type: 'suggestion',
|
2636
2945
|
docs: {
|
2637
2946
|
category: 'Best Practices',
|
2638
2947
|
description: `Enforce unique fragment names across your project.`,
|
2639
|
-
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$
|
2948
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$3}.md`,
|
2640
2949
|
requiresSiblings: true,
|
2641
2950
|
examples: [
|
2642
2951
|
{
|
@@ -2680,21 +2989,21 @@ const rule$j = {
|
|
2680
2989
|
create(context) {
|
2681
2990
|
return {
|
2682
2991
|
FragmentDefinition(node) {
|
2683
|
-
checkNode(context, node, RULE_NAME$
|
2992
|
+
checkNode(context, node, RULE_NAME$3, UNIQUE_FRAGMENT_NAME);
|
2684
2993
|
},
|
2685
2994
|
};
|
2686
2995
|
},
|
2687
2996
|
};
|
2688
2997
|
|
2689
|
-
const RULE_NAME$
|
2998
|
+
const RULE_NAME$4 = 'unique-operation-name';
|
2690
2999
|
const UNIQUE_OPERATION_NAME = 'UNIQUE_OPERATION_NAME';
|
2691
|
-
const rule$
|
3000
|
+
const rule$n = {
|
2692
3001
|
meta: {
|
2693
3002
|
type: 'suggestion',
|
2694
3003
|
docs: {
|
2695
3004
|
category: 'Best Practices',
|
2696
3005
|
description: `Enforce unique operation names across your project.`,
|
2697
|
-
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$
|
3006
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$4}.md`,
|
2698
3007
|
requiresSiblings: true,
|
2699
3008
|
examples: [
|
2700
3009
|
{
|
@@ -2742,35 +3051,41 @@ const rule$k = {
|
|
2742
3051
|
create(context) {
|
2743
3052
|
return {
|
2744
3053
|
OperationDefinition(node) {
|
2745
|
-
checkNode(context, node, RULE_NAME$
|
3054
|
+
checkNode(context, node, RULE_NAME$4, UNIQUE_OPERATION_NAME);
|
2746
3055
|
},
|
2747
3056
|
};
|
2748
3057
|
},
|
2749
3058
|
};
|
2750
3059
|
|
3060
|
+
/*
|
3061
|
+
* 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
|
3062
|
+
*/
|
2751
3063
|
const rules = {
|
3064
|
+
...GRAPHQL_JS_VALIDATIONS,
|
2752
3065
|
'avoid-duplicate-fields': rule,
|
2753
3066
|
'avoid-operation-name-prefix': rule$1,
|
2754
|
-
'avoid-
|
2755
|
-
'
|
2756
|
-
'
|
2757
|
-
'
|
2758
|
-
'
|
2759
|
-
'
|
2760
|
-
'no-
|
2761
|
-
'no-
|
2762
|
-
'no-
|
2763
|
-
'no-
|
2764
|
-
'no-
|
2765
|
-
'no-
|
2766
|
-
'
|
2767
|
-
'require-
|
2768
|
-
'require-
|
2769
|
-
'
|
2770
|
-
'
|
2771
|
-
'
|
2772
|
-
'
|
2773
|
-
|
3067
|
+
'avoid-scalar-result-type-on-mutation': rule$2,
|
3068
|
+
'avoid-typename-prefix': rule$3,
|
3069
|
+
'description-style': rule$4,
|
3070
|
+
'input-name': rule$5,
|
3071
|
+
'match-document-filename': rule$6,
|
3072
|
+
'naming-convention': rule$7,
|
3073
|
+
'no-anonymous-operations': rule$8,
|
3074
|
+
'no-case-insensitive-enum-values-duplicates': rule$9,
|
3075
|
+
'no-deprecated': rule$a,
|
3076
|
+
'no-hashtag-description': rule$b,
|
3077
|
+
'no-operation-name-suffix': rule$c,
|
3078
|
+
'no-unreachable-types': rule$d,
|
3079
|
+
'no-unused-fields': rule$e,
|
3080
|
+
'require-deprecation-date': rule$f,
|
3081
|
+
'require-deprecation-reason': rule$g,
|
3082
|
+
'require-description': rule$h,
|
3083
|
+
'require-field-of-type-query-in-mutation-result': rule$i,
|
3084
|
+
'require-id-when-available': rule$j,
|
3085
|
+
'selection-set-depth': rule$k,
|
3086
|
+
'strict-id-in-types': rule$l,
|
3087
|
+
'unique-fragment-name': rule$m,
|
3088
|
+
'unique-operation-name': rule$n,
|
2774
3089
|
};
|
2775
3090
|
|
2776
3091
|
const RELEVANT_KEYWORDS = ['gql', 'graphql', '/* GraphQL */'];
|
@@ -3047,7 +3362,6 @@ function getReachableTypes(schema) {
|
|
3047
3362
|
return reachableTypesCache;
|
3048
3363
|
}
|
3049
3364
|
const reachableTypes = new Set();
|
3050
|
-
const getTypeName = node => ('type' in node ? getTypeName(node.type) : node.name.value);
|
3051
3365
|
const collect = (node) => {
|
3052
3366
|
const typeName = getTypeName(node);
|
3053
3367
|
if (reachableTypes.has(typeName)) {
|
@@ -3184,6 +3498,7 @@ class GraphQLRuleTester extends require('eslint').RuleTester {
|
|
3184
3498
|
}
|
3185
3499
|
|
3186
3500
|
exports.GraphQLRuleTester = GraphQLRuleTester;
|
3501
|
+
exports.configs = configs;
|
3187
3502
|
exports.convertDescription = convertDescription;
|
3188
3503
|
exports.convertLocation = convertLocation;
|
3189
3504
|
exports.convertRange = convertRange;
|