@graphql-eslint/eslint-plugin 4.3.0-alpha-20241205065956-601947f40654914ef0effc1cdf4fe269245fc7bc → 4.3.1-alpha-20241207204625-6a4230707a78900a6339b03afe904b9dd6c31561
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/cjs/cache.js +6 -2
- package/cjs/configs/operations-all.js +2 -2
- package/cjs/configs/schema-all.js +2 -2
- package/cjs/configs/schema-recommended.js +1 -1
- package/cjs/documents.js +13 -7
- package/cjs/estree-converter/converter.js +17 -8
- package/cjs/estree-converter/utils.js +22 -9
- package/cjs/graphql-config.js +13 -6
- package/cjs/index.d.cts +18 -4
- package/cjs/meta.js +1 -1
- package/cjs/parser.js +36 -9
- package/cjs/processor.js +48 -20
- package/cjs/rules/alphabetize/index.js +99 -47
- package/cjs/rules/description-style/index.js +10 -6
- package/cjs/rules/graphql-js-validation.js +142 -108
- package/cjs/rules/index.d.cts +18 -4
- package/cjs/rules/input-name/index.js +51 -38
- package/cjs/rules/lone-executable-definition/index.js +15 -6
- package/cjs/rules/match-document-filename/index.d.cts +4 -3
- package/cjs/rules/match-document-filename/index.js +63 -37
- package/cjs/rules/naming-convention/index.d.cts +6 -10
- package/cjs/rules/naming-convention/index.js +146 -57
- package/cjs/rules/no-anonymous-operations/index.js +8 -5
- package/cjs/rules/no-deprecated/index.js +27 -13
- package/cjs/rules/no-duplicate-fields/index.js +15 -8
- package/cjs/rules/no-hashtag-description/index.js +18 -10
- package/cjs/rules/no-one-place-fragments/index.js +17 -10
- package/cjs/rules/no-root-type/index.js +15 -8
- package/cjs/rules/no-scalar-result-type-on-mutation/index.js +20 -12
- package/cjs/rules/no-typename-prefix/index.js +25 -21
- package/cjs/rules/no-unreachable-types/index.js +34 -17
- package/cjs/rules/no-unused-fields/index.js +56 -30
- package/cjs/rules/relay-arguments/index.js +31 -13
- package/cjs/rules/relay-connection-types/index.js +31 -9
- package/cjs/rules/relay-edge-types/index.js +84 -41
- package/cjs/rules/relay-page-info/index.js +31 -14
- package/cjs/rules/require-deprecation-date/index.js +20 -9
- package/cjs/rules/require-deprecation-reason/index.js +8 -5
- package/cjs/rules/require-description/index.d.cts +79 -13
- package/cjs/rules/require-description/index.js +67 -49
- package/cjs/rules/require-field-of-type-query-in-mutation-result/index.js +21 -10
- package/cjs/rules/require-import-fragment/index.js +20 -11
- package/cjs/rules/require-nullable-fields-with-oneof/index.js +12 -5
- package/cjs/rules/require-nullable-result-in-root/index.js +32 -27
- package/cjs/rules/require-selections/index.js +88 -46
- package/cjs/rules/require-type-pattern-with-oneof/index.js +14 -10
- package/cjs/rules/selection-set-depth/index.js +19 -10
- package/cjs/rules/strict-id-in-types/index.js +32 -19
- package/cjs/rules/unique-enum-value-names/index.js +4 -3
- package/cjs/rules/unique-fragment-name/index.js +25 -18
- package/cjs/rules/unique-operation-name/index.js +5 -5
- package/cjs/schema.js +14 -8
- package/cjs/siblings.js +60 -32
- package/cjs/utils.js +23 -9
- package/esm/cache.js +6 -2
- package/esm/configs/operations-all.js +2 -2
- package/esm/configs/schema-all.js +2 -2
- package/esm/configs/schema-recommended.js +1 -1
- package/esm/documents.js +13 -7
- package/esm/estree-converter/converter.js +17 -8
- package/esm/estree-converter/utils.js +22 -9
- package/esm/graphql-config.js +13 -6
- package/esm/index.d.ts +19 -5
- package/esm/meta.js +1 -1
- package/esm/parser.js +36 -9
- package/esm/processor.js +48 -20
- package/esm/rules/alphabetize/index.js +99 -47
- package/esm/rules/description-style/index.js +10 -6
- package/esm/rules/graphql-js-validation.js +142 -108
- package/esm/rules/index.d.ts +19 -5
- package/esm/rules/input-name/index.js +51 -38
- package/esm/rules/lone-executable-definition/index.js +15 -6
- package/esm/rules/match-document-filename/index.d.ts +4 -3
- package/esm/rules/match-document-filename/index.js +63 -37
- package/esm/rules/naming-convention/index.d.ts +6 -10
- package/esm/rules/naming-convention/index.js +146 -57
- package/esm/rules/no-anonymous-operations/index.js +8 -5
- package/esm/rules/no-deprecated/index.js +27 -13
- package/esm/rules/no-duplicate-fields/index.js +15 -8
- package/esm/rules/no-hashtag-description/index.js +18 -10
- package/esm/rules/no-one-place-fragments/index.js +17 -10
- package/esm/rules/no-root-type/index.js +15 -8
- package/esm/rules/no-scalar-result-type-on-mutation/index.js +20 -12
- package/esm/rules/no-typename-prefix/index.js +25 -21
- package/esm/rules/no-unreachable-types/index.js +34 -17
- package/esm/rules/no-unused-fields/index.js +56 -30
- package/esm/rules/relay-arguments/index.js +31 -13
- package/esm/rules/relay-connection-types/index.js +31 -9
- package/esm/rules/relay-edge-types/index.js +84 -41
- package/esm/rules/relay-page-info/index.js +31 -14
- package/esm/rules/require-deprecation-date/index.js +20 -9
- package/esm/rules/require-deprecation-reason/index.js +8 -5
- package/esm/rules/require-description/index.d.ts +79 -13
- package/esm/rules/require-description/index.js +67 -49
- package/esm/rules/require-field-of-type-query-in-mutation-result/index.js +21 -10
- package/esm/rules/require-import-fragment/index.js +20 -11
- package/esm/rules/require-nullable-fields-with-oneof/index.js +12 -5
- package/esm/rules/require-nullable-result-in-root/index.js +32 -27
- package/esm/rules/require-selections/index.js +88 -46
- package/esm/rules/require-type-pattern-with-oneof/index.js +14 -10
- package/esm/rules/selection-set-depth/index.js +19 -10
- package/esm/rules/strict-id-in-types/index.js +32 -19
- package/esm/rules/unique-enum-value-names/index.js +4 -3
- package/esm/rules/unique-fragment-name/index.js +25 -18
- package/esm/rules/unique-operation-name/index.js +5 -5
- package/esm/schema.js +15 -8
- package/esm/siblings.js +60 -32
- package/esm/utils.js +23 -9
- package/index.browser.js +1838 -1135
- package/package.json +1 -1
@@ -6,50 +6,66 @@ import {
|
|
6
6
|
} from "graphql";
|
7
7
|
import { getDocumentNodeFromSchema } from "@graphql-tools/utils";
|
8
8
|
import { getTypeName, requireGraphQLSchema } from "../../utils.js";
|
9
|
-
const RULE_ID = "relay-edge-types"
|
9
|
+
const RULE_ID = "relay-edge-types";
|
10
|
+
const MESSAGE_MUST_BE_OBJECT_TYPE = "MESSAGE_MUST_BE_OBJECT_TYPE";
|
11
|
+
const MESSAGE_MISSING_EDGE_SUFFIX = "MESSAGE_MISSING_EDGE_SUFFIX";
|
12
|
+
const MESSAGE_LIST_TYPE_ONLY_EDGE_TYPE = "MESSAGE_LIST_TYPE_ONLY_EDGE_TYPE";
|
13
|
+
const MESSAGE_SHOULD_IMPLEMENTS_NODE = "MESSAGE_SHOULD_IMPLEMENTS_NODE";
|
10
14
|
let edgeTypesCache;
|
11
15
|
function getEdgeTypes(schema2) {
|
12
|
-
if (process.env.NODE_ENV !== "test" && edgeTypesCache)
|
16
|
+
if (process.env.NODE_ENV !== "test" && edgeTypesCache) {
|
13
17
|
return edgeTypesCache;
|
14
|
-
|
18
|
+
}
|
19
|
+
const edgeTypes = /* @__PURE__ */ new Set();
|
20
|
+
const visitor = {
|
15
21
|
ObjectTypeDefinition(node) {
|
16
|
-
|
22
|
+
const typeName = node.name.value;
|
23
|
+
const hasConnectionSuffix = typeName.endsWith("Connection");
|
24
|
+
if (!hasConnectionSuffix) {
|
17
25
|
return;
|
26
|
+
}
|
18
27
|
const edges = node.fields?.find((field) => field.name.value === "edges");
|
19
28
|
if (edges) {
|
20
|
-
const edgesTypeName = getTypeName(edges)
|
21
|
-
|
29
|
+
const edgesTypeName = getTypeName(edges);
|
30
|
+
const edgesType = schema2.getType(edgesTypeName);
|
31
|
+
if (isObjectType(edgesType)) {
|
32
|
+
edgeTypes.add(edgesTypeName);
|
33
|
+
}
|
22
34
|
}
|
23
35
|
}
|
24
|
-
}
|
25
|
-
|
36
|
+
};
|
37
|
+
const astNode = getDocumentNodeFromSchema(schema2);
|
38
|
+
visit(astNode, visitor);
|
39
|
+
edgeTypesCache = edgeTypes;
|
40
|
+
return edgeTypesCache;
|
26
41
|
}
|
27
42
|
const schema = {
|
28
43
|
type: "array",
|
29
44
|
maxItems: 1,
|
30
45
|
items: {
|
31
46
|
type: "object",
|
32
|
-
additionalProperties:
|
47
|
+
additionalProperties: false,
|
33
48
|
minProperties: 1,
|
34
49
|
properties: {
|
35
50
|
withEdgeSuffix: {
|
36
51
|
type: "boolean",
|
37
|
-
default:
|
52
|
+
default: true,
|
38
53
|
description: 'Edge type name must end in "Edge".'
|
39
54
|
},
|
40
55
|
shouldImplementNode: {
|
41
56
|
type: "boolean",
|
42
|
-
default:
|
57
|
+
default: true,
|
43
58
|
description: "Edge type's field `node` must implement `Node` interface."
|
44
59
|
},
|
45
60
|
listTypeCanWrapOnlyEdgeType: {
|
46
61
|
type: "boolean",
|
47
|
-
default:
|
62
|
+
default: true,
|
48
63
|
description: "A list type should only wrap an edge type."
|
49
64
|
}
|
50
65
|
}
|
51
66
|
}
|
52
|
-
}
|
67
|
+
};
|
68
|
+
const rule = {
|
53
69
|
meta: {
|
54
70
|
type: "problem",
|
55
71
|
docs: {
|
@@ -64,11 +80,10 @@ const schema = {
|
|
64
80
|
'- Edge type name must end in "Edge" _(optional)_',
|
65
81
|
"- Edge type's field `node` must implement `Node` interface _(optional)_",
|
66
82
|
"- A list type should only wrap an edge type _(optional)_"
|
67
|
-
].join(
|
68
|
-
`),
|
83
|
+
].join("\n"),
|
69
84
|
url: `https://the-guild.dev/graphql/eslint/rules/${RULE_ID}`,
|
70
|
-
isDisabledForAllConfig:
|
71
|
-
requiresSchema:
|
85
|
+
isDisabledForAllConfig: true,
|
86
|
+
requiresSchema: true,
|
72
87
|
examples: [
|
73
88
|
{
|
74
89
|
title: "Correct",
|
@@ -93,28 +108,40 @@ const schema = {
|
|
93
108
|
schema
|
94
109
|
},
|
95
110
|
create(context) {
|
96
|
-
const schema2 = requireGraphQLSchema(RULE_ID, context)
|
97
|
-
|
98
|
-
|
99
|
-
|
111
|
+
const schema2 = requireGraphQLSchema(RULE_ID, context);
|
112
|
+
const edgeTypes = getEdgeTypes(schema2);
|
113
|
+
const options = {
|
114
|
+
withEdgeSuffix: true,
|
115
|
+
shouldImplementNode: true,
|
116
|
+
listTypeCanWrapOnlyEdgeType: true,
|
100
117
|
...context.options[0]
|
101
|
-
}
|
102
|
-
|
103
|
-
|
118
|
+
};
|
119
|
+
const isNamedOrNonNullNamed = (node) => node.kind === Kind.NAMED_TYPE || node.kind === Kind.NON_NULL_TYPE && node.gqlType.kind === Kind.NAMED_TYPE;
|
120
|
+
const checkNodeField = (node) => {
|
121
|
+
const nodeField = node.fields?.find((field) => field.name.value === "node");
|
122
|
+
const message = "return either a Scalar, Enum, Object, Interface, Union, or a non-null wrapper around one of those types.";
|
123
|
+
if (!nodeField) {
|
104
124
|
context.report({
|
105
125
|
node: node.name,
|
106
126
|
message: `Edge type must contain a field \`node\` that ${message}`
|
107
127
|
});
|
108
|
-
else if (!isNamedOrNonNullNamed(nodeField.gqlType))
|
128
|
+
} else if (!isNamedOrNonNullNamed(nodeField.gqlType)) {
|
109
129
|
context.report({ node: nodeField.name, message: `Field \`node\` must ${message}` });
|
110
|
-
else if (options.shouldImplementNode) {
|
111
|
-
const nodeReturnTypeName = getTypeName(nodeField.gqlType.rawNode())
|
112
|
-
|
130
|
+
} else if (options.shouldImplementNode) {
|
131
|
+
const nodeReturnTypeName = getTypeName(nodeField.gqlType.rawNode());
|
132
|
+
const type = schema2.getType(nodeReturnTypeName);
|
133
|
+
if (!isObjectType(type)) {
|
113
134
|
return;
|
114
|
-
|
135
|
+
}
|
136
|
+
const implementsNode = type.astNode.interfaces?.some((n) => n.name.value === "Node");
|
137
|
+
if (!implementsNode) {
|
138
|
+
context.report({ node: node.name, messageId: MESSAGE_SHOULD_IMPLEMENTS_NODE });
|
139
|
+
}
|
115
140
|
}
|
116
|
-
}
|
117
|
-
|
141
|
+
};
|
142
|
+
const checkCursorField = (node) => {
|
143
|
+
const cursorField = node.fields?.find((field) => field.name.value === "cursor");
|
144
|
+
const message = "return either a String, Scalar, or a non-null wrapper wrapper around one of those types.";
|
118
145
|
if (!cursorField) {
|
119
146
|
context.report({
|
120
147
|
node: node.name,
|
@@ -123,23 +150,39 @@ const schema = {
|
|
123
150
|
return;
|
124
151
|
}
|
125
152
|
const typeName = getTypeName(cursorField.rawNode());
|
126
|
-
(!isNamedOrNonNullNamed(cursorField.gqlType) || typeName !== "String" && !isScalarType(schema2.getType(typeName)))
|
127
|
-
|
153
|
+
if (!isNamedOrNonNullNamed(cursorField.gqlType) || typeName !== "String" && !isScalarType(schema2.getType(typeName))) {
|
154
|
+
context.report({ node: cursorField.name, message: `Field \`cursor\` must ${message}` });
|
155
|
+
}
|
156
|
+
};
|
157
|
+
const listeners = {
|
128
158
|
":matches(ObjectTypeDefinition, ObjectTypeExtension)[name.value=/Connection$/] > FieldDefinition[name.value=edges] > .gqlType Name"(node) {
|
129
159
|
const type = schema2.getType(node.value);
|
130
|
-
isObjectType(type)
|
160
|
+
if (!isObjectType(type)) {
|
161
|
+
context.report({ node, messageId: MESSAGE_MUST_BE_OBJECT_TYPE });
|
162
|
+
}
|
131
163
|
},
|
132
164
|
":matches(ObjectTypeDefinition, ObjectTypeExtension)"(node) {
|
133
165
|
const typeName = node.name.value;
|
134
|
-
edgeTypes.has(typeName)
|
166
|
+
if (edgeTypes.has(typeName)) {
|
167
|
+
checkNodeField(node);
|
168
|
+
checkCursorField(node);
|
169
|
+
if (options.withEdgeSuffix && !typeName.endsWith("Edge")) {
|
170
|
+
context.report({ node: node.name, messageId: MESSAGE_MISSING_EDGE_SUFFIX });
|
171
|
+
}
|
172
|
+
}
|
135
173
|
}
|
136
174
|
};
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
175
|
+
if (options.listTypeCanWrapOnlyEdgeType) {
|
176
|
+
listeners["FieldDefinition > .gqlType"] = (node) => {
|
177
|
+
if (node.kind === Kind.LIST_TYPE || node.kind === Kind.NON_NULL_TYPE && node.gqlType.kind === Kind.LIST_TYPE) {
|
178
|
+
const typeName = getTypeName(node.rawNode());
|
179
|
+
if (!edgeTypes.has(typeName)) {
|
180
|
+
context.report({ node, messageId: MESSAGE_LIST_TYPE_ONLY_EDGE_TYPE });
|
181
|
+
}
|
182
|
+
}
|
183
|
+
};
|
184
|
+
}
|
185
|
+
return listeners;
|
143
186
|
}
|
144
187
|
};
|
145
188
|
export {
|
@@ -1,8 +1,11 @@
|
|
1
1
|
import { isScalarType, Kind } from "graphql";
|
2
2
|
import { REPORT_ON_FIRST_CHARACTER, requireGraphQLSchema } from "../../utils.js";
|
3
3
|
import { NON_OBJECT_TYPES } from "../relay-connection-types/index.js";
|
4
|
-
const RULE_ID = "relay-page-info"
|
5
|
-
|
4
|
+
const RULE_ID = "relay-page-info";
|
5
|
+
const MESSAGE_MUST_EXIST = "MESSAGE_MUST_EXIST";
|
6
|
+
const MESSAGE_MUST_BE_OBJECT_TYPE = "MESSAGE_MUST_BE_OBJECT_TYPE";
|
7
|
+
const notPageInfoTypesSelector = `:matches(${NON_OBJECT_TYPES})[name.value=PageInfo] > .name`;
|
8
|
+
let hasPageInfoChecked = false;
|
6
9
|
const rule = {
|
7
10
|
meta: {
|
8
11
|
type: "problem",
|
@@ -14,8 +17,7 @@ const rule = {
|
|
14
17
|
"- `PageInfo` must be an Object type",
|
15
18
|
"- `PageInfo` must contain fields `hasPreviousPage` and `hasNextPage`, that return non-null Boolean",
|
16
19
|
"- `PageInfo` must contain fields `startCursor` and `endCursor`, that return either String or Scalar, which can be null if there are no results"
|
17
|
-
].join(
|
18
|
-
`),
|
20
|
+
].join("\n"),
|
19
21
|
url: `https://the-guild.dev/graphql/eslint/rules/${RULE_ID}`,
|
20
22
|
examples: [
|
21
23
|
{
|
@@ -33,8 +35,8 @@ const rule = {
|
|
33
35
|
)
|
34
36
|
}
|
35
37
|
],
|
36
|
-
isDisabledForAllConfig:
|
37
|
-
requiresSchema:
|
38
|
+
isDisabledForAllConfig: true,
|
39
|
+
requiresSchema: true
|
38
40
|
},
|
39
41
|
messages: {
|
40
42
|
[MESSAGE_MUST_EXIST]: "The server must provide a `PageInfo` object.",
|
@@ -44,22 +46,34 @@ const rule = {
|
|
44
46
|
},
|
45
47
|
create(context) {
|
46
48
|
const schema = requireGraphQLSchema(RULE_ID, context);
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
49
|
+
if (process.env.NODE_ENV === "test" || !hasPageInfoChecked) {
|
50
|
+
const pageInfoType = schema.getType("PageInfo");
|
51
|
+
if (!pageInfoType) {
|
52
|
+
context.report({
|
53
|
+
loc: REPORT_ON_FIRST_CHARACTER,
|
54
|
+
messageId: MESSAGE_MUST_EXIST
|
55
|
+
});
|
56
|
+
}
|
57
|
+
hasPageInfoChecked = true;
|
58
|
+
}
|
59
|
+
return {
|
51
60
|
[notPageInfoTypesSelector](node) {
|
52
61
|
context.report({ node, messageId: MESSAGE_MUST_BE_OBJECT_TYPE });
|
53
62
|
},
|
54
63
|
"ObjectTypeDefinition[name.value=PageInfo]"(node) {
|
55
64
|
const fieldMap = Object.fromEntries(
|
56
65
|
node.fields?.map((field) => [field.name.value, field]) || []
|
57
|
-
)
|
66
|
+
);
|
67
|
+
const checkField = (fieldName, typeName) => {
|
58
68
|
const field = fieldMap[fieldName];
|
59
|
-
let isAllowedType =
|
69
|
+
let isAllowedType = false;
|
60
70
|
if (field) {
|
61
71
|
const type = field.gqlType;
|
62
|
-
typeName === "Boolean"
|
72
|
+
if (typeName === "Boolean") {
|
73
|
+
isAllowedType = type.kind === Kind.NON_NULL_TYPE && type.gqlType.kind === Kind.NAMED_TYPE && type.gqlType.name.value === "Boolean";
|
74
|
+
} else if (type.kind === Kind.NAMED_TYPE) {
|
75
|
+
isAllowedType = type.name.value === "String" || isScalarType(schema.getType(type.name.value));
|
76
|
+
}
|
63
77
|
}
|
64
78
|
if (!isAllowedType) {
|
65
79
|
const returnType = typeName === "Boolean" ? "non-null Boolean" : "either String or Scalar, which can be null if there are no results";
|
@@ -69,7 +83,10 @@ const rule = {
|
|
69
83
|
});
|
70
84
|
}
|
71
85
|
};
|
72
|
-
checkField("hasPreviousPage", "Boolean")
|
86
|
+
checkField("hasPreviousPage", "Boolean");
|
87
|
+
checkField("hasNextPage", "Boolean");
|
88
|
+
checkField("startCursor", "String");
|
89
|
+
checkField("endCursor", "String");
|
73
90
|
}
|
74
91
|
};
|
75
92
|
}
|
@@ -1,21 +1,27 @@
|
|
1
1
|
import { valueFromNode } from "../../estree-converter/index.js";
|
2
2
|
import { getNodeName } from "../../utils.js";
|
3
|
-
const DATE_REGEX = /^\d{2}\/\d{2}\/\d{4}
|
3
|
+
const DATE_REGEX = /^\d{2}\/\d{2}\/\d{4}$/;
|
4
|
+
const MESSAGE_REQUIRE_DATE = "MESSAGE_REQUIRE_DATE";
|
5
|
+
const MESSAGE_INVALID_FORMAT = "MESSAGE_INVALID_FORMAT";
|
6
|
+
const MESSAGE_INVALID_DATE = "MESSAGE_INVALID_DATE";
|
7
|
+
const MESSAGE_CAN_BE_REMOVED = "MESSAGE_CAN_BE_REMOVED";
|
8
|
+
const schema = {
|
4
9
|
type: "array",
|
5
10
|
maxItems: 1,
|
6
11
|
items: {
|
7
12
|
type: "object",
|
8
|
-
additionalProperties:
|
13
|
+
additionalProperties: false,
|
9
14
|
properties: {
|
10
15
|
argumentName: {
|
11
16
|
type: "string"
|
12
17
|
}
|
13
18
|
}
|
14
19
|
}
|
15
|
-
}
|
20
|
+
};
|
21
|
+
const rule = {
|
16
22
|
meta: {
|
17
23
|
type: "suggestion",
|
18
|
-
hasSuggestions:
|
24
|
+
hasSuggestions: true,
|
19
25
|
docs: {
|
20
26
|
category: "Schema",
|
21
27
|
description: "Require deletion date on `@deprecated` directive. Suggest removing deprecated things after deprecated date.",
|
@@ -71,7 +77,8 @@ const DATE_REGEX = /^\d{2}\/\d{2}\/\d{4}$/, MESSAGE_REQUIRE_DATE = "MESSAGE_REQU
|
|
71
77
|
create(context) {
|
72
78
|
return {
|
73
79
|
"Directive[name.value=deprecated]"(node) {
|
74
|
-
const argName = context.options[0]?.argumentName || "deletionDate"
|
80
|
+
const argName = context.options[0]?.argumentName || "deletionDate";
|
81
|
+
const deletionDateNode = node.arguments?.find((arg) => arg.name.value === argName);
|
75
82
|
if (!deletionDateNode) {
|
76
83
|
context.report({
|
77
84
|
node: node.name,
|
@@ -83,7 +90,8 @@ const DATE_REGEX = /^\d{2}\/\d{2}\/\d{4}$/, MESSAGE_REQUIRE_DATE = "MESSAGE_REQU
|
|
83
90
|
return;
|
84
91
|
}
|
85
92
|
const deletionDate = valueFromNode(deletionDateNode.value);
|
86
|
-
|
93
|
+
const isValidDate = DATE_REGEX.test(deletionDate);
|
94
|
+
if (!isValidDate) {
|
87
95
|
context.report({
|
88
96
|
node: deletionDateNode.value,
|
89
97
|
messageId: MESSAGE_INVALID_FORMAT,
|
@@ -92,7 +100,8 @@ const DATE_REGEX = /^\d{2}\/\d{2}\/\d{4}$/, MESSAGE_REQUIRE_DATE = "MESSAGE_REQU
|
|
92
100
|
return;
|
93
101
|
}
|
94
102
|
let [day, month, year] = deletionDate.split("/");
|
95
|
-
day = day.padStart(2, "0")
|
103
|
+
day = day.padStart(2, "0");
|
104
|
+
month = month.padStart(2, "0");
|
96
105
|
const deletionDateInMS = Date.parse(`${year}-${month}-${day}`);
|
97
106
|
if (Number.isNaN(deletionDateInMS)) {
|
98
107
|
context.report({
|
@@ -105,8 +114,10 @@ const DATE_REGEX = /^\d{2}\/\d{2}\/\d{4}$/, MESSAGE_REQUIRE_DATE = "MESSAGE_REQU
|
|
105
114
|
});
|
106
115
|
return;
|
107
116
|
}
|
108
|
-
|
109
|
-
|
117
|
+
const canRemove = Date.now() > deletionDateInMS;
|
118
|
+
if (canRemove) {
|
119
|
+
const { parent } = node;
|
120
|
+
const nodeName = parent.name.value;
|
110
121
|
context.report({
|
111
122
|
node: parent.name,
|
112
123
|
messageId: MESSAGE_CAN_BE_REMOVED,
|
@@ -6,7 +6,7 @@ const rule = {
|
|
6
6
|
description: "Require all deprecation directives to specify a reason.",
|
7
7
|
category: "Schema",
|
8
8
|
url: "https://the-guild.dev/graphql/eslint/rules/require-deprecation-reason",
|
9
|
-
recommended:
|
9
|
+
recommended: true,
|
10
10
|
examples: [
|
11
11
|
{
|
12
12
|
title: "Incorrect",
|
@@ -52,10 +52,13 @@ const rule = {
|
|
52
52
|
const reasonArgument = node.arguments?.find(
|
53
53
|
(arg) => arg.name.value === "reason"
|
54
54
|
);
|
55
|
-
reasonArgument && String(valueFromNode(reasonArgument.value)).trim()
|
56
|
-
|
57
|
-
|
58
|
-
|
55
|
+
const value = reasonArgument && String(valueFromNode(reasonArgument.value)).trim();
|
56
|
+
if (!value) {
|
57
|
+
context.report({
|
58
|
+
node: node.name,
|
59
|
+
message: `Deprecation reason is required for ${getNodeName(node.parent)}.`
|
60
|
+
});
|
61
|
+
}
|
59
62
|
}
|
60
63
|
};
|
61
64
|
}
|
@@ -1,25 +1,91 @@
|
|
1
|
-
import {
|
1
|
+
import { FromSchema } from 'json-schema-to-ts';
|
2
2
|
import { GraphQLESLintRule } from '../../types.js';
|
3
3
|
import 'eslint';
|
4
4
|
import 'estree';
|
5
|
+
import 'graphql';
|
5
6
|
import 'graphql-config';
|
6
|
-
import 'json-schema-to-ts';
|
7
7
|
import '../../estree-converter/types.js';
|
8
8
|
import '../../siblings.js';
|
9
9
|
import '@graphql-tools/utils';
|
10
10
|
|
11
11
|
declare const RULE_ID = "require-description";
|
12
|
-
declare const
|
13
|
-
type
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
12
|
+
declare const schema: {
|
13
|
+
readonly type: "array";
|
14
|
+
readonly minItems: 1;
|
15
|
+
readonly maxItems: 1;
|
16
|
+
readonly items: {
|
17
|
+
readonly type: "object";
|
18
|
+
readonly additionalProperties: false;
|
19
|
+
readonly minProperties: 1;
|
20
|
+
readonly properties: {
|
21
|
+
readonly OperationDefinition: {
|
22
|
+
type: "boolean";
|
23
|
+
description: string;
|
24
|
+
};
|
25
|
+
readonly ScalarTypeDefinition: {
|
26
|
+
type: "boolean";
|
27
|
+
description: string;
|
28
|
+
};
|
29
|
+
readonly ObjectTypeDefinition: {
|
30
|
+
type: "boolean";
|
31
|
+
description: string;
|
32
|
+
};
|
33
|
+
readonly FieldDefinition: {
|
34
|
+
type: "boolean";
|
35
|
+
description: string;
|
36
|
+
};
|
37
|
+
readonly InputValueDefinition: {
|
38
|
+
type: "boolean";
|
39
|
+
description: string;
|
40
|
+
};
|
41
|
+
readonly InterfaceTypeDefinition: {
|
42
|
+
type: "boolean";
|
43
|
+
description: string;
|
44
|
+
};
|
45
|
+
readonly UnionTypeDefinition: {
|
46
|
+
type: "boolean";
|
47
|
+
description: string;
|
48
|
+
};
|
49
|
+
readonly EnumTypeDefinition: {
|
50
|
+
type: "boolean";
|
51
|
+
description: string;
|
52
|
+
};
|
53
|
+
readonly EnumValueDefinition: {
|
54
|
+
type: "boolean";
|
55
|
+
description: string;
|
56
|
+
};
|
57
|
+
readonly InputObjectTypeDefinition: {
|
58
|
+
type: "boolean";
|
59
|
+
description: string;
|
60
|
+
};
|
61
|
+
readonly DirectiveDefinition: {
|
62
|
+
type: "boolean";
|
63
|
+
description: string;
|
64
|
+
};
|
65
|
+
readonly types: {
|
66
|
+
readonly type: "boolean";
|
67
|
+
readonly enum: readonly [true];
|
68
|
+
readonly description: `Includes:
|
69
|
+
${string}`;
|
70
|
+
};
|
71
|
+
readonly rootField: {
|
72
|
+
readonly type: "boolean";
|
73
|
+
readonly enum: readonly [true];
|
74
|
+
readonly description: "Definitions within `Query`, `Mutation`, and `Subscription` root types.";
|
75
|
+
};
|
76
|
+
readonly ignoredSelectors: {
|
77
|
+
readonly description: string;
|
78
|
+
readonly type: "array";
|
79
|
+
readonly uniqueItems: true;
|
80
|
+
readonly minItems: 1;
|
81
|
+
readonly items: {
|
82
|
+
readonly type: "string";
|
83
|
+
};
|
84
|
+
};
|
85
|
+
};
|
86
|
+
};
|
87
|
+
};
|
88
|
+
type RuleOptions = FromSchema<typeof schema>;
|
23
89
|
declare const rule: GraphQLESLintRule<RuleOptions>;
|
24
90
|
|
25
91
|
export { RULE_ID, type RuleOptions, rule };
|