@graphql-eslint/eslint-plugin 2.3.2-alpha-5497183.0 → 2.3.2
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/index.js +91 -100
- package/index.mjs +91 -100
- package/package.json +1 -1
- package/testkit.d.ts +1 -0
package/index.js
CHANGED
@@ -275,7 +275,7 @@ function validateDoc(sourceNode, context, schema, documentNode, rules, ruleName
|
|
275
275
|
for (const error of validationErrors) {
|
276
276
|
const validateRuleName = ruleName || `[${extractRuleName(error.stack)}]`;
|
277
277
|
context.report({
|
278
|
-
loc: error.locations[0],
|
278
|
+
loc: getLocation({ start: error.locations[0] }),
|
279
279
|
message: ruleName ? error.message : `${validateRuleName} ${error.message}`,
|
280
280
|
});
|
281
281
|
}
|
@@ -612,7 +612,7 @@ const rule = {
|
|
612
612
|
],
|
613
613
|
},
|
614
614
|
messages: {
|
615
|
-
[ALPHABETIZE]: '"{{ currName }}" should be before "{{ prevName }}"
|
615
|
+
[ALPHABETIZE]: '"{{ currName }}" should be before "{{ prevName }}"',
|
616
616
|
},
|
617
617
|
schema: {
|
618
618
|
type: 'array',
|
@@ -669,16 +669,9 @@ const rule = {
|
|
669
669
|
for (const node of nodes) {
|
670
670
|
const currName = node.name.value;
|
671
671
|
if (prevName && prevName > currName) {
|
672
|
-
const { start, end } = node.name.loc;
|
673
672
|
const isVariableNode = node.kind === graphql.Kind.VARIABLE;
|
674
673
|
context.report({
|
675
|
-
loc: {
|
676
|
-
start: {
|
677
|
-
line: start.line,
|
678
|
-
column: start.column - (isVariableNode ? 2 : 1),
|
679
|
-
},
|
680
|
-
end,
|
681
|
-
},
|
674
|
+
loc: getLocation(node.loc, node.name.value, { offsetEnd: isVariableNode ? 0 : 1 }),
|
682
675
|
messageId: ALPHABETIZE,
|
683
676
|
data: isVariableNode
|
684
677
|
? {
|
@@ -1028,23 +1021,13 @@ const rule$4 = {
|
|
1028
1021
|
for (const field of node.fields) {
|
1029
1022
|
const fieldName = field.name.value;
|
1030
1023
|
if (fieldName.toLowerCase().startsWith(lowerTypeName)) {
|
1031
|
-
const { start } = field.loc;
|
1032
1024
|
context.report({
|
1033
1025
|
data: {
|
1034
1026
|
fieldName,
|
1035
1027
|
typeName,
|
1036
1028
|
},
|
1037
1029
|
messageId: AVOID_TYPENAME_PREFIX,
|
1038
|
-
loc:
|
1039
|
-
start: {
|
1040
|
-
line: start.line,
|
1041
|
-
column: start.column - 1,
|
1042
|
-
},
|
1043
|
-
end: {
|
1044
|
-
line: start.line,
|
1045
|
-
column: start.column - 1 + lowerTypeName.length,
|
1046
|
-
},
|
1047
|
-
},
|
1030
|
+
loc: getLocation(field.loc, lowerTypeName),
|
1048
1031
|
});
|
1049
1032
|
}
|
1050
1033
|
}
|
@@ -1104,7 +1087,7 @@ const rule$5 = {
|
|
1104
1087
|
'[description.type="StringValue"]': node => {
|
1105
1088
|
if (node.description.block !== (style === 'block')) {
|
1106
1089
|
context.report({
|
1107
|
-
|
1090
|
+
loc: getLocation(node.description.loc),
|
1108
1091
|
message: `Unexpected ${wrongDescriptionType} description`,
|
1109
1092
|
});
|
1110
1093
|
}
|
@@ -1191,10 +1174,11 @@ const rule$6 = {
|
|
1191
1174
|
const shouldCheckType = node => (options.checkMutations && isMutationType(node)) || (options.checkQueries && isQueryType(node));
|
1192
1175
|
const listeners = {
|
1193
1176
|
'FieldDefinition > InputValueDefinition': node => {
|
1194
|
-
|
1177
|
+
const name = node.name.value;
|
1178
|
+
if (name !== 'input' && shouldCheckType(node.parent.parent)) {
|
1195
1179
|
context.report({
|
1196
|
-
|
1197
|
-
message: `Input "${
|
1180
|
+
loc: getLocation(node.loc, name),
|
1181
|
+
message: `Input "${name}" should be called "input"`,
|
1198
1182
|
});
|
1199
1183
|
}
|
1200
1184
|
},
|
@@ -1211,11 +1195,12 @@ const rule$6 = {
|
|
1211
1195
|
const inputValueNode = findInputType(node);
|
1212
1196
|
if (shouldCheckType(inputValueNode.parent.parent)) {
|
1213
1197
|
const mutationName = `${inputValueNode.parent.name.value}Input`;
|
1198
|
+
const name = node.name.value;
|
1214
1199
|
if ((options.caseSensitiveInputType && node.name.value !== mutationName) ||
|
1215
|
-
|
1200
|
+
name.toLowerCase() !== mutationName.toLowerCase()) {
|
1216
1201
|
context.report({
|
1217
|
-
node,
|
1218
|
-
message: `InputType "${
|
1202
|
+
loc: getLocation(node.loc, name),
|
1203
|
+
message: `InputType "${name}" name should be "${mutationName}"`,
|
1219
1204
|
});
|
1220
1205
|
}
|
1221
1206
|
}
|
@@ -1604,7 +1589,7 @@ const rule$8 = {
|
|
1604
1589
|
});
|
1605
1590
|
if (result.ok === false) {
|
1606
1591
|
context.report({
|
1607
|
-
node,
|
1592
|
+
loc: getLocation(node.loc, node.value),
|
1608
1593
|
message: result.errorMessage,
|
1609
1594
|
data: {
|
1610
1595
|
prefix,
|
@@ -1631,10 +1616,16 @@ const rule$8 = {
|
|
1631
1616
|
return {
|
1632
1617
|
Name: node => {
|
1633
1618
|
if (node.value.startsWith('_') && options.leadingUnderscore === 'forbid') {
|
1634
|
-
context.report({
|
1619
|
+
context.report({
|
1620
|
+
loc: getLocation(node.loc, node.value),
|
1621
|
+
message: 'Leading underscores are not allowed',
|
1622
|
+
});
|
1635
1623
|
}
|
1636
1624
|
if (node.value.endsWith('_') && options.trailingUnderscore === 'forbid') {
|
1637
|
-
context.report({
|
1625
|
+
context.report({
|
1626
|
+
loc: getLocation(node.loc, node.value),
|
1627
|
+
message: 'Trailing underscores are not allowed',
|
1628
|
+
});
|
1638
1629
|
}
|
1639
1630
|
},
|
1640
1631
|
ObjectTypeDefinition: node => {
|
@@ -1748,28 +1739,14 @@ const rule$9 = {
|
|
1748
1739
|
},
|
1749
1740
|
create(context) {
|
1750
1741
|
return {
|
1751
|
-
OperationDefinition(node) {
|
1752
|
-
|
1753
|
-
|
1754
|
-
|
1755
|
-
|
1756
|
-
|
1757
|
-
|
1758
|
-
|
1759
|
-
column: start.column - 1,
|
1760
|
-
line: start.line,
|
1761
|
-
},
|
1762
|
-
end: {
|
1763
|
-
column: start.column - 1 + node.operation.length,
|
1764
|
-
line: start.line,
|
1765
|
-
},
|
1766
|
-
},
|
1767
|
-
data: {
|
1768
|
-
operation: node.operation,
|
1769
|
-
},
|
1770
|
-
messageId: NO_ANONYMOUS_OPERATIONS,
|
1771
|
-
});
|
1772
|
-
}
|
1742
|
+
'OperationDefinition[name=undefined]'(node) {
|
1743
|
+
context.report({
|
1744
|
+
loc: getLocation(node.loc, node.operation),
|
1745
|
+
data: {
|
1746
|
+
operation: node.operation,
|
1747
|
+
},
|
1748
|
+
messageId: NO_ANONYMOUS_OPERATIONS,
|
1749
|
+
});
|
1773
1750
|
},
|
1774
1751
|
};
|
1775
1752
|
},
|
@@ -1876,8 +1853,8 @@ const rule$b = {
|
|
1876
1853
|
mutation {
|
1877
1854
|
changeSomething(
|
1878
1855
|
type: OLD # This is deprecated, so you'll get an error
|
1879
|
-
) {
|
1880
|
-
...
|
1856
|
+
) {
|
1857
|
+
...
|
1881
1858
|
}
|
1882
1859
|
}
|
1883
1860
|
`,
|
@@ -1915,8 +1892,9 @@ const rule$b = {
|
|
1915
1892
|
const typeInfo = node.typeInfo();
|
1916
1893
|
if (typeInfo && typeInfo.enumValue) {
|
1917
1894
|
if (typeInfo.enumValue.isDeprecated) {
|
1895
|
+
const enumValueName = node.value;
|
1918
1896
|
context.report({
|
1919
|
-
loc: node.loc,
|
1897
|
+
loc: getLocation(node.loc, enumValueName),
|
1920
1898
|
messageId: NO_DEPRECATED,
|
1921
1899
|
data: {
|
1922
1900
|
type: 'enum value',
|
@@ -1931,8 +1909,9 @@ const rule$b = {
|
|
1931
1909
|
const typeInfo = node.typeInfo();
|
1932
1910
|
if (typeInfo && typeInfo.fieldDef) {
|
1933
1911
|
if (typeInfo.fieldDef.isDeprecated) {
|
1912
|
+
const fieldName = node.name.value;
|
1934
1913
|
context.report({
|
1935
|
-
loc: node.loc,
|
1914
|
+
loc: getLocation(node.loc, fieldName),
|
1936
1915
|
messageId: NO_DEPRECATED,
|
1937
1916
|
data: {
|
1938
1917
|
type: 'field',
|
@@ -2137,7 +2116,7 @@ const rule$e = {
|
|
2137
2116
|
const typeName = node.name.value;
|
2138
2117
|
if (!reachableTypes.has(typeName)) {
|
2139
2118
|
context.report({
|
2140
|
-
node,
|
2119
|
+
loc: getLocation(node.name.loc, typeName, { offsetStart: node.kind === graphql.Kind.DIRECTIVE_DEFINITION ? 2 : 1 }),
|
2141
2120
|
messageId: UNREACHABLE_TYPE,
|
2142
2121
|
data: { typeName },
|
2143
2122
|
fix: fixer => fixer.remove(node),
|
@@ -2235,7 +2214,7 @@ const rule$f = {
|
|
2235
2214
|
return;
|
2236
2215
|
}
|
2237
2216
|
context.report({
|
2238
|
-
node,
|
2217
|
+
loc: getLocation(node.loc, fieldName),
|
2239
2218
|
messageId: UNUSED_FIELD,
|
2240
2219
|
data: { fieldName },
|
2241
2220
|
fix(fixer) {
|
@@ -2390,10 +2369,10 @@ const rule$g = {
|
|
2390
2369
|
],
|
2391
2370
|
},
|
2392
2371
|
messages: {
|
2393
|
-
[MESSAGE_REQUIRE_DATE]: 'Directive "@deprecated" must have a deletion date
|
2394
|
-
[MESSAGE_INVALID_FORMAT]: 'Deletion date must be in format "DD/MM/YYYY"
|
2395
|
-
[MESSAGE_INVALID_DATE]: 'Invalid "{{ deletionDate }}" deletion date
|
2396
|
-
[MESSAGE_CAN_BE_REMOVED]: '"{{ nodeName }}" сan be removed
|
2372
|
+
[MESSAGE_REQUIRE_DATE]: 'Directive "@deprecated" must have a deletion date',
|
2373
|
+
[MESSAGE_INVALID_FORMAT]: 'Deletion date must be in format "DD/MM/YYYY"',
|
2374
|
+
[MESSAGE_INVALID_DATE]: 'Invalid "{{ deletionDate }}" deletion date',
|
2375
|
+
[MESSAGE_CAN_BE_REMOVED]: '"{{ nodeName }}" сan be removed',
|
2397
2376
|
},
|
2398
2377
|
schema: [
|
2399
2378
|
{
|
@@ -2414,13 +2393,16 @@ const rule$g = {
|
|
2414
2393
|
const argName = ((_a = context.options[0]) === null || _a === void 0 ? void 0 : _a.argumentName) || 'deletionDate';
|
2415
2394
|
const deletionDateNode = node.arguments.find(arg => arg.name.value === argName);
|
2416
2395
|
if (!deletionDateNode) {
|
2417
|
-
context.report({
|
2396
|
+
context.report({
|
2397
|
+
loc: getLocation(node.loc, node.name.value, { offsetEnd: 0 }),
|
2398
|
+
messageId: MESSAGE_REQUIRE_DATE,
|
2399
|
+
});
|
2418
2400
|
return;
|
2419
2401
|
}
|
2420
2402
|
const deletionDate = valueFromNode(deletionDateNode.value);
|
2421
2403
|
const isValidDate = DATE_REGEX.test(deletionDate);
|
2422
2404
|
if (!isValidDate) {
|
2423
|
-
context.report({ node:
|
2405
|
+
context.report({ node: deletionDateNode.value, messageId: MESSAGE_INVALID_FORMAT });
|
2424
2406
|
return;
|
2425
2407
|
}
|
2426
2408
|
let [day, month, year] = deletionDate.split('/');
|
@@ -2429,7 +2411,7 @@ const rule$g = {
|
|
2429
2411
|
const deletionDateInMS = Date.parse(`${year}-${month}-${day}`);
|
2430
2412
|
if (Number.isNaN(deletionDateInMS)) {
|
2431
2413
|
context.report({
|
2432
|
-
node:
|
2414
|
+
node: deletionDateNode.value,
|
2433
2415
|
messageId: MESSAGE_INVALID_DATE,
|
2434
2416
|
data: {
|
2435
2417
|
deletionDate,
|
@@ -2440,7 +2422,7 @@ const rule$g = {
|
|
2440
2422
|
const canRemove = Date.now() > deletionDateInMS;
|
2441
2423
|
if (canRemove) {
|
2442
2424
|
context.report({
|
2443
|
-
node
|
2425
|
+
node,
|
2444
2426
|
messageId: MESSAGE_CAN_BE_REMOVED,
|
2445
2427
|
data: {
|
2446
2428
|
nodeName: node.parent.name.value,
|
@@ -2491,17 +2473,15 @@ const rule$h = {
|
|
2491
2473
|
},
|
2492
2474
|
create(context) {
|
2493
2475
|
return {
|
2494
|
-
Directive(node) {
|
2495
|
-
|
2496
|
-
|
2497
|
-
|
2498
|
-
|
2499
|
-
|
2500
|
-
|
2501
|
-
|
2502
|
-
|
2503
|
-
});
|
2504
|
-
}
|
2476
|
+
'Directive[name.value=deprecated]'(node) {
|
2477
|
+
const args = node.arguments || [];
|
2478
|
+
const reasonArg = args.find(arg => arg.name && arg.name.value === 'reason');
|
2479
|
+
const value = reasonArg ? String(valueFromNode(reasonArg.value) || '').trim() : null;
|
2480
|
+
if (!value) {
|
2481
|
+
context.report({
|
2482
|
+
loc: getLocation(node.loc, node.name.value, { offsetEnd: 0 }),
|
2483
|
+
message: 'Directive "@deprecated" must have a reason!',
|
2484
|
+
});
|
2505
2485
|
}
|
2506
2486
|
},
|
2507
2487
|
};
|
@@ -2524,20 +2504,8 @@ const DESCRIBABLE_NODES = [
|
|
2524
2504
|
function verifyRule(context, node) {
|
2525
2505
|
if (node) {
|
2526
2506
|
if (!node.description || !node.description.value || node.description.value.trim().length === 0) {
|
2527
|
-
const { start, end } = ('name' in node ? node.name : node).loc;
|
2528
2507
|
context.report({
|
2529
|
-
loc:
|
2530
|
-
start: {
|
2531
|
-
line: start.line,
|
2532
|
-
column: start.column - 1,
|
2533
|
-
},
|
2534
|
-
end: {
|
2535
|
-
line: end.line,
|
2536
|
-
column:
|
2537
|
-
// node.name don't exist on SchemaDefinition
|
2538
|
-
'name' in node ? end.column - 1 + node.name.value.length : end.column,
|
2539
|
-
},
|
2540
|
-
},
|
2508
|
+
loc: getLocation(('name' in node ? node.name : node).loc, 'name' in node ? node.name.value : 'schema'),
|
2541
2509
|
messageId: REQUIRE_DESCRIPTION_ERROR,
|
2542
2510
|
data: {
|
2543
2511
|
nodeType: node.kind,
|
@@ -2658,18 +2626,22 @@ const rule$j = {
|
|
2658
2626
|
if (!mutationType || !queryType) {
|
2659
2627
|
return {};
|
2660
2628
|
}
|
2661
|
-
const selector =
|
2629
|
+
const selector = [
|
2630
|
+
`:matches(${graphql.Kind.OBJECT_TYPE_DEFINITION}, ${graphql.Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}]`,
|
2631
|
+
'>',
|
2632
|
+
graphql.Kind.FIELD_DEFINITION,
|
2633
|
+
graphql.Kind.NAMED_TYPE,
|
2634
|
+
].join(' ');
|
2662
2635
|
return {
|
2663
2636
|
[selector](node) {
|
2664
|
-
const
|
2665
|
-
const typeName = getTypeName(rawNode);
|
2637
|
+
const typeName = node.name.value;
|
2666
2638
|
const graphQLType = schema.getType(typeName);
|
2667
2639
|
if (graphql.isObjectType(graphQLType)) {
|
2668
2640
|
const { fields } = graphQLType.astNode;
|
2669
2641
|
const hasQueryType = fields.some(field => getTypeName(field) === queryType.name);
|
2670
2642
|
if (!hasQueryType) {
|
2671
2643
|
context.report({
|
2672
|
-
node,
|
2644
|
+
loc: getLocation(node.loc, typeName),
|
2673
2645
|
message: `Mutation result type "${graphQLType.name}" must contain field of type "${queryType.name}".`,
|
2674
2646
|
});
|
2675
2647
|
}
|
@@ -3008,7 +2980,7 @@ const rule$l = {
|
|
3008
2980
|
getDocument: () => document,
|
3009
2981
|
reportError: (error) => {
|
3010
2982
|
context.report({
|
3011
|
-
loc: error.locations[0],
|
2983
|
+
loc: getLocation({ start: error.locations[0] }),
|
3012
2984
|
message: error.message,
|
3013
2985
|
});
|
3014
2986
|
},
|
@@ -3164,15 +3136,16 @@ const rule$m = {
|
|
3164
3136
|
}
|
3165
3137
|
return isValidIdName && isValidIdType;
|
3166
3138
|
});
|
3139
|
+
const typeName = node.name.value;
|
3167
3140
|
// Usually, there should be only one unique identifier field per type.
|
3168
3141
|
// Some clients allow multiple fields to be used. If more people need this,
|
3169
3142
|
// we can extend this rule later.
|
3170
3143
|
if (validIds.length !== 1) {
|
3171
3144
|
context.report({
|
3172
|
-
node,
|
3173
|
-
message:
|
3145
|
+
loc: getLocation(node.name.loc, typeName),
|
3146
|
+
message: `{{ typeName }} must have exactly one non-nullable unique identifier. Accepted name(s): {{ acceptedNamesString }} ; Accepted type(s): {{ acceptedTypesString }}`,
|
3174
3147
|
data: {
|
3175
|
-
|
3148
|
+
typeName,
|
3176
3149
|
acceptedNamesString: options.acceptedIdNames.join(','),
|
3177
3150
|
acceptedTypesString: options.acceptedIdTypes.join(','),
|
3178
3151
|
},
|
@@ -3768,7 +3741,25 @@ class GraphQLRuleTester extends eslint.RuleTester {
|
|
3768
3741
|
return fs.readFileSync(path.resolve(__dirname, `../tests/mocks/${path$1}`), 'utf-8');
|
3769
3742
|
}
|
3770
3743
|
runGraphQLTests(name, rule, tests) {
|
3771
|
-
|
3744
|
+
const ruleTests = eslint.Linter.version.startsWith('8')
|
3745
|
+
? tests
|
3746
|
+
: {
|
3747
|
+
valid: tests.valid.map(test => {
|
3748
|
+
if (typeof test === 'string') {
|
3749
|
+
return test;
|
3750
|
+
}
|
3751
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
3752
|
+
const { name, ...testCaseOptions } = test;
|
3753
|
+
return testCaseOptions;
|
3754
|
+
}),
|
3755
|
+
invalid: tests.invalid.map(test => {
|
3756
|
+
// ESLint 7 throws an error on CI - Unexpected top-level property "name"
|
3757
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
3758
|
+
const { name, ...testCaseOptions } = test;
|
3759
|
+
return testCaseOptions;
|
3760
|
+
}),
|
3761
|
+
};
|
3762
|
+
super.run(name, rule, ruleTests);
|
3772
3763
|
// Skip snapshot testing if `expect` variable is not defined
|
3773
3764
|
if (typeof expect === 'undefined') {
|
3774
3765
|
return;
|
package/index.mjs
CHANGED
@@ -269,7 +269,7 @@ function validateDoc(sourceNode, context, schema, documentNode, rules, ruleName
|
|
269
269
|
for (const error of validationErrors) {
|
270
270
|
const validateRuleName = ruleName || `[${extractRuleName(error.stack)}]`;
|
271
271
|
context.report({
|
272
|
-
loc: error.locations[0],
|
272
|
+
loc: getLocation({ start: error.locations[0] }),
|
273
273
|
message: ruleName ? error.message : `${validateRuleName} ${error.message}`,
|
274
274
|
});
|
275
275
|
}
|
@@ -606,7 +606,7 @@ const rule = {
|
|
606
606
|
],
|
607
607
|
},
|
608
608
|
messages: {
|
609
|
-
[ALPHABETIZE]: '"{{ currName }}" should be before "{{ prevName }}"
|
609
|
+
[ALPHABETIZE]: '"{{ currName }}" should be before "{{ prevName }}"',
|
610
610
|
},
|
611
611
|
schema: {
|
612
612
|
type: 'array',
|
@@ -663,16 +663,9 @@ const rule = {
|
|
663
663
|
for (const node of nodes) {
|
664
664
|
const currName = node.name.value;
|
665
665
|
if (prevName && prevName > currName) {
|
666
|
-
const { start, end } = node.name.loc;
|
667
666
|
const isVariableNode = node.kind === Kind.VARIABLE;
|
668
667
|
context.report({
|
669
|
-
loc: {
|
670
|
-
start: {
|
671
|
-
line: start.line,
|
672
|
-
column: start.column - (isVariableNode ? 2 : 1),
|
673
|
-
},
|
674
|
-
end,
|
675
|
-
},
|
668
|
+
loc: getLocation(node.loc, node.name.value, { offsetEnd: isVariableNode ? 0 : 1 }),
|
676
669
|
messageId: ALPHABETIZE,
|
677
670
|
data: isVariableNode
|
678
671
|
? {
|
@@ -1022,23 +1015,13 @@ const rule$4 = {
|
|
1022
1015
|
for (const field of node.fields) {
|
1023
1016
|
const fieldName = field.name.value;
|
1024
1017
|
if (fieldName.toLowerCase().startsWith(lowerTypeName)) {
|
1025
|
-
const { start } = field.loc;
|
1026
1018
|
context.report({
|
1027
1019
|
data: {
|
1028
1020
|
fieldName,
|
1029
1021
|
typeName,
|
1030
1022
|
},
|
1031
1023
|
messageId: AVOID_TYPENAME_PREFIX,
|
1032
|
-
loc:
|
1033
|
-
start: {
|
1034
|
-
line: start.line,
|
1035
|
-
column: start.column - 1,
|
1036
|
-
},
|
1037
|
-
end: {
|
1038
|
-
line: start.line,
|
1039
|
-
column: start.column - 1 + lowerTypeName.length,
|
1040
|
-
},
|
1041
|
-
},
|
1024
|
+
loc: getLocation(field.loc, lowerTypeName),
|
1042
1025
|
});
|
1043
1026
|
}
|
1044
1027
|
}
|
@@ -1098,7 +1081,7 @@ const rule$5 = {
|
|
1098
1081
|
'[description.type="StringValue"]': node => {
|
1099
1082
|
if (node.description.block !== (style === 'block')) {
|
1100
1083
|
context.report({
|
1101
|
-
|
1084
|
+
loc: getLocation(node.description.loc),
|
1102
1085
|
message: `Unexpected ${wrongDescriptionType} description`,
|
1103
1086
|
});
|
1104
1087
|
}
|
@@ -1185,10 +1168,11 @@ const rule$6 = {
|
|
1185
1168
|
const shouldCheckType = node => (options.checkMutations && isMutationType(node)) || (options.checkQueries && isQueryType(node));
|
1186
1169
|
const listeners = {
|
1187
1170
|
'FieldDefinition > InputValueDefinition': node => {
|
1188
|
-
|
1171
|
+
const name = node.name.value;
|
1172
|
+
if (name !== 'input' && shouldCheckType(node.parent.parent)) {
|
1189
1173
|
context.report({
|
1190
|
-
|
1191
|
-
message: `Input "${
|
1174
|
+
loc: getLocation(node.loc, name),
|
1175
|
+
message: `Input "${name}" should be called "input"`,
|
1192
1176
|
});
|
1193
1177
|
}
|
1194
1178
|
},
|
@@ -1205,11 +1189,12 @@ const rule$6 = {
|
|
1205
1189
|
const inputValueNode = findInputType(node);
|
1206
1190
|
if (shouldCheckType(inputValueNode.parent.parent)) {
|
1207
1191
|
const mutationName = `${inputValueNode.parent.name.value}Input`;
|
1192
|
+
const name = node.name.value;
|
1208
1193
|
if ((options.caseSensitiveInputType && node.name.value !== mutationName) ||
|
1209
|
-
|
1194
|
+
name.toLowerCase() !== mutationName.toLowerCase()) {
|
1210
1195
|
context.report({
|
1211
|
-
node,
|
1212
|
-
message: `InputType "${
|
1196
|
+
loc: getLocation(node.loc, name),
|
1197
|
+
message: `InputType "${name}" name should be "${mutationName}"`,
|
1213
1198
|
});
|
1214
1199
|
}
|
1215
1200
|
}
|
@@ -1598,7 +1583,7 @@ const rule$8 = {
|
|
1598
1583
|
});
|
1599
1584
|
if (result.ok === false) {
|
1600
1585
|
context.report({
|
1601
|
-
node,
|
1586
|
+
loc: getLocation(node.loc, node.value),
|
1602
1587
|
message: result.errorMessage,
|
1603
1588
|
data: {
|
1604
1589
|
prefix,
|
@@ -1625,10 +1610,16 @@ const rule$8 = {
|
|
1625
1610
|
return {
|
1626
1611
|
Name: node => {
|
1627
1612
|
if (node.value.startsWith('_') && options.leadingUnderscore === 'forbid') {
|
1628
|
-
context.report({
|
1613
|
+
context.report({
|
1614
|
+
loc: getLocation(node.loc, node.value),
|
1615
|
+
message: 'Leading underscores are not allowed',
|
1616
|
+
});
|
1629
1617
|
}
|
1630
1618
|
if (node.value.endsWith('_') && options.trailingUnderscore === 'forbid') {
|
1631
|
-
context.report({
|
1619
|
+
context.report({
|
1620
|
+
loc: getLocation(node.loc, node.value),
|
1621
|
+
message: 'Trailing underscores are not allowed',
|
1622
|
+
});
|
1632
1623
|
}
|
1633
1624
|
},
|
1634
1625
|
ObjectTypeDefinition: node => {
|
@@ -1742,28 +1733,14 @@ const rule$9 = {
|
|
1742
1733
|
},
|
1743
1734
|
create(context) {
|
1744
1735
|
return {
|
1745
|
-
OperationDefinition(node) {
|
1746
|
-
|
1747
|
-
|
1748
|
-
|
1749
|
-
|
1750
|
-
|
1751
|
-
|
1752
|
-
|
1753
|
-
column: start.column - 1,
|
1754
|
-
line: start.line,
|
1755
|
-
},
|
1756
|
-
end: {
|
1757
|
-
column: start.column - 1 + node.operation.length,
|
1758
|
-
line: start.line,
|
1759
|
-
},
|
1760
|
-
},
|
1761
|
-
data: {
|
1762
|
-
operation: node.operation,
|
1763
|
-
},
|
1764
|
-
messageId: NO_ANONYMOUS_OPERATIONS,
|
1765
|
-
});
|
1766
|
-
}
|
1736
|
+
'OperationDefinition[name=undefined]'(node) {
|
1737
|
+
context.report({
|
1738
|
+
loc: getLocation(node.loc, node.operation),
|
1739
|
+
data: {
|
1740
|
+
operation: node.operation,
|
1741
|
+
},
|
1742
|
+
messageId: NO_ANONYMOUS_OPERATIONS,
|
1743
|
+
});
|
1767
1744
|
},
|
1768
1745
|
};
|
1769
1746
|
},
|
@@ -1870,8 +1847,8 @@ const rule$b = {
|
|
1870
1847
|
mutation {
|
1871
1848
|
changeSomething(
|
1872
1849
|
type: OLD # This is deprecated, so you'll get an error
|
1873
|
-
) {
|
1874
|
-
...
|
1850
|
+
) {
|
1851
|
+
...
|
1875
1852
|
}
|
1876
1853
|
}
|
1877
1854
|
`,
|
@@ -1909,8 +1886,9 @@ const rule$b = {
|
|
1909
1886
|
const typeInfo = node.typeInfo();
|
1910
1887
|
if (typeInfo && typeInfo.enumValue) {
|
1911
1888
|
if (typeInfo.enumValue.isDeprecated) {
|
1889
|
+
const enumValueName = node.value;
|
1912
1890
|
context.report({
|
1913
|
-
loc: node.loc,
|
1891
|
+
loc: getLocation(node.loc, enumValueName),
|
1914
1892
|
messageId: NO_DEPRECATED,
|
1915
1893
|
data: {
|
1916
1894
|
type: 'enum value',
|
@@ -1925,8 +1903,9 @@ const rule$b = {
|
|
1925
1903
|
const typeInfo = node.typeInfo();
|
1926
1904
|
if (typeInfo && typeInfo.fieldDef) {
|
1927
1905
|
if (typeInfo.fieldDef.isDeprecated) {
|
1906
|
+
const fieldName = node.name.value;
|
1928
1907
|
context.report({
|
1929
|
-
loc: node.loc,
|
1908
|
+
loc: getLocation(node.loc, fieldName),
|
1930
1909
|
messageId: NO_DEPRECATED,
|
1931
1910
|
data: {
|
1932
1911
|
type: 'field',
|
@@ -2131,7 +2110,7 @@ const rule$e = {
|
|
2131
2110
|
const typeName = node.name.value;
|
2132
2111
|
if (!reachableTypes.has(typeName)) {
|
2133
2112
|
context.report({
|
2134
|
-
node,
|
2113
|
+
loc: getLocation(node.name.loc, typeName, { offsetStart: node.kind === Kind.DIRECTIVE_DEFINITION ? 2 : 1 }),
|
2135
2114
|
messageId: UNREACHABLE_TYPE,
|
2136
2115
|
data: { typeName },
|
2137
2116
|
fix: fixer => fixer.remove(node),
|
@@ -2229,7 +2208,7 @@ const rule$f = {
|
|
2229
2208
|
return;
|
2230
2209
|
}
|
2231
2210
|
context.report({
|
2232
|
-
node,
|
2211
|
+
loc: getLocation(node.loc, fieldName),
|
2233
2212
|
messageId: UNUSED_FIELD,
|
2234
2213
|
data: { fieldName },
|
2235
2214
|
fix(fixer) {
|
@@ -2384,10 +2363,10 @@ const rule$g = {
|
|
2384
2363
|
],
|
2385
2364
|
},
|
2386
2365
|
messages: {
|
2387
|
-
[MESSAGE_REQUIRE_DATE]: 'Directive "@deprecated" must have a deletion date
|
2388
|
-
[MESSAGE_INVALID_FORMAT]: 'Deletion date must be in format "DD/MM/YYYY"
|
2389
|
-
[MESSAGE_INVALID_DATE]: 'Invalid "{{ deletionDate }}" deletion date
|
2390
|
-
[MESSAGE_CAN_BE_REMOVED]: '"{{ nodeName }}" сan be removed
|
2366
|
+
[MESSAGE_REQUIRE_DATE]: 'Directive "@deprecated" must have a deletion date',
|
2367
|
+
[MESSAGE_INVALID_FORMAT]: 'Deletion date must be in format "DD/MM/YYYY"',
|
2368
|
+
[MESSAGE_INVALID_DATE]: 'Invalid "{{ deletionDate }}" deletion date',
|
2369
|
+
[MESSAGE_CAN_BE_REMOVED]: '"{{ nodeName }}" сan be removed',
|
2391
2370
|
},
|
2392
2371
|
schema: [
|
2393
2372
|
{
|
@@ -2408,13 +2387,16 @@ const rule$g = {
|
|
2408
2387
|
const argName = ((_a = context.options[0]) === null || _a === void 0 ? void 0 : _a.argumentName) || 'deletionDate';
|
2409
2388
|
const deletionDateNode = node.arguments.find(arg => arg.name.value === argName);
|
2410
2389
|
if (!deletionDateNode) {
|
2411
|
-
context.report({
|
2390
|
+
context.report({
|
2391
|
+
loc: getLocation(node.loc, node.name.value, { offsetEnd: 0 }),
|
2392
|
+
messageId: MESSAGE_REQUIRE_DATE,
|
2393
|
+
});
|
2412
2394
|
return;
|
2413
2395
|
}
|
2414
2396
|
const deletionDate = valueFromNode(deletionDateNode.value);
|
2415
2397
|
const isValidDate = DATE_REGEX.test(deletionDate);
|
2416
2398
|
if (!isValidDate) {
|
2417
|
-
context.report({ node:
|
2399
|
+
context.report({ node: deletionDateNode.value, messageId: MESSAGE_INVALID_FORMAT });
|
2418
2400
|
return;
|
2419
2401
|
}
|
2420
2402
|
let [day, month, year] = deletionDate.split('/');
|
@@ -2423,7 +2405,7 @@ const rule$g = {
|
|
2423
2405
|
const deletionDateInMS = Date.parse(`${year}-${month}-${day}`);
|
2424
2406
|
if (Number.isNaN(deletionDateInMS)) {
|
2425
2407
|
context.report({
|
2426
|
-
node:
|
2408
|
+
node: deletionDateNode.value,
|
2427
2409
|
messageId: MESSAGE_INVALID_DATE,
|
2428
2410
|
data: {
|
2429
2411
|
deletionDate,
|
@@ -2434,7 +2416,7 @@ const rule$g = {
|
|
2434
2416
|
const canRemove = Date.now() > deletionDateInMS;
|
2435
2417
|
if (canRemove) {
|
2436
2418
|
context.report({
|
2437
|
-
node
|
2419
|
+
node,
|
2438
2420
|
messageId: MESSAGE_CAN_BE_REMOVED,
|
2439
2421
|
data: {
|
2440
2422
|
nodeName: node.parent.name.value,
|
@@ -2485,17 +2467,15 @@ const rule$h = {
|
|
2485
2467
|
},
|
2486
2468
|
create(context) {
|
2487
2469
|
return {
|
2488
|
-
Directive(node) {
|
2489
|
-
|
2490
|
-
|
2491
|
-
|
2492
|
-
|
2493
|
-
|
2494
|
-
|
2495
|
-
|
2496
|
-
|
2497
|
-
});
|
2498
|
-
}
|
2470
|
+
'Directive[name.value=deprecated]'(node) {
|
2471
|
+
const args = node.arguments || [];
|
2472
|
+
const reasonArg = args.find(arg => arg.name && arg.name.value === 'reason');
|
2473
|
+
const value = reasonArg ? String(valueFromNode(reasonArg.value) || '').trim() : null;
|
2474
|
+
if (!value) {
|
2475
|
+
context.report({
|
2476
|
+
loc: getLocation(node.loc, node.name.value, { offsetEnd: 0 }),
|
2477
|
+
message: 'Directive "@deprecated" must have a reason!',
|
2478
|
+
});
|
2499
2479
|
}
|
2500
2480
|
},
|
2501
2481
|
};
|
@@ -2518,20 +2498,8 @@ const DESCRIBABLE_NODES = [
|
|
2518
2498
|
function verifyRule(context, node) {
|
2519
2499
|
if (node) {
|
2520
2500
|
if (!node.description || !node.description.value || node.description.value.trim().length === 0) {
|
2521
|
-
const { start, end } = ('name' in node ? node.name : node).loc;
|
2522
2501
|
context.report({
|
2523
|
-
loc:
|
2524
|
-
start: {
|
2525
|
-
line: start.line,
|
2526
|
-
column: start.column - 1,
|
2527
|
-
},
|
2528
|
-
end: {
|
2529
|
-
line: end.line,
|
2530
|
-
column:
|
2531
|
-
// node.name don't exist on SchemaDefinition
|
2532
|
-
'name' in node ? end.column - 1 + node.name.value.length : end.column,
|
2533
|
-
},
|
2534
|
-
},
|
2502
|
+
loc: getLocation(('name' in node ? node.name : node).loc, 'name' in node ? node.name.value : 'schema'),
|
2535
2503
|
messageId: REQUIRE_DESCRIPTION_ERROR,
|
2536
2504
|
data: {
|
2537
2505
|
nodeType: node.kind,
|
@@ -2652,18 +2620,22 @@ const rule$j = {
|
|
2652
2620
|
if (!mutationType || !queryType) {
|
2653
2621
|
return {};
|
2654
2622
|
}
|
2655
|
-
const selector =
|
2623
|
+
const selector = [
|
2624
|
+
`:matches(${Kind.OBJECT_TYPE_DEFINITION}, ${Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}]`,
|
2625
|
+
'>',
|
2626
|
+
Kind.FIELD_DEFINITION,
|
2627
|
+
Kind.NAMED_TYPE,
|
2628
|
+
].join(' ');
|
2656
2629
|
return {
|
2657
2630
|
[selector](node) {
|
2658
|
-
const
|
2659
|
-
const typeName = getTypeName(rawNode);
|
2631
|
+
const typeName = node.name.value;
|
2660
2632
|
const graphQLType = schema.getType(typeName);
|
2661
2633
|
if (isObjectType$1(graphQLType)) {
|
2662
2634
|
const { fields } = graphQLType.astNode;
|
2663
2635
|
const hasQueryType = fields.some(field => getTypeName(field) === queryType.name);
|
2664
2636
|
if (!hasQueryType) {
|
2665
2637
|
context.report({
|
2666
|
-
node,
|
2638
|
+
loc: getLocation(node.loc, typeName),
|
2667
2639
|
message: `Mutation result type "${graphQLType.name}" must contain field of type "${queryType.name}".`,
|
2668
2640
|
});
|
2669
2641
|
}
|
@@ -3002,7 +2974,7 @@ const rule$l = {
|
|
3002
2974
|
getDocument: () => document,
|
3003
2975
|
reportError: (error) => {
|
3004
2976
|
context.report({
|
3005
|
-
loc: error.locations[0],
|
2977
|
+
loc: getLocation({ start: error.locations[0] }),
|
3006
2978
|
message: error.message,
|
3007
2979
|
});
|
3008
2980
|
},
|
@@ -3158,15 +3130,16 @@ const rule$m = {
|
|
3158
3130
|
}
|
3159
3131
|
return isValidIdName && isValidIdType;
|
3160
3132
|
});
|
3133
|
+
const typeName = node.name.value;
|
3161
3134
|
// Usually, there should be only one unique identifier field per type.
|
3162
3135
|
// Some clients allow multiple fields to be used. If more people need this,
|
3163
3136
|
// we can extend this rule later.
|
3164
3137
|
if (validIds.length !== 1) {
|
3165
3138
|
context.report({
|
3166
|
-
node,
|
3167
|
-
message:
|
3139
|
+
loc: getLocation(node.name.loc, typeName),
|
3140
|
+
message: `{{ typeName }} must have exactly one non-nullable unique identifier. Accepted name(s): {{ acceptedNamesString }} ; Accepted type(s): {{ acceptedTypesString }}`,
|
3168
3141
|
data: {
|
3169
|
-
|
3142
|
+
typeName,
|
3170
3143
|
acceptedNamesString: options.acceptedIdNames.join(','),
|
3171
3144
|
acceptedTypesString: options.acceptedIdTypes.join(','),
|
3172
3145
|
},
|
@@ -3762,7 +3735,25 @@ class GraphQLRuleTester extends RuleTester {
|
|
3762
3735
|
return readFileSync(resolve(__dirname, `../tests/mocks/${path}`), 'utf-8');
|
3763
3736
|
}
|
3764
3737
|
runGraphQLTests(name, rule, tests) {
|
3765
|
-
|
3738
|
+
const ruleTests = Linter.version.startsWith('8')
|
3739
|
+
? tests
|
3740
|
+
: {
|
3741
|
+
valid: tests.valid.map(test => {
|
3742
|
+
if (typeof test === 'string') {
|
3743
|
+
return test;
|
3744
|
+
}
|
3745
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
3746
|
+
const { name, ...testCaseOptions } = test;
|
3747
|
+
return testCaseOptions;
|
3748
|
+
}),
|
3749
|
+
invalid: tests.invalid.map(test => {
|
3750
|
+
// ESLint 7 throws an error on CI - Unexpected top-level property "name"
|
3751
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
3752
|
+
const { name, ...testCaseOptions } = test;
|
3753
|
+
return testCaseOptions;
|
3754
|
+
}),
|
3755
|
+
};
|
3756
|
+
super.run(name, rule, ruleTests);
|
3766
3757
|
// Skip snapshot testing if `expect` variable is not defined
|
3767
3758
|
if (typeof expect === 'undefined') {
|
3768
3759
|
return;
|
package/package.json
CHANGED
package/testkit.d.ts
CHANGED
@@ -6,6 +6,7 @@ export declare type GraphQLESLintRuleListener<WithTypeInfo extends boolean = fal
|
|
6
6
|
[K in keyof ASTKindToNode]?: (node: GraphQLESTreeNode<ASTKindToNode[K], WithTypeInfo>) => void;
|
7
7
|
} & Record<string, any>;
|
8
8
|
export declare type GraphQLValidTestCase<Options> = Omit<RuleTester.ValidTestCase, 'options' | 'parserOptions'> & {
|
9
|
+
name: string;
|
9
10
|
options?: Options;
|
10
11
|
parserOptions?: ParserOptions;
|
11
12
|
};
|