@graphql-eslint/eslint-plugin 3.14.0-alpha-20221221142641-4e1a924 → 3.14.0-alpha-20221222012949-5caade4
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 +309 -0
- package/cjs/cache.js +30 -0
- package/cjs/configs/base.js +7 -0
- package/cjs/configs/index.js +16 -0
- package/cjs/configs/operations-all.js +31 -0
- package/cjs/configs/operations-recommended.js +56 -0
- package/cjs/configs/relay.js +12 -0
- package/cjs/configs/schema-all.js +23 -0
- package/cjs/configs/schema-recommended.js +52 -0
- package/cjs/documents.js +149 -0
- package/cjs/estree-converter/converter.js +62 -0
- package/cjs/estree-converter/index.js +6 -0
- package/cjs/estree-converter/types.js +2 -0
- package/cjs/estree-converter/utils.js +109 -0
- package/cjs/graphql-config.js +55 -0
- package/cjs/index.js +17 -0
- package/cjs/parser.js +61 -0
- package/cjs/processor.js +78 -0
- package/cjs/rules/alphabetize.js +348 -0
- package/cjs/rules/description-style.js +78 -0
- package/cjs/rules/graphql-js-validation.js +499 -0
- package/cjs/rules/index.js +68 -0
- package/cjs/rules/input-name.js +136 -0
- package/cjs/rules/lone-executable-definition.js +88 -0
- package/cjs/rules/match-document-filename.js +235 -0
- package/cjs/rules/naming-convention.js +310 -0
- package/cjs/rules/no-anonymous-operations.js +67 -0
- package/cjs/rules/no-case-insensitive-enum-values-duplicates.js +61 -0
- package/cjs/rules/no-deprecated.js +124 -0
- package/cjs/rules/no-duplicate-fields.js +112 -0
- package/cjs/rules/no-hashtag-description.js +89 -0
- package/cjs/rules/no-root-type.js +86 -0
- package/cjs/rules/no-scalar-result-type-on-mutation.js +66 -0
- package/cjs/rules/no-typename-prefix.js +65 -0
- package/cjs/rules/no-unreachable-types.js +158 -0
- package/cjs/rules/no-unused-fields.js +130 -0
- package/cjs/rules/relay-arguments.js +121 -0
- package/cjs/rules/relay-connection-types.js +107 -0
- package/cjs/rules/relay-edge-types.js +189 -0
- package/cjs/rules/relay-page-info.js +100 -0
- package/cjs/rules/require-deprecation-date.js +123 -0
- package/cjs/rules/require-deprecation-reason.js +56 -0
- package/cjs/rules/require-description.js +193 -0
- package/cjs/rules/require-field-of-type-query-in-mutation-result.js +72 -0
- package/cjs/rules/require-id-when-available.js +199 -0
- package/cjs/rules/selection-set-depth.js +135 -0
- package/cjs/rules/strict-id-in-types.js +162 -0
- package/cjs/rules/unique-fragment-name.js +90 -0
- package/cjs/rules/unique-operation-name.js +65 -0
- package/cjs/schema.js +42 -0
- package/cjs/testkit.js +183 -0
- package/cjs/types.js +2 -0
- package/cjs/utils.js +96 -0
- package/docs/README.md +82 -0
- package/docs/custom-rules.md +184 -0
- package/docs/deprecated-rules.md +24 -0
- package/docs/parser-options.md +95 -0
- package/docs/parser.md +67 -0
- package/docs/rules/alphabetize.md +194 -0
- package/docs/rules/description-style.md +57 -0
- package/docs/rules/executable-definitions.md +20 -0
- package/docs/rules/fields-on-correct-type.md +23 -0
- package/docs/rules/fragments-on-composite-type.md +20 -0
- package/docs/rules/input-name.md +80 -0
- package/docs/rules/known-argument-names.md +23 -0
- package/docs/rules/known-directives.md +48 -0
- package/docs/rules/known-fragment-names.md +72 -0
- package/docs/rules/known-type-names.md +24 -0
- package/docs/rules/lone-anonymous-operation.md +20 -0
- package/docs/rules/lone-executable-definition.md +59 -0
- package/docs/rules/lone-schema-definition.md +19 -0
- package/docs/rules/match-document-filename.md +181 -0
- package/docs/rules/naming-convention.md +320 -0
- package/docs/rules/no-anonymous-operations.md +43 -0
- package/docs/rules/no-case-insensitive-enum-values-duplicates.md +46 -0
- package/docs/rules/no-deprecated.md +88 -0
- package/docs/rules/no-duplicate-fields.md +69 -0
- package/docs/rules/no-fragment-cycles.md +19 -0
- package/docs/rules/no-hashtag-description.md +62 -0
- package/docs/rules/no-root-type.md +55 -0
- package/docs/rules/no-scalar-result-type-on-mutation.md +39 -0
- package/docs/rules/no-typename-prefix.md +42 -0
- package/docs/rules/no-undefined-variables.md +20 -0
- package/docs/rules/no-unreachable-types.md +52 -0
- package/docs/rules/no-unused-fields.md +64 -0
- package/docs/rules/no-unused-fragments.md +20 -0
- package/docs/rules/no-unused-variables.md +20 -0
- package/docs/rules/one-field-subscriptions.md +19 -0
- package/docs/rules/overlapping-fields-can-be-merged.md +20 -0
- package/docs/rules/possible-fragment-spread.md +21 -0
- package/docs/rules/possible-type-extension.md +19 -0
- package/docs/rules/provided-required-arguments.md +21 -0
- package/docs/rules/relay-arguments.md +59 -0
- package/docs/rules/relay-connection-types.md +43 -0
- package/docs/rules/relay-edge-types.md +60 -0
- package/docs/rules/relay-page-info.md +34 -0
- package/docs/rules/require-deprecation-date.md +59 -0
- package/docs/rules/require-deprecation-reason.md +49 -0
- package/docs/rules/require-description.md +147 -0
- package/docs/rules/require-field-of-type-query-in-mutation-result.md +50 -0
- package/docs/rules/require-id-when-available.md +91 -0
- package/docs/rules/scalar-leafs.md +23 -0
- package/docs/rules/selection-set-depth.md +86 -0
- package/docs/rules/strict-id-in-types.md +129 -0
- package/docs/rules/unique-argument-names.md +19 -0
- package/docs/rules/unique-directive-names-per-location.md +21 -0
- package/docs/rules/unique-directive-names.md +19 -0
- package/docs/rules/unique-enum-value-names.md +16 -0
- package/docs/rules/unique-field-definition-names.md +19 -0
- package/docs/rules/unique-fragment-name.md +52 -0
- package/docs/rules/unique-input-field-names.md +19 -0
- package/docs/rules/unique-operation-name.md +56 -0
- package/docs/rules/unique-operation-types.md +19 -0
- package/docs/rules/unique-type-names.md +19 -0
- package/docs/rules/unique-variable-names.md +19 -0
- package/docs/rules/value-literals-of-correct-type.md +22 -0
- package/docs/rules/variables-are-input-types.md +20 -0
- package/docs/rules/variables-in-allowed-position.md +19 -0
- package/package.json +8 -11
- package/{cache.d.ts → typings/cache.d.cts} +0 -0
- package/typings/cache.d.ts +10 -0
- package/typings/configs/base.d.cts +5 -0
- package/typings/configs/base.d.ts +5 -0
- package/typings/configs/index.d.cts +139 -0
- package/typings/configs/index.d.ts +139 -0
- package/typings/configs/operations-all.d.cts +20 -0
- package/typings/configs/operations-all.d.ts +20 -0
- package/typings/configs/operations-recommended.d.cts +50 -0
- package/typings/configs/operations-recommended.d.ts +50 -0
- package/typings/configs/relay.d.cts +10 -0
- package/typings/configs/relay.d.ts +10 -0
- package/typings/configs/schema-all.d.cts +15 -0
- package/typings/configs/schema-all.d.ts +15 -0
- package/typings/configs/schema-recommended.d.cts +47 -0
- package/typings/configs/schema-recommended.d.ts +47 -0
- package/{documents.d.ts → typings/documents.d.cts} +0 -0
- package/typings/documents.d.ts +21 -0
- package/{estree-converter/converter.d.ts → typings/estree-converter/converter.d.cts} +0 -0
- package/typings/estree-converter/converter.d.ts +3 -0
- package/{estree-converter/index.d.ts → typings/estree-converter/index.d.cts} +0 -0
- package/typings/estree-converter/index.d.ts +3 -0
- package/{estree-converter/types.d.ts → typings/estree-converter/types.d.cts} +0 -0
- package/typings/estree-converter/types.d.ts +40 -0
- package/{estree-converter/utils.d.ts → typings/estree-converter/utils.d.cts} +0 -0
- package/typings/estree-converter/utils.d.ts +13 -0
- package/{graphql-config.d.ts → typings/graphql-config.d.cts} +0 -0
- package/typings/graphql-config.d.ts +4 -0
- package/typings/index.d.cts +9 -0
- package/{index.d.ts → typings/index.d.ts} +1 -5
- package/{parser.d.ts → typings/parser.d.cts} +0 -0
- package/typings/parser.d.ts +2 -0
- package/{processor.d.ts → typings/processor.d.cts} +0 -0
- package/typings/processor.d.ts +6 -0
- package/{rules/alphabetize.d.ts → typings/rules/alphabetize.d.cts} +0 -0
- package/typings/rules/alphabetize.d.ts +76 -0
- package/{rules/description-style.d.ts → typings/rules/description-style.d.cts} +0 -0
- package/typings/rules/description-style.d.ts +20 -0
- package/{rules/graphql-js-validation.d.ts → typings/rules/graphql-js-validation.d.cts} +0 -0
- package/typings/rules/graphql-js-validation.d.ts +2 -0
- package/{rules/index.d.ts → typings/rules/index.d.cts} +0 -0
- package/typings/rules/index.d.ts +104 -0
- package/{rules/input-name.d.ts → typings/rules/input-name.d.cts} +0 -0
- package/typings/rules/input-name.d.ts +35 -0
- package/{rules/lone-executable-definition.d.ts → typings/rules/lone-executable-definition.d.cts} +0 -0
- package/typings/rules/lone-executable-definition.d.ts +26 -0
- package/{rules/match-document-filename.d.ts → typings/rules/match-document-filename.d.cts} +0 -0
- package/typings/rules/match-document-filename.d.ts +72 -0
- package/{rules/naming-convention.d.ts → typings/rules/naming-convention.d.cts} +0 -0
- package/typings/rules/naming-convention.d.ts +83 -0
- package/{rules/no-anonymous-operations.d.ts → typings/rules/no-anonymous-operations.d.cts} +0 -0
- package/{rules/no-case-insensitive-enum-values-duplicates.d.ts → typings/rules/no-anonymous-operations.d.ts} +0 -0
- package/{rules/no-hashtag-description.d.ts → typings/rules/no-case-insensitive-enum-values-duplicates.d.cts} +0 -0
- package/{rules/no-scalar-result-type-on-mutation.d.ts → typings/rules/no-case-insensitive-enum-values-duplicates.d.ts} +0 -0
- package/{rules/no-deprecated.d.ts → typings/rules/no-deprecated.d.cts} +0 -0
- package/typings/rules/no-deprecated.d.ts +2 -0
- package/{rules/no-duplicate-fields.d.ts → typings/rules/no-duplicate-fields.d.cts} +0 -0
- package/{rules/relay-page-info.d.ts → typings/rules/no-duplicate-fields.d.ts} +0 -0
- package/{rules/no-typename-prefix.d.ts → typings/rules/no-hashtag-description.d.cts} +0 -0
- package/{rules/no-unreachable-types.d.ts → typings/rules/no-hashtag-description.d.ts} +0 -0
- package/{rules/no-root-type.d.ts → typings/rules/no-root-type.d.cts} +0 -0
- package/typings/rules/no-root-type.d.ts +25 -0
- package/{rules/no-unused-fields.d.ts → typings/rules/no-scalar-result-type-on-mutation.d.cts} +0 -0
- package/{rules/unique-operation-name.d.ts → typings/rules/no-scalar-result-type-on-mutation.d.ts} +0 -0
- package/typings/rules/no-typename-prefix.d.cts +2 -0
- package/typings/rules/no-typename-prefix.d.ts +2 -0
- package/typings/rules/no-unreachable-types.d.cts +2 -0
- package/typings/rules/no-unreachable-types.d.ts +2 -0
- package/typings/rules/no-unused-fields.d.cts +2 -0
- package/typings/rules/no-unused-fields.d.ts +2 -0
- package/{rules/relay-arguments.d.ts → typings/rules/relay-arguments.d.cts} +0 -0
- package/typings/rules/relay-arguments.d.ts +21 -0
- package/{rules/relay-connection-types.d.ts → typings/rules/relay-connection-types.d.cts} +0 -0
- package/typings/rules/relay-connection-types.d.ts +4 -0
- package/{rules/relay-edge-types.d.ts → typings/rules/relay-edge-types.d.cts} +0 -0
- package/typings/rules/relay-edge-types.d.ts +31 -0
- package/{rules/require-deprecation-reason.d.ts → typings/rules/relay-page-info.d.cts} +0 -0
- package/{rules/require-field-of-type-query-in-mutation-result.d.ts → typings/rules/relay-page-info.d.ts} +0 -0
- package/{rules/require-deprecation-date.d.ts → typings/rules/require-deprecation-date.d.cts} +0 -0
- package/typings/rules/require-deprecation-date.d.ts +18 -0
- package/typings/rules/require-deprecation-reason.d.cts +2 -0
- package/typings/rules/require-deprecation-reason.d.ts +2 -0
- package/{rules/require-description.d.ts → typings/rules/require-description.d.cts} +0 -0
- package/typings/rules/require-description.d.ts +14 -0
- package/typings/rules/require-field-of-type-query-in-mutation-result.d.cts +2 -0
- package/typings/rules/require-field-of-type-query-in-mutation-result.d.ts +2 -0
- package/{rules/require-id-when-available.d.ts → typings/rules/require-id-when-available.d.cts} +0 -0
- package/typings/rules/require-id-when-available.d.ts +36 -0
- package/{rules/selection-set-depth.d.ts → typings/rules/selection-set-depth.d.cts} +0 -0
- package/typings/rules/selection-set-depth.d.ts +28 -0
- package/{rules/strict-id-in-types.d.ts → typings/rules/strict-id-in-types.d.cts} +0 -0
- package/typings/rules/strict-id-in-types.d.ts +57 -0
- package/{rules/unique-fragment-name.d.ts → typings/rules/unique-fragment-name.d.cts} +0 -0
- package/typings/rules/unique-fragment-name.d.ts +5 -0
- package/typings/rules/unique-operation-name.d.cts +2 -0
- package/typings/rules/unique-operation-name.d.ts +2 -0
- package/{schema.d.ts → typings/schema.d.cts} +0 -0
- package/typings/schema.d.ts +3 -0
- package/{testkit.d.ts → typings/testkit.d.cts} +0 -0
- package/typings/testkit.d.ts +27 -0
- package/{types.d.ts → typings/types.d.cts} +0 -0
- package/typings/types.d.ts +81 -0
- package/{utils.d.ts → typings/utils.d.cts} +0 -0
- package/typings/utils.d.ts +34 -0
- package/configs/base.json +0 -4
- package/configs/operations-all.json +0 -25
- package/configs/operations-recommended.json +0 -50
- package/configs/relay.json +0 -9
- package/configs/schema-all.json +0 -17
- package/configs/schema-recommended.json +0 -49
- package/index.js +0 -4995
- package/index.mjs +0 -4983
@@ -0,0 +1,56 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.rule = void 0;
|
4
|
+
const estree_converter_1 = require("../estree-converter");
|
5
|
+
exports.rule = {
|
6
|
+
meta: {
|
7
|
+
docs: {
|
8
|
+
description: 'Require all deprecation directives to specify a reason.',
|
9
|
+
category: 'Schema',
|
10
|
+
url: 'https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/require-deprecation-reason.md',
|
11
|
+
recommended: true,
|
12
|
+
examples: [
|
13
|
+
{
|
14
|
+
title: 'Incorrect',
|
15
|
+
code: /* GraphQL */ `
|
16
|
+
type MyType {
|
17
|
+
name: String @deprecated
|
18
|
+
}
|
19
|
+
`,
|
20
|
+
},
|
21
|
+
{
|
22
|
+
title: 'Incorrect',
|
23
|
+
code: /* GraphQL */ `
|
24
|
+
type MyType {
|
25
|
+
name: String @deprecated(reason: "")
|
26
|
+
}
|
27
|
+
`,
|
28
|
+
},
|
29
|
+
{
|
30
|
+
title: 'Correct',
|
31
|
+
code: /* GraphQL */ `
|
32
|
+
type MyType {
|
33
|
+
name: String @deprecated(reason: "no longer relevant, please use fullName field")
|
34
|
+
}
|
35
|
+
`,
|
36
|
+
},
|
37
|
+
],
|
38
|
+
},
|
39
|
+
type: 'suggestion',
|
40
|
+
schema: [],
|
41
|
+
},
|
42
|
+
create(context) {
|
43
|
+
return {
|
44
|
+
'Directive[name.value=deprecated]'(node) {
|
45
|
+
const reasonArgument = node.arguments.find(arg => arg.name.value === 'reason');
|
46
|
+
const value = reasonArgument && String((0, estree_converter_1.valueFromNode)(reasonArgument.value)).trim();
|
47
|
+
if (!value) {
|
48
|
+
context.report({
|
49
|
+
node: node.name,
|
50
|
+
message: 'Directive "@deprecated" must have a reason!',
|
51
|
+
});
|
52
|
+
}
|
53
|
+
},
|
54
|
+
};
|
55
|
+
},
|
56
|
+
};
|
@@ -0,0 +1,193 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.rule = void 0;
|
4
|
+
const graphql_1 = require("graphql");
|
5
|
+
const utils_1 = require("../utils");
|
6
|
+
const utils_2 = require("@graphql-tools/utils");
|
7
|
+
const RULE_ID = 'require-description';
|
8
|
+
const ALLOWED_KINDS = [
|
9
|
+
...utils_1.TYPES_KINDS,
|
10
|
+
graphql_1.Kind.DIRECTIVE_DEFINITION,
|
11
|
+
graphql_1.Kind.FIELD_DEFINITION,
|
12
|
+
graphql_1.Kind.INPUT_VALUE_DEFINITION,
|
13
|
+
graphql_1.Kind.ENUM_VALUE_DEFINITION,
|
14
|
+
graphql_1.Kind.OPERATION_DEFINITION,
|
15
|
+
];
|
16
|
+
function getNodeName(node) {
|
17
|
+
const DisplayNodeNameMap = {
|
18
|
+
[graphql_1.Kind.OBJECT_TYPE_DEFINITION]: 'type',
|
19
|
+
[graphql_1.Kind.INTERFACE_TYPE_DEFINITION]: 'interface',
|
20
|
+
[graphql_1.Kind.ENUM_TYPE_DEFINITION]: 'enum',
|
21
|
+
[graphql_1.Kind.SCALAR_TYPE_DEFINITION]: 'scalar',
|
22
|
+
[graphql_1.Kind.INPUT_OBJECT_TYPE_DEFINITION]: 'input',
|
23
|
+
[graphql_1.Kind.UNION_TYPE_DEFINITION]: 'union',
|
24
|
+
[graphql_1.Kind.DIRECTIVE_DEFINITION]: 'directive',
|
25
|
+
};
|
26
|
+
switch (node.kind) {
|
27
|
+
case graphql_1.Kind.OBJECT_TYPE_DEFINITION:
|
28
|
+
case graphql_1.Kind.INTERFACE_TYPE_DEFINITION:
|
29
|
+
case graphql_1.Kind.ENUM_TYPE_DEFINITION:
|
30
|
+
case graphql_1.Kind.SCALAR_TYPE_DEFINITION:
|
31
|
+
case graphql_1.Kind.INPUT_OBJECT_TYPE_DEFINITION:
|
32
|
+
case graphql_1.Kind.UNION_TYPE_DEFINITION:
|
33
|
+
return `${DisplayNodeNameMap[node.kind]} ${node.name.value}`;
|
34
|
+
case graphql_1.Kind.DIRECTIVE_DEFINITION:
|
35
|
+
return `${DisplayNodeNameMap[node.kind]} @${node.name.value}`;
|
36
|
+
case graphql_1.Kind.FIELD_DEFINITION:
|
37
|
+
case graphql_1.Kind.INPUT_VALUE_DEFINITION:
|
38
|
+
case graphql_1.Kind.ENUM_VALUE_DEFINITION:
|
39
|
+
return `${node.parent.name.value}.${node.name.value}`;
|
40
|
+
case graphql_1.Kind.OPERATION_DEFINITION:
|
41
|
+
return node.name ? `${node.operation} ${node.name.value}` : node.operation;
|
42
|
+
}
|
43
|
+
}
|
44
|
+
const schema = {
|
45
|
+
type: 'array',
|
46
|
+
minItems: 1,
|
47
|
+
maxItems: 1,
|
48
|
+
items: {
|
49
|
+
type: 'object',
|
50
|
+
additionalProperties: false,
|
51
|
+
minProperties: 1,
|
52
|
+
properties: {
|
53
|
+
types: {
|
54
|
+
type: 'boolean',
|
55
|
+
description: `Includes:\n${utils_1.TYPES_KINDS.map(kind => `- \`${kind}\``).join('\n')}`,
|
56
|
+
},
|
57
|
+
rootField: {
|
58
|
+
type: 'boolean',
|
59
|
+
description: 'Definitions within `Query`, `Mutation`, and `Subscription` root types.',
|
60
|
+
},
|
61
|
+
...Object.fromEntries([...ALLOWED_KINDS].sort().map(kind => {
|
62
|
+
let description = `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`;
|
63
|
+
if (kind === graphql_1.Kind.OPERATION_DEFINITION) {
|
64
|
+
description +=
|
65
|
+
'\n> You must use only comment syntax `#` and not description syntax `"""` or `"`.';
|
66
|
+
}
|
67
|
+
return [kind, { type: 'boolean', description }];
|
68
|
+
})),
|
69
|
+
},
|
70
|
+
},
|
71
|
+
};
|
72
|
+
exports.rule = {
|
73
|
+
meta: {
|
74
|
+
docs: {
|
75
|
+
category: 'Schema',
|
76
|
+
description: 'Enforce descriptions in type definitions and operations.',
|
77
|
+
url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
|
78
|
+
examples: [
|
79
|
+
{
|
80
|
+
title: 'Incorrect',
|
81
|
+
usage: [{ types: true, FieldDefinition: true }],
|
82
|
+
code: /* GraphQL */ `
|
83
|
+
type someTypeName {
|
84
|
+
name: String
|
85
|
+
}
|
86
|
+
`,
|
87
|
+
},
|
88
|
+
{
|
89
|
+
title: 'Correct',
|
90
|
+
usage: [{ types: true, FieldDefinition: true }],
|
91
|
+
code: /* GraphQL */ `
|
92
|
+
"""
|
93
|
+
Some type description
|
94
|
+
"""
|
95
|
+
type someTypeName {
|
96
|
+
"""
|
97
|
+
Name description
|
98
|
+
"""
|
99
|
+
name: String
|
100
|
+
}
|
101
|
+
`,
|
102
|
+
},
|
103
|
+
{
|
104
|
+
title: 'Correct',
|
105
|
+
usage: [{ OperationDefinition: true }],
|
106
|
+
code: /* GraphQL */ `
|
107
|
+
# Create a new user
|
108
|
+
mutation createUser {
|
109
|
+
# ...
|
110
|
+
}
|
111
|
+
`,
|
112
|
+
},
|
113
|
+
{
|
114
|
+
title: 'Correct',
|
115
|
+
usage: [{ rootField: true }],
|
116
|
+
code: /* GraphQL */ `
|
117
|
+
type Mutation {
|
118
|
+
"Create a new user"
|
119
|
+
createUser: User
|
120
|
+
}
|
121
|
+
|
122
|
+
type User {
|
123
|
+
name: String
|
124
|
+
}
|
125
|
+
`,
|
126
|
+
},
|
127
|
+
],
|
128
|
+
configOptions: [
|
129
|
+
{
|
130
|
+
types: true,
|
131
|
+
[graphql_1.Kind.DIRECTIVE_DEFINITION]: true,
|
132
|
+
// rootField: true TODO enable in graphql-eslint v4
|
133
|
+
},
|
134
|
+
],
|
135
|
+
recommended: true,
|
136
|
+
},
|
137
|
+
type: 'suggestion',
|
138
|
+
messages: {
|
139
|
+
[RULE_ID]: 'Description is required for `{{ nodeName }}`.',
|
140
|
+
},
|
141
|
+
schema,
|
142
|
+
},
|
143
|
+
create(context) {
|
144
|
+
const { types, rootField, ...restOptions } = context.options[0] || {};
|
145
|
+
const kinds = new Set(types ? utils_1.TYPES_KINDS : []);
|
146
|
+
for (const [kind, isEnabled] of Object.entries(restOptions)) {
|
147
|
+
if (isEnabled) {
|
148
|
+
kinds.add(kind);
|
149
|
+
}
|
150
|
+
else {
|
151
|
+
kinds.delete(kind);
|
152
|
+
}
|
153
|
+
}
|
154
|
+
if (rootField) {
|
155
|
+
const schema = (0, utils_1.requireGraphQLSchemaFromContext)(RULE_ID, context);
|
156
|
+
const rootTypeNames = (0, utils_2.getRootTypeNames)(schema);
|
157
|
+
kinds.add(`:matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=/^(${[
|
158
|
+
...rootTypeNames,
|
159
|
+
].join(',')})$/] > FieldDefinition`);
|
160
|
+
}
|
161
|
+
const selector = [...kinds].join(',');
|
162
|
+
return {
|
163
|
+
[selector](node) {
|
164
|
+
var _a;
|
165
|
+
let description = '';
|
166
|
+
const isOperation = node.kind === graphql_1.Kind.OPERATION_DEFINITION;
|
167
|
+
if (isOperation) {
|
168
|
+
const rawNode = node.rawNode();
|
169
|
+
const { prev, line } = rawNode.loc.startToken;
|
170
|
+
if (prev.kind === graphql_1.TokenKind.COMMENT) {
|
171
|
+
const value = prev.value.trim();
|
172
|
+
const linesBefore = line - prev.line;
|
173
|
+
if (!value.startsWith('eslint') && linesBefore === 1) {
|
174
|
+
description = value;
|
175
|
+
}
|
176
|
+
}
|
177
|
+
}
|
178
|
+
else {
|
179
|
+
description = ((_a = node.description) === null || _a === void 0 ? void 0 : _a.value.trim()) || '';
|
180
|
+
}
|
181
|
+
if (description.length === 0) {
|
182
|
+
context.report({
|
183
|
+
loc: isOperation ? (0, utils_1.getLocation)(node.loc.start, node.operation) : node.name.loc,
|
184
|
+
messageId: RULE_ID,
|
185
|
+
data: {
|
186
|
+
nodeName: getNodeName(node),
|
187
|
+
},
|
188
|
+
});
|
189
|
+
}
|
190
|
+
},
|
191
|
+
};
|
192
|
+
},
|
193
|
+
};
|
@@ -0,0 +1,72 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.rule = void 0;
|
4
|
+
const graphql_1 = require("graphql");
|
5
|
+
const utils_1 = require("../utils");
|
6
|
+
const RULE_ID = 'require-field-of-type-query-in-mutation-result';
|
7
|
+
exports.rule = {
|
8
|
+
meta: {
|
9
|
+
type: 'suggestion',
|
10
|
+
docs: {
|
11
|
+
category: 'Schema',
|
12
|
+
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`.',
|
13
|
+
url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
|
14
|
+
requiresSchema: true,
|
15
|
+
examples: [
|
16
|
+
{
|
17
|
+
title: 'Incorrect',
|
18
|
+
code: /* GraphQL */ `
|
19
|
+
type User { ... }
|
20
|
+
|
21
|
+
type Mutation {
|
22
|
+
createUser: User!
|
23
|
+
}
|
24
|
+
`,
|
25
|
+
},
|
26
|
+
{
|
27
|
+
title: 'Correct',
|
28
|
+
code: /* GraphQL */ `
|
29
|
+
type User { ... }
|
30
|
+
|
31
|
+
type Query { ... }
|
32
|
+
|
33
|
+
type CreateUserPayload {
|
34
|
+
user: User!
|
35
|
+
query: Query!
|
36
|
+
}
|
37
|
+
|
38
|
+
type Mutation {
|
39
|
+
createUser: CreateUserPayload!
|
40
|
+
}
|
41
|
+
`,
|
42
|
+
},
|
43
|
+
],
|
44
|
+
},
|
45
|
+
schema: [],
|
46
|
+
},
|
47
|
+
create(context) {
|
48
|
+
const schema = (0, utils_1.requireGraphQLSchemaFromContext)(RULE_ID, context);
|
49
|
+
const mutationType = schema.getMutationType();
|
50
|
+
const queryType = schema.getQueryType();
|
51
|
+
if (!mutationType || !queryType) {
|
52
|
+
return {};
|
53
|
+
}
|
54
|
+
const selector = `:matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=${mutationType.name}] > FieldDefinition > .gqlType Name`;
|
55
|
+
return {
|
56
|
+
[selector](node) {
|
57
|
+
const typeName = node.value;
|
58
|
+
const graphQLType = schema.getType(typeName);
|
59
|
+
if ((0, graphql_1.isObjectType)(graphQLType)) {
|
60
|
+
const { fields } = graphQLType.astNode;
|
61
|
+
const hasQueryType = fields.some(field => (0, utils_1.getTypeName)(field) === queryType.name);
|
62
|
+
if (!hasQueryType) {
|
63
|
+
context.report({
|
64
|
+
node,
|
65
|
+
message: `Mutation result type "${graphQLType.name}" must contain field of type "${queryType.name}"`,
|
66
|
+
});
|
67
|
+
}
|
68
|
+
}
|
69
|
+
},
|
70
|
+
};
|
71
|
+
},
|
72
|
+
};
|
@@ -0,0 +1,199 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.rule = void 0;
|
4
|
+
const graphql_1 = require("graphql");
|
5
|
+
const utils_1 = require("@graphql-tools/utils");
|
6
|
+
const utils_2 = require("../utils");
|
7
|
+
const estree_converter_1 = require("../estree-converter");
|
8
|
+
const RULE_ID = 'require-id-when-available';
|
9
|
+
const DEFAULT_ID_FIELD_NAME = 'id';
|
10
|
+
const schema = {
|
11
|
+
definitions: {
|
12
|
+
asString: {
|
13
|
+
type: 'string',
|
14
|
+
},
|
15
|
+
asArray: utils_2.ARRAY_DEFAULT_OPTIONS,
|
16
|
+
},
|
17
|
+
type: 'array',
|
18
|
+
maxItems: 1,
|
19
|
+
items: {
|
20
|
+
type: 'object',
|
21
|
+
additionalProperties: false,
|
22
|
+
properties: {
|
23
|
+
fieldName: {
|
24
|
+
oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asArray' }],
|
25
|
+
default: DEFAULT_ID_FIELD_NAME,
|
26
|
+
},
|
27
|
+
},
|
28
|
+
},
|
29
|
+
};
|
30
|
+
exports.rule = {
|
31
|
+
meta: {
|
32
|
+
type: 'suggestion',
|
33
|
+
hasSuggestions: true,
|
34
|
+
docs: {
|
35
|
+
category: 'Operations',
|
36
|
+
description: 'Enforce selecting specific fields when they are available on the GraphQL type.',
|
37
|
+
url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
|
38
|
+
requiresSchema: true,
|
39
|
+
requiresSiblings: true,
|
40
|
+
examples: [
|
41
|
+
{
|
42
|
+
title: 'Incorrect',
|
43
|
+
code: /* GraphQL */ `
|
44
|
+
# In your schema
|
45
|
+
type User {
|
46
|
+
id: ID!
|
47
|
+
name: String!
|
48
|
+
}
|
49
|
+
|
50
|
+
# Query
|
51
|
+
query {
|
52
|
+
user {
|
53
|
+
name
|
54
|
+
}
|
55
|
+
}
|
56
|
+
`,
|
57
|
+
},
|
58
|
+
{
|
59
|
+
title: 'Correct',
|
60
|
+
code: /* GraphQL */ `
|
61
|
+
# In your schema
|
62
|
+
type User {
|
63
|
+
id: ID!
|
64
|
+
name: String!
|
65
|
+
}
|
66
|
+
|
67
|
+
# Query
|
68
|
+
query {
|
69
|
+
user {
|
70
|
+
id
|
71
|
+
name
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
# Selecting \`id\` with an alias is also valid
|
76
|
+
query {
|
77
|
+
user {
|
78
|
+
id: name
|
79
|
+
}
|
80
|
+
}
|
81
|
+
`,
|
82
|
+
},
|
83
|
+
],
|
84
|
+
recommended: true,
|
85
|
+
},
|
86
|
+
messages: {
|
87
|
+
[RULE_ID]: "Field{{ pluralSuffix }} {{ fieldName }} must be selected when it's available on a type.\nInclude it in your selection set{{ addition }}.",
|
88
|
+
},
|
89
|
+
schema,
|
90
|
+
},
|
91
|
+
create(context) {
|
92
|
+
const schema = (0, utils_2.requireGraphQLSchemaFromContext)(RULE_ID, context);
|
93
|
+
const siblings = (0, utils_2.requireSiblingsOperations)(RULE_ID, context);
|
94
|
+
const { fieldName = DEFAULT_ID_FIELD_NAME } = context.options[0] || {};
|
95
|
+
const idNames = (0, utils_1.asArray)(fieldName);
|
96
|
+
// Check selections only in OperationDefinition,
|
97
|
+
// skip selections of OperationDefinition and InlineFragment
|
98
|
+
const selector = 'OperationDefinition SelectionSet[parent.kind!=/(^OperationDefinition|InlineFragment)$/]';
|
99
|
+
const typeInfo = new graphql_1.TypeInfo(schema);
|
100
|
+
function checkFragments(node) {
|
101
|
+
for (const selection of node.selections) {
|
102
|
+
if (selection.kind !== graphql_1.Kind.FRAGMENT_SPREAD) {
|
103
|
+
continue;
|
104
|
+
}
|
105
|
+
const [foundSpread] = siblings.getFragment(selection.name.value);
|
106
|
+
if (!foundSpread) {
|
107
|
+
continue;
|
108
|
+
}
|
109
|
+
const checkedFragmentSpreads = new Set();
|
110
|
+
const visitor = (0, graphql_1.visitWithTypeInfo)(typeInfo, {
|
111
|
+
SelectionSet(node, key, parent) {
|
112
|
+
if (parent.kind === graphql_1.Kind.FRAGMENT_DEFINITION) {
|
113
|
+
checkedFragmentSpreads.add(parent.name.value);
|
114
|
+
}
|
115
|
+
else if (parent.kind !== graphql_1.Kind.INLINE_FRAGMENT) {
|
116
|
+
checkSelections(node, typeInfo.getType(), selection.loc.start, parent, checkedFragmentSpreads);
|
117
|
+
}
|
118
|
+
},
|
119
|
+
});
|
120
|
+
(0, graphql_1.visit)(foundSpread.document, visitor);
|
121
|
+
}
|
122
|
+
}
|
123
|
+
function checkSelections(node, type,
|
124
|
+
// Fragment can be placed in separate file
|
125
|
+
// Provide actual fragment spread location instead of location in fragment
|
126
|
+
loc,
|
127
|
+
// Can't access to node.parent in GraphQL AST.Node, so pass as argument
|
128
|
+
parent, checkedFragmentSpreads = new Set()) {
|
129
|
+
const rawType = (0, estree_converter_1.getBaseType)(type);
|
130
|
+
const isObjectType = rawType instanceof graphql_1.GraphQLObjectType;
|
131
|
+
const isInterfaceType = rawType instanceof graphql_1.GraphQLInterfaceType;
|
132
|
+
if (!isObjectType && !isInterfaceType) {
|
133
|
+
return;
|
134
|
+
}
|
135
|
+
const fields = rawType.getFields();
|
136
|
+
const hasIdFieldInType = idNames.some(name => fields[name]);
|
137
|
+
if (!hasIdFieldInType) {
|
138
|
+
return;
|
139
|
+
}
|
140
|
+
function hasIdField({ selections }) {
|
141
|
+
return selections.some(selection => {
|
142
|
+
if (selection.kind === graphql_1.Kind.FIELD) {
|
143
|
+
if (selection.alias && idNames.includes(selection.alias.value)) {
|
144
|
+
return true;
|
145
|
+
}
|
146
|
+
return idNames.includes(selection.name.value);
|
147
|
+
}
|
148
|
+
if (selection.kind === graphql_1.Kind.INLINE_FRAGMENT) {
|
149
|
+
return hasIdField(selection.selectionSet);
|
150
|
+
}
|
151
|
+
if (selection.kind === graphql_1.Kind.FRAGMENT_SPREAD) {
|
152
|
+
const [foundSpread] = siblings.getFragment(selection.name.value);
|
153
|
+
if (foundSpread) {
|
154
|
+
const fragmentSpread = foundSpread.document;
|
155
|
+
checkedFragmentSpreads.add(fragmentSpread.name.value);
|
156
|
+
return hasIdField(fragmentSpread.selectionSet);
|
157
|
+
}
|
158
|
+
}
|
159
|
+
return false;
|
160
|
+
});
|
161
|
+
}
|
162
|
+
const hasId = hasIdField(node);
|
163
|
+
checkFragments(node);
|
164
|
+
if (hasId) {
|
165
|
+
return;
|
166
|
+
}
|
167
|
+
const pluralSuffix = idNames.length > 1 ? 's' : '';
|
168
|
+
const fieldName = (0, utils_2.englishJoinWords)(idNames.map(name => `\`${(parent.alias || parent.name).value}.${name}\``));
|
169
|
+
const addition = checkedFragmentSpreads.size === 0
|
170
|
+
? ''
|
171
|
+
: ` or add to used fragment${checkedFragmentSpreads.size > 1 ? 's' : ''} ${(0, utils_2.englishJoinWords)([...checkedFragmentSpreads].map(name => `\`${name}\``))}`;
|
172
|
+
const problem = {
|
173
|
+
loc,
|
174
|
+
messageId: RULE_ID,
|
175
|
+
data: {
|
176
|
+
pluralSuffix,
|
177
|
+
fieldName,
|
178
|
+
addition,
|
179
|
+
},
|
180
|
+
};
|
181
|
+
// Don't provide suggestions for selections in fragments as fragment can be in a separate file
|
182
|
+
if ('type' in node) {
|
183
|
+
problem.suggest = idNames.map(idName => ({
|
184
|
+
desc: `Add \`${idName}\` selection`,
|
185
|
+
fix: fixer => fixer.insertTextBefore(node.selections[0], `${idName} `),
|
186
|
+
}));
|
187
|
+
}
|
188
|
+
context.report(problem);
|
189
|
+
}
|
190
|
+
return {
|
191
|
+
[selector](node) {
|
192
|
+
const typeInfo = node.typeInfo();
|
193
|
+
if (typeInfo.gqlType) {
|
194
|
+
checkSelections(node, typeInfo.gqlType, node.loc.start, node.parent);
|
195
|
+
}
|
196
|
+
},
|
197
|
+
};
|
198
|
+
},
|
199
|
+
};
|
@@ -0,0 +1,135 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.rule = void 0;
|
4
|
+
const tslib_1 = require("tslib");
|
5
|
+
const graphql_depth_limit_1 = tslib_1.__importDefault(require("graphql-depth-limit"));
|
6
|
+
const graphql_1 = require("graphql");
|
7
|
+
const utils_1 = require("../utils");
|
8
|
+
const RULE_ID = 'selection-set-depth';
|
9
|
+
const schema = {
|
10
|
+
type: 'array',
|
11
|
+
minItems: 1,
|
12
|
+
maxItems: 1,
|
13
|
+
items: {
|
14
|
+
type: 'object',
|
15
|
+
additionalProperties: false,
|
16
|
+
required: ['maxDepth'],
|
17
|
+
properties: {
|
18
|
+
maxDepth: {
|
19
|
+
type: 'number',
|
20
|
+
},
|
21
|
+
ignore: utils_1.ARRAY_DEFAULT_OPTIONS,
|
22
|
+
},
|
23
|
+
},
|
24
|
+
};
|
25
|
+
exports.rule = {
|
26
|
+
meta: {
|
27
|
+
type: 'suggestion',
|
28
|
+
hasSuggestions: true,
|
29
|
+
docs: {
|
30
|
+
category: 'Operations',
|
31
|
+
description: 'Limit the complexity of the GraphQL operations solely by their depth. Based on [graphql-depth-limit](https://npmjs.com/package/graphql-depth-limit).',
|
32
|
+
url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
|
33
|
+
requiresSiblings: true,
|
34
|
+
examples: [
|
35
|
+
{
|
36
|
+
title: 'Incorrect',
|
37
|
+
usage: [{ maxDepth: 1 }],
|
38
|
+
code: `
|
39
|
+
query deep2 {
|
40
|
+
viewer { # Level 0
|
41
|
+
albums { # Level 1
|
42
|
+
title # Level 2
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}
|
46
|
+
`,
|
47
|
+
},
|
48
|
+
{
|
49
|
+
title: 'Correct',
|
50
|
+
usage: [{ maxDepth: 4 }],
|
51
|
+
code: `
|
52
|
+
query deep2 {
|
53
|
+
viewer { # Level 0
|
54
|
+
albums { # Level 1
|
55
|
+
title # Level 2
|
56
|
+
}
|
57
|
+
}
|
58
|
+
}
|
59
|
+
`,
|
60
|
+
},
|
61
|
+
{
|
62
|
+
title: 'Correct (ignored field)',
|
63
|
+
usage: [{ maxDepth: 1, ignore: ['albums'] }],
|
64
|
+
code: `
|
65
|
+
query deep2 {
|
66
|
+
viewer { # Level 0
|
67
|
+
albums { # Level 1
|
68
|
+
title # Level 2
|
69
|
+
}
|
70
|
+
}
|
71
|
+
}
|
72
|
+
`,
|
73
|
+
},
|
74
|
+
],
|
75
|
+
recommended: true,
|
76
|
+
configOptions: [{ maxDepth: 7 }],
|
77
|
+
},
|
78
|
+
schema,
|
79
|
+
},
|
80
|
+
create(context) {
|
81
|
+
let siblings = null;
|
82
|
+
try {
|
83
|
+
siblings = (0, utils_1.requireSiblingsOperations)(RULE_ID, context);
|
84
|
+
}
|
85
|
+
catch (_a) {
|
86
|
+
utils_1.logger.warn(`Rule "${RULE_ID}" works best with siblings operations loaded. For more info: https://bit.ly/graphql-eslint-operations`);
|
87
|
+
}
|
88
|
+
const { maxDepth, ignore = [] } = context.options[0];
|
89
|
+
const checkFn = (0, graphql_depth_limit_1.default)(maxDepth, { ignore });
|
90
|
+
return {
|
91
|
+
'OperationDefinition, FragmentDefinition'(node) {
|
92
|
+
try {
|
93
|
+
const rawNode = node.rawNode();
|
94
|
+
const fragmentsInUse = siblings ? siblings.getFragmentsInUse(rawNode) : [];
|
95
|
+
const document = {
|
96
|
+
kind: graphql_1.Kind.DOCUMENT,
|
97
|
+
definitions: [rawNode, ...fragmentsInUse],
|
98
|
+
};
|
99
|
+
checkFn({
|
100
|
+
getDocument: () => document,
|
101
|
+
reportError(error) {
|
102
|
+
const { line, column } = error.locations[0];
|
103
|
+
const ancestors = context.getAncestors();
|
104
|
+
const token = ancestors[0].tokens.find(token => token.loc.start.line === line && token.loc.start.column === column - 1);
|
105
|
+
context.report({
|
106
|
+
loc: {
|
107
|
+
line,
|
108
|
+
column: column - 1,
|
109
|
+
},
|
110
|
+
message: error.message,
|
111
|
+
// Don't provide suggestions for fragment that can be in a separate file
|
112
|
+
...(token && {
|
113
|
+
suggest: [
|
114
|
+
{
|
115
|
+
desc: 'Remove selections',
|
116
|
+
fix(fixer) {
|
117
|
+
const sourceCode = context.getSourceCode();
|
118
|
+
const foundNode = sourceCode.getNodeByRangeIndex(token.range[0]);
|
119
|
+
const parentNode = foundNode.parent.parent;
|
120
|
+
return fixer.remove(foundNode.kind === 'Name' ? parentNode.parent : parentNode);
|
121
|
+
},
|
122
|
+
},
|
123
|
+
],
|
124
|
+
}),
|
125
|
+
});
|
126
|
+
},
|
127
|
+
});
|
128
|
+
}
|
129
|
+
catch (e) {
|
130
|
+
utils_1.logger.warn(`Rule "${RULE_ID}" check failed due to a missing siblings operations. For more info: https://bit.ly/graphql-eslint-operations`, e);
|
131
|
+
}
|
132
|
+
},
|
133
|
+
};
|
134
|
+
},
|
135
|
+
};
|