@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.mjs
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import { Source, validate,
|
1
|
+
import { Kind, Source, validate, isScalarType, TokenKind, isNonNullType, isListType, isObjectType as isObjectType$1, visit, visitWithTypeInfo, GraphQLObjectType, GraphQLInterfaceType, TypeInfo, isInterfaceType, GraphQLError } from 'graphql';
|
2
2
|
import { validateSDL } from 'graphql/validation/validate';
|
3
3
|
import { processImport, parseImportLine } from '@graphql-tools/import';
|
4
4
|
import { statSync, existsSync, readFileSync } from 'fs';
|
@@ -10,6 +10,86 @@ import { parseCode } from '@graphql-tools/graphql-tag-pluck';
|
|
10
10
|
import { loadConfigSync, GraphQLConfig } from 'graphql-config';
|
11
11
|
import { CodeFileLoader } from '@graphql-tools/code-file-loader';
|
12
12
|
|
13
|
+
/*
|
14
|
+
* 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
|
15
|
+
*/
|
16
|
+
const recommendedConfig = {
|
17
|
+
parser: '@graphql-eslint/eslint-plugin',
|
18
|
+
plugins: ['@graphql-eslint'],
|
19
|
+
rules: {
|
20
|
+
'@graphql-eslint/avoid-typename-prefix': 'error',
|
21
|
+
'@graphql-eslint/executable-definitions': 'error',
|
22
|
+
'@graphql-eslint/fields-on-correct-type': 'error',
|
23
|
+
'@graphql-eslint/fragments-on-composite-type': 'error',
|
24
|
+
'@graphql-eslint/known-argument-names': 'error',
|
25
|
+
'@graphql-eslint/known-directives': 'error',
|
26
|
+
'@graphql-eslint/known-fragment-names': 'error',
|
27
|
+
'@graphql-eslint/known-type-names': 'error',
|
28
|
+
'@graphql-eslint/lone-anonymous-operation': 'error',
|
29
|
+
'@graphql-eslint/lone-schema-definition': 'error',
|
30
|
+
'@graphql-eslint/naming-convention': 'error',
|
31
|
+
'@graphql-eslint/no-anonymous-operations': 'error',
|
32
|
+
'@graphql-eslint/no-case-insensitive-enum-values-duplicates': 'error',
|
33
|
+
'@graphql-eslint/no-fragment-cycles': 'error',
|
34
|
+
'@graphql-eslint/no-operation-name-suffix': 'error',
|
35
|
+
'@graphql-eslint/no-undefined-variables': 'error',
|
36
|
+
'@graphql-eslint/no-unused-fragments': 'error',
|
37
|
+
'@graphql-eslint/no-unused-variables': 'error',
|
38
|
+
'@graphql-eslint/one-field-subscriptions': 'error',
|
39
|
+
'@graphql-eslint/overlapping-fields-can-be-merged': 'error',
|
40
|
+
'@graphql-eslint/possible-fragment-spread': 'error',
|
41
|
+
'@graphql-eslint/possible-type-extension': 'error',
|
42
|
+
'@graphql-eslint/provided-required-arguments': 'error',
|
43
|
+
'@graphql-eslint/require-deprecation-reason': 'error',
|
44
|
+
'@graphql-eslint/scalar-leafs': 'error',
|
45
|
+
'@graphql-eslint/strict-id-in-types': 'error',
|
46
|
+
'@graphql-eslint/unique-argument-names': 'error',
|
47
|
+
'@graphql-eslint/unique-directive-names': 'error',
|
48
|
+
'@graphql-eslint/unique-directive-names-per-location': 'error',
|
49
|
+
'@graphql-eslint/unique-enum-value-names': 'error',
|
50
|
+
'@graphql-eslint/unique-field-definition-names': 'error',
|
51
|
+
'@graphql-eslint/unique-input-field-names': 'error',
|
52
|
+
'@graphql-eslint/unique-operation-types': 'error',
|
53
|
+
'@graphql-eslint/unique-type-names': 'error',
|
54
|
+
'@graphql-eslint/unique-variable-names': 'error',
|
55
|
+
'@graphql-eslint/value-literals-of-correct-type': 'error',
|
56
|
+
'@graphql-eslint/variables-are-input-types': 'error',
|
57
|
+
'@graphql-eslint/variables-in-allowed-position': 'error',
|
58
|
+
},
|
59
|
+
};
|
60
|
+
|
61
|
+
/*
|
62
|
+
* 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
|
63
|
+
*/
|
64
|
+
const allConfig = {
|
65
|
+
...recommendedConfig,
|
66
|
+
rules: {
|
67
|
+
...recommendedConfig.rules,
|
68
|
+
'@graphql-eslint/avoid-duplicate-fields': 'error',
|
69
|
+
'@graphql-eslint/avoid-operation-name-prefix': 'error',
|
70
|
+
'@graphql-eslint/avoid-scalar-result-type-on-mutation': 'error',
|
71
|
+
'@graphql-eslint/description-style': 'error',
|
72
|
+
'@graphql-eslint/input-name': 'error',
|
73
|
+
'@graphql-eslint/match-document-filename': 'error',
|
74
|
+
'@graphql-eslint/no-deprecated': 'error',
|
75
|
+
'@graphql-eslint/no-hashtag-description': 'error',
|
76
|
+
'@graphql-eslint/no-unreachable-types': 'error',
|
77
|
+
'@graphql-eslint/no-unused-fields': 'error',
|
78
|
+
'@graphql-eslint/require-deprecation-date': 'error',
|
79
|
+
'@graphql-eslint/require-description': 'error',
|
80
|
+
'@graphql-eslint/require-field-of-type-query-in-mutation-result': 'error',
|
81
|
+
'@graphql-eslint/require-id-when-available': 'error',
|
82
|
+
'@graphql-eslint/selection-set-depth': 'error',
|
83
|
+
'@graphql-eslint/unique-fragment-name': 'error',
|
84
|
+
'@graphql-eslint/unique-operation-name': 'error',
|
85
|
+
},
|
86
|
+
};
|
87
|
+
|
88
|
+
const configs = {
|
89
|
+
all: allConfig,
|
90
|
+
recommended: recommendedConfig,
|
91
|
+
};
|
92
|
+
|
13
93
|
function requireSiblingsOperations(ruleName, context) {
|
14
94
|
if (!context.parserServices) {
|
15
95
|
throw new Error(`Rule '${ruleName}' requires 'parserOptions.operations' to be set and loaded. See http://bit.ly/graphql-eslint-operations for more info`);
|
@@ -95,6 +175,7 @@ const getOnDiskFilepath = (filepath) => {
|
|
95
175
|
}
|
96
176
|
return filepath;
|
97
177
|
};
|
178
|
+
const getTypeName = node => ('type' in node ? getTypeName(node.type) : node.name.value);
|
98
179
|
// Small workaround for the bug in older versions of @graphql-tools/load
|
99
180
|
// Can be removed after graphql-config bumps to a new version
|
100
181
|
const loaderCache = new Proxy(Object.create(null), {
|
@@ -112,7 +193,7 @@ const loaderCache = new Proxy(Object.create(null), {
|
|
112
193
|
return true;
|
113
194
|
},
|
114
195
|
});
|
115
|
-
const isObjectType = (node) => [
|
196
|
+
const isObjectType = (node) => [Kind.OBJECT_TYPE_DEFINITION, Kind.OBJECT_TYPE_EXTENSION].includes(node.type);
|
116
197
|
const isQueryType = (node) => isObjectType(node) && node.name.value === 'Query';
|
117
198
|
const isMutationType = (node) => isObjectType(node) && node.name.value === 'Mutation';
|
118
199
|
var CaseStyle;
|
@@ -123,16 +204,18 @@ var CaseStyle;
|
|
123
204
|
CaseStyle["upperCase"] = "UPPER_CASE";
|
124
205
|
CaseStyle["kebabCase"] = "kebab-case";
|
125
206
|
})(CaseStyle || (CaseStyle = {}));
|
207
|
+
const pascalCase = (str) => lowerCase(str)
|
208
|
+
.split(' ')
|
209
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
210
|
+
.join('');
|
211
|
+
const camelCase = (str) => {
|
212
|
+
const result = pascalCase(str);
|
213
|
+
return result.charAt(0).toLowerCase() + result.slice(1);
|
214
|
+
};
|
126
215
|
const convertCase = (style, str) => {
|
127
|
-
const pascalCase = (str) => lowerCase(str)
|
128
|
-
.split(' ')
|
129
|
-
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
130
|
-
.join('');
|
131
216
|
switch (style) {
|
132
|
-
case CaseStyle.camelCase:
|
133
|
-
|
134
|
-
return result.charAt(0).toLowerCase() + result.slice(1);
|
135
|
-
}
|
217
|
+
case CaseStyle.camelCase:
|
218
|
+
return camelCase(str);
|
136
219
|
case CaseStyle.pascalCase:
|
137
220
|
return pascalCase(str);
|
138
221
|
case CaseStyle.snakeCase:
|
@@ -194,9 +277,10 @@ const validationToRule = (name, ruleName, docs, getDocumentNode) => {
|
|
194
277
|
docs: {
|
195
278
|
...docs,
|
196
279
|
category: 'Validation',
|
280
|
+
recommended: true,
|
197
281
|
requiresSchema,
|
198
282
|
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${name}.md`,
|
199
|
-
description: `${docs.description}\n\n> This rule is a wrapper around a \`graphql-js\` validation function. [You can find
|
283
|
+
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).`,
|
200
284
|
},
|
201
285
|
},
|
202
286
|
create(context) {
|
@@ -220,6 +304,14 @@ const validationToRule = (name, ruleName, docs, getDocumentNode) => {
|
|
220
304
|
},
|
221
305
|
};
|
222
306
|
};
|
307
|
+
const importFiles = (context) => {
|
308
|
+
const code = context.getSourceCode().text;
|
309
|
+
if (!isGraphQLImportFile(code)) {
|
310
|
+
return null;
|
311
|
+
}
|
312
|
+
// Import documents because file contains '#import' comments
|
313
|
+
return processImport(context.getFilename());
|
314
|
+
};
|
223
315
|
const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-definitions', 'ExecutableDefinitions', {
|
224
316
|
description: `A GraphQL document is only valid for execution if all definitions are either operation or fragment definitions.`,
|
225
317
|
}), validationToRule('fields-on-correct-type', 'FieldsOnCorrectType', {
|
@@ -296,14 +388,7 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
|
|
296
388
|
\``,
|
297
389
|
},
|
298
390
|
],
|
299
|
-
},
|
300
|
-
const code = context.getSourceCode().text;
|
301
|
-
if (!isGraphQLImportFile(code)) {
|
302
|
-
return null;
|
303
|
-
}
|
304
|
-
// Import documents because file contains '#import' comments
|
305
|
-
return processImport(context.getFilename());
|
306
|
-
}), validationToRule('known-type-names', 'KnownTypeNames', {
|
391
|
+
}, importFiles), validationToRule('known-type-names', 'KnownTypeNames', {
|
307
392
|
description: `A GraphQL document is only valid if referenced types (specifically variable definitions and fragment conditions) are defined by the type schema.`,
|
308
393
|
}), validationToRule('lone-anonymous-operation', 'LoneAnonymousOperation', {
|
309
394
|
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.`,
|
@@ -345,7 +430,7 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
|
|
345
430
|
return getParentNode(context.getFilename());
|
346
431
|
}), validationToRule('no-unused-variables', 'NoUnusedVariables', {
|
347
432
|
description: `A GraphQL operation is only valid if all variables defined by an operation are used, either directly or within a spread fragment.`,
|
348
|
-
}), validationToRule('overlapping-fields-can-be-merged', 'OverlappingFieldsCanBeMerged', {
|
433
|
+
}, importFiles), validationToRule('overlapping-fields-can-be-merged', 'OverlappingFieldsCanBeMerged', {
|
349
434
|
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.`,
|
350
435
|
}), validationToRule('possible-fragment-spread', 'PossibleFragmentSpreads', {
|
351
436
|
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.`,
|
@@ -598,8 +683,59 @@ const rule$1 = {
|
|
598
683
|
},
|
599
684
|
};
|
600
685
|
|
601
|
-
const AVOID_TYPENAME_PREFIX = 'AVOID_TYPENAME_PREFIX';
|
602
686
|
const rule$2 = {
|
687
|
+
meta: {
|
688
|
+
type: 'suggestion',
|
689
|
+
docs: {
|
690
|
+
category: 'Best Practices',
|
691
|
+
description: 'Avoid scalar result type on mutation type to make sure to return a valid state.',
|
692
|
+
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-scalar-result-type-on-mutation.md',
|
693
|
+
requiresSchema: true,
|
694
|
+
examples: [
|
695
|
+
{
|
696
|
+
title: 'Incorrect',
|
697
|
+
code: /* GraphQL */ `
|
698
|
+
type Mutation {
|
699
|
+
createUser: Boolean
|
700
|
+
}
|
701
|
+
`,
|
702
|
+
},
|
703
|
+
{
|
704
|
+
title: 'Correct',
|
705
|
+
code: /* GraphQL */ `
|
706
|
+
type Mutation {
|
707
|
+
createUser: User!
|
708
|
+
}
|
709
|
+
`,
|
710
|
+
},
|
711
|
+
],
|
712
|
+
},
|
713
|
+
},
|
714
|
+
create(context) {
|
715
|
+
const schema = requireGraphQLSchemaFromContext('avoid-scalar-result-type-on-mutation', context);
|
716
|
+
const mutationType = schema.getMutationType();
|
717
|
+
if (!mutationType) {
|
718
|
+
return {};
|
719
|
+
}
|
720
|
+
const selector = `:matches(${Kind.OBJECT_TYPE_DEFINITION}, ${Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}] > ${Kind.FIELD_DEFINITION}`;
|
721
|
+
return {
|
722
|
+
[selector](node) {
|
723
|
+
const rawNode = node.rawNode();
|
724
|
+
const typeName = getTypeName(rawNode);
|
725
|
+
const graphQLType = schema.getType(typeName);
|
726
|
+
if (isScalarType(graphQLType)) {
|
727
|
+
context.report({
|
728
|
+
node,
|
729
|
+
message: `Unexpected scalar result type "${typeName}".`,
|
730
|
+
});
|
731
|
+
}
|
732
|
+
},
|
733
|
+
};
|
734
|
+
},
|
735
|
+
};
|
736
|
+
|
737
|
+
const AVOID_TYPENAME_PREFIX = 'AVOID_TYPENAME_PREFIX';
|
738
|
+
const rule$3 = {
|
603
739
|
meta: {
|
604
740
|
type: 'suggestion',
|
605
741
|
docs: {
|
@@ -653,7 +789,7 @@ const rule$2 = {
|
|
653
789
|
},
|
654
790
|
};
|
655
791
|
|
656
|
-
const rule$
|
792
|
+
const rule$4 = {
|
657
793
|
meta: {
|
658
794
|
type: 'suggestion',
|
659
795
|
docs: {
|
@@ -713,7 +849,7 @@ const rule$3 = {
|
|
713
849
|
},
|
714
850
|
};
|
715
851
|
|
716
|
-
const rule$
|
852
|
+
const rule$5 = {
|
717
853
|
meta: {
|
718
854
|
type: 'suggestion',
|
719
855
|
docs: {
|
@@ -838,12 +974,12 @@ const CASE_STYLES = [
|
|
838
974
|
const schemaOption = {
|
839
975
|
oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
|
840
976
|
};
|
841
|
-
const rule$
|
977
|
+
const rule$6 = {
|
842
978
|
meta: {
|
843
979
|
type: 'suggestion',
|
844
980
|
docs: {
|
845
981
|
category: 'Best Practices',
|
846
|
-
description: 'This rule allows you to enforce that the file name should match the operation name',
|
982
|
+
description: 'This rule allows you to enforce that the file name should match the operation name.',
|
847
983
|
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/match-document-filename.md`,
|
848
984
|
examples: [
|
849
985
|
{
|
@@ -1050,7 +1186,7 @@ function checkNameFormat(params) {
|
|
1050
1186
|
errorMessage: '{{nodeType}} name "{{nodeName}}" should have "{{suffix}}" suffix',
|
1051
1187
|
};
|
1052
1188
|
}
|
1053
|
-
if (style && !acceptedStyles.
|
1189
|
+
if (style && !acceptedStyles.includes(style)) {
|
1054
1190
|
return {
|
1055
1191
|
ok: false,
|
1056
1192
|
errorMessage: `{{nodeType}} name "{{nodeName}}" should be in one of the following options: ${acceptedStyles.join(',')}`,
|
@@ -1083,7 +1219,7 @@ function checkNameFormat(params) {
|
|
1083
1219
|
const schemaOption$1 = {
|
1084
1220
|
oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
|
1085
1221
|
};
|
1086
|
-
const rule$
|
1222
|
+
const rule$7 = {
|
1087
1223
|
meta: {
|
1088
1224
|
type: 'suggestion',
|
1089
1225
|
docs: {
|
@@ -1312,7 +1448,7 @@ const rule$6 = {
|
|
1312
1448
|
};
|
1313
1449
|
|
1314
1450
|
const NO_ANONYMOUS_OPERATIONS = 'NO_ANONYMOUS_OPERATIONS';
|
1315
|
-
const rule$
|
1451
|
+
const rule$8 = {
|
1316
1452
|
meta: {
|
1317
1453
|
type: 'suggestion',
|
1318
1454
|
docs: {
|
@@ -1370,13 +1506,14 @@ const rule$7 = {
|
|
1370
1506
|
};
|
1371
1507
|
|
1372
1508
|
const ERROR_MESSAGE_ID = 'NO_CASE_INSENSITIVE_ENUM_VALUES_DUPLICATES';
|
1373
|
-
const rule$
|
1509
|
+
const rule$9 = {
|
1374
1510
|
meta: {
|
1375
1511
|
type: 'suggestion',
|
1376
1512
|
docs: {
|
1377
1513
|
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/no-case-insensitive-enum-values-duplicates.md',
|
1378
1514
|
category: 'Best Practices',
|
1379
1515
|
recommended: true,
|
1516
|
+
description: 'Disallow case-insensitive enum values duplicates.',
|
1380
1517
|
examples: [
|
1381
1518
|
{
|
1382
1519
|
title: 'Incorrect',
|
@@ -1424,7 +1561,7 @@ const rule$8 = {
|
|
1424
1561
|
};
|
1425
1562
|
|
1426
1563
|
const NO_DEPRECATED = 'NO_DEPRECATED';
|
1427
|
-
const rule$
|
1564
|
+
const rule$a = {
|
1428
1565
|
meta: {
|
1429
1566
|
type: 'suggestion',
|
1430
1567
|
docs: {
|
@@ -1538,7 +1675,7 @@ const rule$9 = {
|
|
1538
1675
|
};
|
1539
1676
|
|
1540
1677
|
const HASHTAG_COMMENT = 'HASHTAG_COMMENT';
|
1541
|
-
const rule$
|
1678
|
+
const rule$b = {
|
1542
1679
|
meta: {
|
1543
1680
|
messages: {
|
1544
1681
|
[HASHTAG_COMMENT]: 'Using hashtag (#) for adding GraphQL descriptions is not allowed. Prefer using """ for multiline, or " for a single line description.',
|
@@ -1613,7 +1750,7 @@ const rule$a = {
|
|
1613
1750
|
};
|
1614
1751
|
|
1615
1752
|
const NO_OPERATION_NAME_SUFFIX = 'NO_OPERATION_NAME_SUFFIX';
|
1616
|
-
const rule$
|
1753
|
+
const rule$c = {
|
1617
1754
|
meta: {
|
1618
1755
|
fixable: 'code',
|
1619
1756
|
type: 'suggestion',
|
@@ -1668,7 +1805,7 @@ const rule$b = {
|
|
1668
1805
|
|
1669
1806
|
const UNREACHABLE_TYPE = 'UNREACHABLE_TYPE';
|
1670
1807
|
const RULE_NAME = 'no-unreachable-types';
|
1671
|
-
const rule$
|
1808
|
+
const rule$d = {
|
1672
1809
|
meta: {
|
1673
1810
|
messages: {
|
1674
1811
|
[UNREACHABLE_TYPE]: `Type "{{ typeName }}" is unreachable`,
|
@@ -1743,7 +1880,7 @@ const rule$c = {
|
|
1743
1880
|
|
1744
1881
|
const UNUSED_FIELD = 'UNUSED_FIELD';
|
1745
1882
|
const RULE_NAME$1 = 'no-unused-fields';
|
1746
|
-
const rule$
|
1883
|
+
const rule$e = {
|
1747
1884
|
meta: {
|
1748
1885
|
messages: {
|
1749
1886
|
[UNUSED_FIELD]: `Field "{{fieldName}}" is unused`,
|
@@ -1925,7 +2062,112 @@ function convertDescription(node) {
|
|
1925
2062
|
return [];
|
1926
2063
|
}
|
1927
2064
|
|
1928
|
-
const
|
2065
|
+
const DATE_REGEX = /^\d{2}\/\d{2}\/\d{4}$/;
|
2066
|
+
const MESSAGE_REQUIRE_DATE = 'MESSAGE_REQUIRE_DATE';
|
2067
|
+
const MESSAGE_INVALID_FORMAT = 'MESSAGE_INVALID_FORMAT';
|
2068
|
+
const MESSAGE_INVALID_DATE = 'MESSAGE_INVALID_DATE';
|
2069
|
+
const MESSAGE_CAN_BE_REMOVED = 'MESSAGE_CAN_BE_REMOVED';
|
2070
|
+
const rule$f = {
|
2071
|
+
meta: {
|
2072
|
+
type: 'suggestion',
|
2073
|
+
docs: {
|
2074
|
+
category: 'Best Practices',
|
2075
|
+
description: 'Require deletion date on `@deprecated` directive. Suggest removing deprecated things after deprecated date.',
|
2076
|
+
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-deprecation-date.md',
|
2077
|
+
examples: [
|
2078
|
+
{
|
2079
|
+
title: 'Incorrect',
|
2080
|
+
code: /* GraphQL */ `
|
2081
|
+
type User {
|
2082
|
+
firstname: String @deprecated
|
2083
|
+
firstName: String
|
2084
|
+
}
|
2085
|
+
`,
|
2086
|
+
},
|
2087
|
+
{
|
2088
|
+
title: 'Incorrect',
|
2089
|
+
code: /* GraphQL */ `
|
2090
|
+
type User {
|
2091
|
+
firstname: String @deprecated(reason: "Use 'firstName' instead")
|
2092
|
+
firstName: String
|
2093
|
+
}
|
2094
|
+
`,
|
2095
|
+
},
|
2096
|
+
{
|
2097
|
+
title: 'Correct',
|
2098
|
+
code: /* GraphQL */ `
|
2099
|
+
type User {
|
2100
|
+
firstname: String @deprecated(reason: "Use 'firstName' instead", deletionDate: "25/12/2022")
|
2101
|
+
firstName: String
|
2102
|
+
}
|
2103
|
+
`,
|
2104
|
+
},
|
2105
|
+
],
|
2106
|
+
},
|
2107
|
+
messages: {
|
2108
|
+
[MESSAGE_REQUIRE_DATE]: 'Directive "@deprecated" must have a deletion date.',
|
2109
|
+
[MESSAGE_INVALID_FORMAT]: 'Deletion date must be in format "DD/MM/YYYY".',
|
2110
|
+
[MESSAGE_INVALID_DATE]: 'Invalid "{{ deletionDate }}" deletion date.',
|
2111
|
+
[MESSAGE_CAN_BE_REMOVED]: '"{{ nodeName }}" сan be removed.',
|
2112
|
+
},
|
2113
|
+
schema: [
|
2114
|
+
{
|
2115
|
+
type: 'object',
|
2116
|
+
additionalProperties: false,
|
2117
|
+
properties: {
|
2118
|
+
argumentName: {
|
2119
|
+
type: 'string',
|
2120
|
+
},
|
2121
|
+
},
|
2122
|
+
},
|
2123
|
+
],
|
2124
|
+
},
|
2125
|
+
create(context) {
|
2126
|
+
return {
|
2127
|
+
'Directive[name.value=deprecated]'(node) {
|
2128
|
+
var _a;
|
2129
|
+
const argName = ((_a = context.options[0]) === null || _a === void 0 ? void 0 : _a.argumentName) || 'deletionDate';
|
2130
|
+
const deletionDateNode = node.arguments.find(arg => arg.name.value === argName);
|
2131
|
+
if (!deletionDateNode) {
|
2132
|
+
context.report({ node: node.name, messageId: MESSAGE_REQUIRE_DATE });
|
2133
|
+
return;
|
2134
|
+
}
|
2135
|
+
const deletionDate = valueFromNode(deletionDateNode.value);
|
2136
|
+
const isValidDate = DATE_REGEX.test(deletionDate);
|
2137
|
+
if (!isValidDate) {
|
2138
|
+
context.report({ node: node.name, messageId: MESSAGE_INVALID_FORMAT });
|
2139
|
+
return;
|
2140
|
+
}
|
2141
|
+
let [day, month, year] = deletionDate.split('/');
|
2142
|
+
day = day.toString().padStart(2, '0');
|
2143
|
+
month = month.toString().padStart(2, '0');
|
2144
|
+
const deletionDateInMS = Date.parse(`${year}-${month}-${day}`);
|
2145
|
+
if (Number.isNaN(deletionDateInMS)) {
|
2146
|
+
context.report({
|
2147
|
+
node: node.name,
|
2148
|
+
messageId: MESSAGE_INVALID_DATE,
|
2149
|
+
data: {
|
2150
|
+
deletionDate,
|
2151
|
+
},
|
2152
|
+
});
|
2153
|
+
return;
|
2154
|
+
}
|
2155
|
+
const canRemove = Date.now() > deletionDateInMS;
|
2156
|
+
if (canRemove) {
|
2157
|
+
context.report({
|
2158
|
+
node: node.name,
|
2159
|
+
messageId: MESSAGE_CAN_BE_REMOVED,
|
2160
|
+
data: {
|
2161
|
+
nodeName: node.parent.name.value,
|
2162
|
+
},
|
2163
|
+
});
|
2164
|
+
}
|
2165
|
+
},
|
2166
|
+
};
|
2167
|
+
},
|
2168
|
+
};
|
2169
|
+
|
2170
|
+
const rule$g = {
|
1929
2171
|
meta: {
|
1930
2172
|
docs: {
|
1931
2173
|
description: `Require all deprecation directives to specify a reason.`,
|
@@ -1967,11 +2209,11 @@ const rule$e = {
|
|
1967
2209
|
if (node && node.name && node.name.value === 'deprecated') {
|
1968
2210
|
const args = node.arguments || [];
|
1969
2211
|
const reasonArg = args.find(arg => arg.name && arg.name.value === 'reason');
|
1970
|
-
const value = reasonArg ? String(valueFromNode(reasonArg.value
|
2212
|
+
const value = reasonArg ? String(valueFromNode(reasonArg.value) || '').trim() : null;
|
1971
2213
|
if (!value) {
|
1972
2214
|
context.report({
|
1973
2215
|
node: node.name,
|
1974
|
-
message:
|
2216
|
+
message: 'Directive "@deprecated" must have a reason!',
|
1975
2217
|
});
|
1976
2218
|
}
|
1977
2219
|
}
|
@@ -2015,13 +2257,12 @@ function verifyRule(context, node) {
|
|
2015
2257
|
}
|
2016
2258
|
}
|
2017
2259
|
}
|
2018
|
-
const rule$
|
2260
|
+
const rule$h = {
|
2019
2261
|
meta: {
|
2020
2262
|
docs: {
|
2021
2263
|
category: 'Best Practices',
|
2022
2264
|
description: `Enforce descriptions in your type definitions.`,
|
2023
2265
|
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-description.md`,
|
2024
|
-
recommended: true,
|
2025
2266
|
examples: [
|
2026
2267
|
{
|
2027
2268
|
title: 'Incorrect',
|
@@ -2080,6 +2321,74 @@ const rule$f = {
|
|
2080
2321
|
},
|
2081
2322
|
};
|
2082
2323
|
|
2324
|
+
const RULE_NAME$2 = 'require-field-of-type-query-in-mutation-result';
|
2325
|
+
const rule$i = {
|
2326
|
+
meta: {
|
2327
|
+
type: 'suggestion',
|
2328
|
+
docs: {
|
2329
|
+
category: 'Best Practices',
|
2330
|
+
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`.',
|
2331
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$2}.md`,
|
2332
|
+
requiresSchema: true,
|
2333
|
+
examples: [
|
2334
|
+
{
|
2335
|
+
title: 'Incorrect',
|
2336
|
+
code: /* GraphQL */ `
|
2337
|
+
type User { ... }
|
2338
|
+
|
2339
|
+
type Mutation {
|
2340
|
+
createUser: User!
|
2341
|
+
}
|
2342
|
+
`,
|
2343
|
+
},
|
2344
|
+
{
|
2345
|
+
title: 'Correct',
|
2346
|
+
code: /* GraphQL */ `
|
2347
|
+
type User { ... }
|
2348
|
+
|
2349
|
+
type Query { ... }
|
2350
|
+
|
2351
|
+
type CreateUserPayload {
|
2352
|
+
user: User!
|
2353
|
+
query: Query!
|
2354
|
+
}
|
2355
|
+
|
2356
|
+
type Mutation {
|
2357
|
+
createUser: CreateUserPayload!
|
2358
|
+
}
|
2359
|
+
`,
|
2360
|
+
},
|
2361
|
+
],
|
2362
|
+
},
|
2363
|
+
},
|
2364
|
+
create(context) {
|
2365
|
+
const schema = requireGraphQLSchemaFromContext(RULE_NAME$2, context);
|
2366
|
+
const mutationType = schema.getMutationType();
|
2367
|
+
const queryType = schema.getQueryType();
|
2368
|
+
if (!mutationType || !queryType) {
|
2369
|
+
return {};
|
2370
|
+
}
|
2371
|
+
const selector = `:matches(${Kind.OBJECT_TYPE_DEFINITION}, ${Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}] > ${Kind.FIELD_DEFINITION}`;
|
2372
|
+
return {
|
2373
|
+
[selector](node) {
|
2374
|
+
const rawNode = node.rawNode();
|
2375
|
+
const typeName = getTypeName(rawNode);
|
2376
|
+
const graphQLType = schema.getType(typeName);
|
2377
|
+
if (isObjectType$1(graphQLType)) {
|
2378
|
+
const { fields } = graphQLType.astNode;
|
2379
|
+
const hasQueryType = fields.some(field => getTypeName(field) === queryType.name);
|
2380
|
+
if (!hasQueryType) {
|
2381
|
+
context.report({
|
2382
|
+
node,
|
2383
|
+
message: `Mutation result type "${graphQLType.name}" must contain field of type "${queryType.name}".`,
|
2384
|
+
});
|
2385
|
+
}
|
2386
|
+
}
|
2387
|
+
},
|
2388
|
+
};
|
2389
|
+
},
|
2390
|
+
};
|
2391
|
+
|
2083
2392
|
function convertToESTree(node, typeInfo) {
|
2084
2393
|
const visitor = { leave: convertNode(typeInfo) };
|
2085
2394
|
return {
|
@@ -2176,7 +2485,7 @@ const convertNode = (typeInfo) => (node, key, parent) => {
|
|
2176
2485
|
|
2177
2486
|
const REQUIRE_ID_WHEN_AVAILABLE = 'REQUIRE_ID_WHEN_AVAILABLE';
|
2178
2487
|
const DEFAULT_ID_FIELD_NAME = 'id';
|
2179
|
-
const rule$
|
2488
|
+
const rule$j = {
|
2180
2489
|
meta: {
|
2181
2490
|
type: 'suggestion',
|
2182
2491
|
docs: {
|
@@ -2266,13 +2575,13 @@ const rule$g = {
|
|
2266
2575
|
found = true;
|
2267
2576
|
}
|
2268
2577
|
else if (selection.kind === 'InlineFragment') {
|
2269
|
-
found =
|
2578
|
+
found = (((_a = selection.selectionSet) === null || _a === void 0 ? void 0 : _a.selections) || []).some(s => s.kind === 'Field' && s.name.value === fieldName);
|
2270
2579
|
}
|
2271
2580
|
else if (selection.kind === 'FragmentSpread') {
|
2272
2581
|
const foundSpread = siblings.getFragment(selection.name.value);
|
2273
2582
|
if (foundSpread[0]) {
|
2274
2583
|
checkedFragmentSpreads.add(foundSpread[0].document.name.value);
|
2275
|
-
found =
|
2584
|
+
found = (((_b = foundSpread[0].document.selectionSet) === null || _b === void 0 ? void 0 : _b.selections) || []).some(s => s.kind === 'Field' && s.name.value === fieldName);
|
2276
2585
|
}
|
2277
2586
|
}
|
2278
2587
|
if (found) {
|
@@ -2284,7 +2593,7 @@ const rule$g = {
|
|
2284
2593
|
parent.kind === 'InlineFragment' &&
|
2285
2594
|
parent.parent &&
|
2286
2595
|
parent.parent.kind === 'SelectionSet' &&
|
2287
|
-
|
2596
|
+
parent.parent.selections.some(s => s.kind === 'Field' && s.name.value === fieldName);
|
2288
2597
|
if (!found && !hasIdFieldInInterfaceSelectionSet) {
|
2289
2598
|
context.report({
|
2290
2599
|
loc: {
|
@@ -2312,7 +2621,7 @@ const rule$g = {
|
|
2312
2621
|
},
|
2313
2622
|
};
|
2314
2623
|
|
2315
|
-
const rule$
|
2624
|
+
const rule$k = {
|
2316
2625
|
meta: {
|
2317
2626
|
docs: {
|
2318
2627
|
category: 'Best Practices',
|
@@ -2426,7 +2735,7 @@ const rule$h = {
|
|
2426
2735
|
|
2427
2736
|
const shouldIgnoreNode = ({ node, exceptions }) => {
|
2428
2737
|
const rawNode = node.rawNode();
|
2429
|
-
if (exceptions.types && exceptions.types.
|
2738
|
+
if (exceptions.types && exceptions.types.includes(rawNode.name.value)) {
|
2430
2739
|
return true;
|
2431
2740
|
}
|
2432
2741
|
if (exceptions.suffixes && exceptions.suffixes.some(suffix => rawNode.name.value.endsWith(suffix))) {
|
@@ -2434,7 +2743,7 @@ const shouldIgnoreNode = ({ node, exceptions }) => {
|
|
2434
2743
|
}
|
2435
2744
|
return false;
|
2436
2745
|
};
|
2437
|
-
const rule$
|
2746
|
+
const rule$l = {
|
2438
2747
|
meta: {
|
2439
2748
|
type: 'suggestion',
|
2440
2749
|
docs: {
|
@@ -2584,7 +2893,7 @@ const rule$i = {
|
|
2584
2893
|
},
|
2585
2894
|
};
|
2586
2895
|
|
2587
|
-
const RULE_NAME$
|
2896
|
+
const RULE_NAME$3 = 'unique-fragment-name';
|
2588
2897
|
const UNIQUE_FRAGMENT_NAME = 'UNIQUE_FRAGMENT_NAME';
|
2589
2898
|
const checkNode = (context, node, ruleName, messageId) => {
|
2590
2899
|
var _a;
|
@@ -2624,13 +2933,13 @@ const checkNode = (context, node, ruleName, messageId) => {
|
|
2624
2933
|
});
|
2625
2934
|
}
|
2626
2935
|
};
|
2627
|
-
const rule$
|
2936
|
+
const rule$m = {
|
2628
2937
|
meta: {
|
2629
2938
|
type: 'suggestion',
|
2630
2939
|
docs: {
|
2631
2940
|
category: 'Best Practices',
|
2632
2941
|
description: `Enforce unique fragment names across your project.`,
|
2633
|
-
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$
|
2942
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$3}.md`,
|
2634
2943
|
requiresSiblings: true,
|
2635
2944
|
examples: [
|
2636
2945
|
{
|
@@ -2674,21 +2983,21 @@ const rule$j = {
|
|
2674
2983
|
create(context) {
|
2675
2984
|
return {
|
2676
2985
|
FragmentDefinition(node) {
|
2677
|
-
checkNode(context, node, RULE_NAME$
|
2986
|
+
checkNode(context, node, RULE_NAME$3, UNIQUE_FRAGMENT_NAME);
|
2678
2987
|
},
|
2679
2988
|
};
|
2680
2989
|
},
|
2681
2990
|
};
|
2682
2991
|
|
2683
|
-
const RULE_NAME$
|
2992
|
+
const RULE_NAME$4 = 'unique-operation-name';
|
2684
2993
|
const UNIQUE_OPERATION_NAME = 'UNIQUE_OPERATION_NAME';
|
2685
|
-
const rule$
|
2994
|
+
const rule$n = {
|
2686
2995
|
meta: {
|
2687
2996
|
type: 'suggestion',
|
2688
2997
|
docs: {
|
2689
2998
|
category: 'Best Practices',
|
2690
2999
|
description: `Enforce unique operation names across your project.`,
|
2691
|
-
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$
|
3000
|
+
url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$4}.md`,
|
2692
3001
|
requiresSiblings: true,
|
2693
3002
|
examples: [
|
2694
3003
|
{
|
@@ -2736,35 +3045,41 @@ const rule$k = {
|
|
2736
3045
|
create(context) {
|
2737
3046
|
return {
|
2738
3047
|
OperationDefinition(node) {
|
2739
|
-
checkNode(context, node, RULE_NAME$
|
3048
|
+
checkNode(context, node, RULE_NAME$4, UNIQUE_OPERATION_NAME);
|
2740
3049
|
},
|
2741
3050
|
};
|
2742
3051
|
},
|
2743
3052
|
};
|
2744
3053
|
|
3054
|
+
/*
|
3055
|
+
* 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
|
3056
|
+
*/
|
2745
3057
|
const rules = {
|
3058
|
+
...GRAPHQL_JS_VALIDATIONS,
|
2746
3059
|
'avoid-duplicate-fields': rule,
|
2747
3060
|
'avoid-operation-name-prefix': rule$1,
|
2748
|
-
'avoid-
|
2749
|
-
'
|
2750
|
-
'
|
2751
|
-
'
|
2752
|
-
'
|
2753
|
-
'
|
2754
|
-
'no-
|
2755
|
-
'no-
|
2756
|
-
'no-
|
2757
|
-
'no-
|
2758
|
-
'no-
|
2759
|
-
'no-
|
2760
|
-
'
|
2761
|
-
'require-
|
2762
|
-
'require-
|
2763
|
-
'
|
2764
|
-
'
|
2765
|
-
'
|
2766
|
-
'
|
2767
|
-
|
3061
|
+
'avoid-scalar-result-type-on-mutation': rule$2,
|
3062
|
+
'avoid-typename-prefix': rule$3,
|
3063
|
+
'description-style': rule$4,
|
3064
|
+
'input-name': rule$5,
|
3065
|
+
'match-document-filename': rule$6,
|
3066
|
+
'naming-convention': rule$7,
|
3067
|
+
'no-anonymous-operations': rule$8,
|
3068
|
+
'no-case-insensitive-enum-values-duplicates': rule$9,
|
3069
|
+
'no-deprecated': rule$a,
|
3070
|
+
'no-hashtag-description': rule$b,
|
3071
|
+
'no-operation-name-suffix': rule$c,
|
3072
|
+
'no-unreachable-types': rule$d,
|
3073
|
+
'no-unused-fields': rule$e,
|
3074
|
+
'require-deprecation-date': rule$f,
|
3075
|
+
'require-deprecation-reason': rule$g,
|
3076
|
+
'require-description': rule$h,
|
3077
|
+
'require-field-of-type-query-in-mutation-result': rule$i,
|
3078
|
+
'require-id-when-available': rule$j,
|
3079
|
+
'selection-set-depth': rule$k,
|
3080
|
+
'strict-id-in-types': rule$l,
|
3081
|
+
'unique-fragment-name': rule$m,
|
3082
|
+
'unique-operation-name': rule$n,
|
2768
3083
|
};
|
2769
3084
|
|
2770
3085
|
const RELEVANT_KEYWORDS = ['gql', 'graphql', '/* GraphQL */'];
|
@@ -3041,7 +3356,6 @@ function getReachableTypes(schema) {
|
|
3041
3356
|
return reachableTypesCache;
|
3042
3357
|
}
|
3043
3358
|
const reachableTypes = new Set();
|
3044
|
-
const getTypeName = node => ('type' in node ? getTypeName(node.type) : node.name.value);
|
3045
3359
|
const collect = (node) => {
|
3046
3360
|
const typeName = getTypeName(node);
|
3047
3361
|
if (reachableTypes.has(typeName)) {
|
@@ -3177,4 +3491,4 @@ class GraphQLRuleTester extends require('eslint').RuleTester {
|
|
3177
3491
|
}
|
3178
3492
|
}
|
3179
3493
|
|
3180
|
-
export { GraphQLRuleTester, convertDescription, convertLocation, convertRange, convertToESTree, extractCommentsFromAst, getBaseType, isNodeWithDescription, parse, parseForESLint, processors, rules, valueFromNode };
|
3494
|
+
export { GraphQLRuleTester, configs, convertDescription, convertLocation, convertRange, convertToESTree, extractCommentsFromAst, getBaseType, isNodeWithDescription, parse, parseForESLint, processors, rules, valueFromNode };
|