@graphql-eslint/eslint-plugin 2.3.2-alpha-6c8a706.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/docs/rules/avoid-duplicate-fields.md +9 -7
- package/index.js +117 -115
- package/index.mjs +117 -115
- package/package.json +1 -1
- package/rules/avoid-duplicate-fields.d.ts +1 -1
- package/rules/index.d.ts +1 -1
- package/testkit.d.ts +1 -0
@@ -14,11 +14,11 @@ Checks for duplicate fields in selection set, variables in operation definition,
|
|
14
14
|
```graphql
|
15
15
|
# eslint @graphql-eslint/avoid-duplicate-fields: 'error'
|
16
16
|
|
17
|
-
query
|
17
|
+
query {
|
18
18
|
user {
|
19
|
-
name
|
19
|
+
name
|
20
20
|
email
|
21
|
-
name #
|
21
|
+
name # duplicate field
|
22
22
|
}
|
23
23
|
}
|
24
24
|
```
|
@@ -28,7 +28,7 @@ query getUserDetails {
|
|
28
28
|
```graphql
|
29
29
|
# eslint @graphql-eslint/avoid-duplicate-fields: 'error'
|
30
30
|
|
31
|
-
query
|
31
|
+
query {
|
32
32
|
users(
|
33
33
|
first: 100
|
34
34
|
skip: 50
|
@@ -45,9 +45,11 @@ query getUsers {
|
|
45
45
|
```graphql
|
46
46
|
# eslint @graphql-eslint/avoid-duplicate-fields: 'error'
|
47
47
|
|
48
|
-
query
|
49
|
-
|
50
|
-
|
48
|
+
query (
|
49
|
+
$first: Int!
|
50
|
+
$first: Int! # duplicate variable
|
51
|
+
) {
|
52
|
+
users(first: $first, skip: 50) {
|
51
53
|
id
|
52
54
|
}
|
53
55
|
}
|
package/index.js
CHANGED
@@ -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
|
? {
|
@@ -744,35 +737,22 @@ const rule = {
|
|
744
737
|
};
|
745
738
|
|
746
739
|
const AVOID_DUPLICATE_FIELDS = 'AVOID_DUPLICATE_FIELDS';
|
747
|
-
const ensureUnique = () => {
|
748
|
-
const set = new Set();
|
749
|
-
return {
|
750
|
-
add: (item, onError) => {
|
751
|
-
if (set.has(item)) {
|
752
|
-
onError();
|
753
|
-
}
|
754
|
-
else {
|
755
|
-
set.add(item);
|
756
|
-
}
|
757
|
-
},
|
758
|
-
};
|
759
|
-
};
|
760
740
|
const rule$1 = {
|
761
741
|
meta: {
|
762
742
|
type: 'suggestion',
|
763
743
|
docs: {
|
764
|
-
description:
|
744
|
+
description: `Checks for duplicate fields in selection set, variables in operation definition, or in arguments set of a field.`,
|
765
745
|
category: 'Stylistic Issues',
|
766
746
|
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-duplicate-fields.md',
|
767
747
|
examples: [
|
768
748
|
{
|
769
749
|
title: 'Incorrect',
|
770
750
|
code: /* GraphQL */ `
|
771
|
-
query
|
751
|
+
query {
|
772
752
|
user {
|
773
|
-
name
|
753
|
+
name
|
774
754
|
email
|
775
|
-
name #
|
755
|
+
name # duplicate field
|
776
756
|
}
|
777
757
|
}
|
778
758
|
`,
|
@@ -780,7 +760,7 @@ const rule$1 = {
|
|
780
760
|
{
|
781
761
|
title: 'Incorrect',
|
782
762
|
code: /* GraphQL */ `
|
783
|
-
query
|
763
|
+
query {
|
784
764
|
users(
|
785
765
|
first: 100
|
786
766
|
skip: 50
|
@@ -795,9 +775,11 @@ const rule$1 = {
|
|
795
775
|
{
|
796
776
|
title: 'Incorrect',
|
797
777
|
code: /* GraphQL */ `
|
798
|
-
query
|
799
|
-
|
800
|
-
|
778
|
+
query (
|
779
|
+
$first: Int!
|
780
|
+
$first: Int! # duplicate variable
|
781
|
+
) {
|
782
|
+
users(first: $first, skip: 50) {
|
801
783
|
id
|
802
784
|
}
|
803
785
|
}
|
@@ -806,58 +788,47 @@ const rule$1 = {
|
|
806
788
|
],
|
807
789
|
},
|
808
790
|
messages: {
|
809
|
-
[AVOID_DUPLICATE_FIELDS]: `{{ type }} "{{ fieldName }}" defined multiple times
|
791
|
+
[AVOID_DUPLICATE_FIELDS]: `{{ type }} "{{ fieldName }}" defined multiple times`,
|
810
792
|
},
|
811
793
|
schema: [],
|
812
794
|
},
|
813
795
|
create(context) {
|
796
|
+
function checkNode(usedFields, fieldName, type, node) {
|
797
|
+
if (usedFields.has(fieldName)) {
|
798
|
+
context.report({
|
799
|
+
loc: getLocation((node.kind === graphql.Kind.FIELD && node.alias ? node.alias : node).loc, fieldName, {
|
800
|
+
offsetEnd: node.kind === graphql.Kind.VARIABLE_DEFINITION ? 0 : 1,
|
801
|
+
}),
|
802
|
+
messageId: AVOID_DUPLICATE_FIELDS,
|
803
|
+
data: {
|
804
|
+
type,
|
805
|
+
fieldName,
|
806
|
+
},
|
807
|
+
});
|
808
|
+
}
|
809
|
+
else {
|
810
|
+
usedFields.add(fieldName);
|
811
|
+
}
|
812
|
+
}
|
814
813
|
return {
|
815
814
|
OperationDefinition(node) {
|
816
|
-
const
|
817
|
-
for (const
|
818
|
-
|
819
|
-
context.report({
|
820
|
-
messageId: AVOID_DUPLICATE_FIELDS,
|
821
|
-
data: {
|
822
|
-
type: 'Operation variable',
|
823
|
-
fieldName: arg.variable.name.value,
|
824
|
-
},
|
825
|
-
node: arg,
|
826
|
-
});
|
827
|
-
});
|
815
|
+
const set = new Set();
|
816
|
+
for (const varDef of node.variableDefinitions) {
|
817
|
+
checkNode(set, varDef.variable.name.value, 'Operation variable', varDef);
|
828
818
|
}
|
829
819
|
},
|
830
820
|
Field(node) {
|
831
|
-
const
|
832
|
-
for (const arg of node.arguments
|
833
|
-
|
834
|
-
context.report({
|
835
|
-
messageId: AVOID_DUPLICATE_FIELDS,
|
836
|
-
data: {
|
837
|
-
type: 'Field argument',
|
838
|
-
fieldName: arg.name.value,
|
839
|
-
},
|
840
|
-
node: arg,
|
841
|
-
});
|
842
|
-
});
|
821
|
+
const set = new Set();
|
822
|
+
for (const arg of node.arguments) {
|
823
|
+
checkNode(set, arg.name.value, 'Field argument', arg);
|
843
824
|
}
|
844
825
|
},
|
845
826
|
SelectionSet(node) {
|
846
827
|
var _a;
|
847
|
-
const
|
848
|
-
for (const selection of node.selections
|
828
|
+
const set = new Set();
|
829
|
+
for (const selection of node.selections) {
|
849
830
|
if (selection.kind === graphql.Kind.FIELD) {
|
850
|
-
|
851
|
-
uniqueCheck.add(nameToCheck, () => {
|
852
|
-
context.report({
|
853
|
-
messageId: AVOID_DUPLICATE_FIELDS,
|
854
|
-
data: {
|
855
|
-
type: 'Field',
|
856
|
-
fieldName: nameToCheck,
|
857
|
-
},
|
858
|
-
node: selection,
|
859
|
-
});
|
860
|
-
});
|
831
|
+
checkNode(set, ((_a = selection.alias) === null || _a === void 0 ? void 0 : _a.value) || selection.name.value, 'Field', selection);
|
861
832
|
}
|
862
833
|
}
|
863
834
|
},
|
@@ -1116,7 +1087,7 @@ const rule$5 = {
|
|
1116
1087
|
'[description.type="StringValue"]': node => {
|
1117
1088
|
if (node.description.block !== (style === 'block')) {
|
1118
1089
|
context.report({
|
1119
|
-
|
1090
|
+
loc: getLocation(node.description.loc),
|
1120
1091
|
message: `Unexpected ${wrongDescriptionType} description`,
|
1121
1092
|
});
|
1122
1093
|
}
|
@@ -1203,10 +1174,11 @@ const rule$6 = {
|
|
1203
1174
|
const shouldCheckType = node => (options.checkMutations && isMutationType(node)) || (options.checkQueries && isQueryType(node));
|
1204
1175
|
const listeners = {
|
1205
1176
|
'FieldDefinition > InputValueDefinition': node => {
|
1206
|
-
|
1177
|
+
const name = node.name.value;
|
1178
|
+
if (name !== 'input' && shouldCheckType(node.parent.parent)) {
|
1207
1179
|
context.report({
|
1208
|
-
|
1209
|
-
message: `Input "${
|
1180
|
+
loc: getLocation(node.loc, name),
|
1181
|
+
message: `Input "${name}" should be called "input"`,
|
1210
1182
|
});
|
1211
1183
|
}
|
1212
1184
|
},
|
@@ -1223,11 +1195,12 @@ const rule$6 = {
|
|
1223
1195
|
const inputValueNode = findInputType(node);
|
1224
1196
|
if (shouldCheckType(inputValueNode.parent.parent)) {
|
1225
1197
|
const mutationName = `${inputValueNode.parent.name.value}Input`;
|
1198
|
+
const name = node.name.value;
|
1226
1199
|
if ((options.caseSensitiveInputType && node.name.value !== mutationName) ||
|
1227
|
-
|
1200
|
+
name.toLowerCase() !== mutationName.toLowerCase()) {
|
1228
1201
|
context.report({
|
1229
|
-
node,
|
1230
|
-
message: `InputType "${
|
1202
|
+
loc: getLocation(node.loc, name),
|
1203
|
+
message: `InputType "${name}" name should be "${mutationName}"`,
|
1231
1204
|
});
|
1232
1205
|
}
|
1233
1206
|
}
|
@@ -1616,7 +1589,7 @@ const rule$8 = {
|
|
1616
1589
|
});
|
1617
1590
|
if (result.ok === false) {
|
1618
1591
|
context.report({
|
1619
|
-
node,
|
1592
|
+
loc: getLocation(node.loc, node.value),
|
1620
1593
|
message: result.errorMessage,
|
1621
1594
|
data: {
|
1622
1595
|
prefix,
|
@@ -1643,10 +1616,16 @@ const rule$8 = {
|
|
1643
1616
|
return {
|
1644
1617
|
Name: node => {
|
1645
1618
|
if (node.value.startsWith('_') && options.leadingUnderscore === 'forbid') {
|
1646
|
-
context.report({
|
1619
|
+
context.report({
|
1620
|
+
loc: getLocation(node.loc, node.value),
|
1621
|
+
message: 'Leading underscores are not allowed',
|
1622
|
+
});
|
1647
1623
|
}
|
1648
1624
|
if (node.value.endsWith('_') && options.trailingUnderscore === 'forbid') {
|
1649
|
-
context.report({
|
1625
|
+
context.report({
|
1626
|
+
loc: getLocation(node.loc, node.value),
|
1627
|
+
message: 'Trailing underscores are not allowed',
|
1628
|
+
});
|
1650
1629
|
}
|
1651
1630
|
},
|
1652
1631
|
ObjectTypeDefinition: node => {
|
@@ -1874,8 +1853,8 @@ const rule$b = {
|
|
1874
1853
|
mutation {
|
1875
1854
|
changeSomething(
|
1876
1855
|
type: OLD # This is deprecated, so you'll get an error
|
1877
|
-
) {
|
1878
|
-
...
|
1856
|
+
) {
|
1857
|
+
...
|
1879
1858
|
}
|
1880
1859
|
}
|
1881
1860
|
`,
|
@@ -1913,8 +1892,9 @@ const rule$b = {
|
|
1913
1892
|
const typeInfo = node.typeInfo();
|
1914
1893
|
if (typeInfo && typeInfo.enumValue) {
|
1915
1894
|
if (typeInfo.enumValue.isDeprecated) {
|
1895
|
+
const enumValueName = node.value;
|
1916
1896
|
context.report({
|
1917
|
-
loc: node.loc,
|
1897
|
+
loc: getLocation(node.loc, enumValueName),
|
1918
1898
|
messageId: NO_DEPRECATED,
|
1919
1899
|
data: {
|
1920
1900
|
type: 'enum value',
|
@@ -1929,8 +1909,9 @@ const rule$b = {
|
|
1929
1909
|
const typeInfo = node.typeInfo();
|
1930
1910
|
if (typeInfo && typeInfo.fieldDef) {
|
1931
1911
|
if (typeInfo.fieldDef.isDeprecated) {
|
1912
|
+
const fieldName = node.name.value;
|
1932
1913
|
context.report({
|
1933
|
-
loc: node.loc,
|
1914
|
+
loc: getLocation(node.loc, fieldName),
|
1934
1915
|
messageId: NO_DEPRECATED,
|
1935
1916
|
data: {
|
1936
1917
|
type: 'field',
|
@@ -2006,10 +1987,7 @@ const rule$c = {
|
|
2006
1987
|
if (!isEslintComment && line !== prev.line && next.kind === graphql.TokenKind.NAME && linesAfter < 2) {
|
2007
1988
|
context.report({
|
2008
1989
|
messageId: HASHTAG_COMMENT,
|
2009
|
-
loc: {
|
2010
|
-
start: { line, column },
|
2011
|
-
end: { line, column },
|
2012
|
-
},
|
1990
|
+
loc: getLocation({ start: { line, column } }),
|
2013
1991
|
});
|
2014
1992
|
}
|
2015
1993
|
}
|
@@ -2138,7 +2116,7 @@ const rule$e = {
|
|
2138
2116
|
const typeName = node.name.value;
|
2139
2117
|
if (!reachableTypes.has(typeName)) {
|
2140
2118
|
context.report({
|
2141
|
-
node,
|
2119
|
+
loc: getLocation(node.name.loc, typeName, { offsetStart: node.kind === graphql.Kind.DIRECTIVE_DEFINITION ? 2 : 1 }),
|
2142
2120
|
messageId: UNREACHABLE_TYPE,
|
2143
2121
|
data: { typeName },
|
2144
2122
|
fix: fixer => fixer.remove(node),
|
@@ -2236,7 +2214,7 @@ const rule$f = {
|
|
2236
2214
|
return;
|
2237
2215
|
}
|
2238
2216
|
context.report({
|
2239
|
-
node,
|
2217
|
+
loc: getLocation(node.loc, fieldName),
|
2240
2218
|
messageId: UNUSED_FIELD,
|
2241
2219
|
data: { fieldName },
|
2242
2220
|
fix(fixer) {
|
@@ -2391,10 +2369,10 @@ const rule$g = {
|
|
2391
2369
|
],
|
2392
2370
|
},
|
2393
2371
|
messages: {
|
2394
|
-
[MESSAGE_REQUIRE_DATE]: 'Directive "@deprecated" must have a deletion date
|
2395
|
-
[MESSAGE_INVALID_FORMAT]: 'Deletion date must be in format "DD/MM/YYYY"
|
2396
|
-
[MESSAGE_INVALID_DATE]: 'Invalid "{{ deletionDate }}" deletion date
|
2397
|
-
[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',
|
2398
2376
|
},
|
2399
2377
|
schema: [
|
2400
2378
|
{
|
@@ -2415,13 +2393,16 @@ const rule$g = {
|
|
2415
2393
|
const argName = ((_a = context.options[0]) === null || _a === void 0 ? void 0 : _a.argumentName) || 'deletionDate';
|
2416
2394
|
const deletionDateNode = node.arguments.find(arg => arg.name.value === argName);
|
2417
2395
|
if (!deletionDateNode) {
|
2418
|
-
context.report({
|
2396
|
+
context.report({
|
2397
|
+
loc: getLocation(node.loc, node.name.value, { offsetEnd: 0 }),
|
2398
|
+
messageId: MESSAGE_REQUIRE_DATE,
|
2399
|
+
});
|
2419
2400
|
return;
|
2420
2401
|
}
|
2421
2402
|
const deletionDate = valueFromNode(deletionDateNode.value);
|
2422
2403
|
const isValidDate = DATE_REGEX.test(deletionDate);
|
2423
2404
|
if (!isValidDate) {
|
2424
|
-
context.report({ node:
|
2405
|
+
context.report({ node: deletionDateNode.value, messageId: MESSAGE_INVALID_FORMAT });
|
2425
2406
|
return;
|
2426
2407
|
}
|
2427
2408
|
let [day, month, year] = deletionDate.split('/');
|
@@ -2430,7 +2411,7 @@ const rule$g = {
|
|
2430
2411
|
const deletionDateInMS = Date.parse(`${year}-${month}-${day}`);
|
2431
2412
|
if (Number.isNaN(deletionDateInMS)) {
|
2432
2413
|
context.report({
|
2433
|
-
node:
|
2414
|
+
node: deletionDateNode.value,
|
2434
2415
|
messageId: MESSAGE_INVALID_DATE,
|
2435
2416
|
data: {
|
2436
2417
|
deletionDate,
|
@@ -2441,7 +2422,7 @@ const rule$g = {
|
|
2441
2422
|
const canRemove = Date.now() > deletionDateInMS;
|
2442
2423
|
if (canRemove) {
|
2443
2424
|
context.report({
|
2444
|
-
node
|
2425
|
+
node,
|
2445
2426
|
messageId: MESSAGE_CAN_BE_REMOVED,
|
2446
2427
|
data: {
|
2447
2428
|
nodeName: node.parent.name.value,
|
@@ -2492,17 +2473,15 @@ const rule$h = {
|
|
2492
2473
|
},
|
2493
2474
|
create(context) {
|
2494
2475
|
return {
|
2495
|
-
Directive(node) {
|
2496
|
-
|
2497
|
-
|
2498
|
-
|
2499
|
-
|
2500
|
-
|
2501
|
-
|
2502
|
-
|
2503
|
-
|
2504
|
-
});
|
2505
|
-
}
|
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
|
+
});
|
2506
2485
|
}
|
2507
2486
|
},
|
2508
2487
|
};
|
@@ -2647,18 +2626,22 @@ const rule$j = {
|
|
2647
2626
|
if (!mutationType || !queryType) {
|
2648
2627
|
return {};
|
2649
2628
|
}
|
2650
|
-
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(' ');
|
2651
2635
|
return {
|
2652
2636
|
[selector](node) {
|
2653
|
-
const
|
2654
|
-
const typeName = getTypeName(rawNode);
|
2637
|
+
const typeName = node.name.value;
|
2655
2638
|
const graphQLType = schema.getType(typeName);
|
2656
2639
|
if (graphql.isObjectType(graphQLType)) {
|
2657
2640
|
const { fields } = graphQLType.astNode;
|
2658
2641
|
const hasQueryType = fields.some(field => getTypeName(field) === queryType.name);
|
2659
2642
|
if (!hasQueryType) {
|
2660
2643
|
context.report({
|
2661
|
-
node,
|
2644
|
+
loc: getLocation(node.loc, typeName),
|
2662
2645
|
message: `Mutation result type "${graphQLType.name}" must contain field of type "${queryType.name}".`,
|
2663
2646
|
});
|
2664
2647
|
}
|
@@ -3153,15 +3136,16 @@ const rule$m = {
|
|
3153
3136
|
}
|
3154
3137
|
return isValidIdName && isValidIdType;
|
3155
3138
|
});
|
3139
|
+
const typeName = node.name.value;
|
3156
3140
|
// Usually, there should be only one unique identifier field per type.
|
3157
3141
|
// Some clients allow multiple fields to be used. If more people need this,
|
3158
3142
|
// we can extend this rule later.
|
3159
3143
|
if (validIds.length !== 1) {
|
3160
3144
|
context.report({
|
3161
|
-
node,
|
3162
|
-
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 }}`,
|
3163
3147
|
data: {
|
3164
|
-
|
3148
|
+
typeName,
|
3165
3149
|
acceptedNamesString: options.acceptedIdNames.join(','),
|
3166
3150
|
acceptedTypesString: options.acceptedIdTypes.join(','),
|
3167
3151
|
},
|
@@ -3757,7 +3741,25 @@ class GraphQLRuleTester extends eslint.RuleTester {
|
|
3757
3741
|
return fs.readFileSync(path.resolve(__dirname, `../tests/mocks/${path$1}`), 'utf-8');
|
3758
3742
|
}
|
3759
3743
|
runGraphQLTests(name, rule, tests) {
|
3760
|
-
|
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);
|
3761
3763
|
// Skip snapshot testing if `expect` variable is not defined
|
3762
3764
|
if (typeof expect === 'undefined') {
|
3763
3765
|
return;
|
package/index.mjs
CHANGED
@@ -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
|
? {
|
@@ -738,35 +731,22 @@ const rule = {
|
|
738
731
|
};
|
739
732
|
|
740
733
|
const AVOID_DUPLICATE_FIELDS = 'AVOID_DUPLICATE_FIELDS';
|
741
|
-
const ensureUnique = () => {
|
742
|
-
const set = new Set();
|
743
|
-
return {
|
744
|
-
add: (item, onError) => {
|
745
|
-
if (set.has(item)) {
|
746
|
-
onError();
|
747
|
-
}
|
748
|
-
else {
|
749
|
-
set.add(item);
|
750
|
-
}
|
751
|
-
},
|
752
|
-
};
|
753
|
-
};
|
754
734
|
const rule$1 = {
|
755
735
|
meta: {
|
756
736
|
type: 'suggestion',
|
757
737
|
docs: {
|
758
|
-
description:
|
738
|
+
description: `Checks for duplicate fields in selection set, variables in operation definition, or in arguments set of a field.`,
|
759
739
|
category: 'Stylistic Issues',
|
760
740
|
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-duplicate-fields.md',
|
761
741
|
examples: [
|
762
742
|
{
|
763
743
|
title: 'Incorrect',
|
764
744
|
code: /* GraphQL */ `
|
765
|
-
query
|
745
|
+
query {
|
766
746
|
user {
|
767
|
-
name
|
747
|
+
name
|
768
748
|
email
|
769
|
-
name #
|
749
|
+
name # duplicate field
|
770
750
|
}
|
771
751
|
}
|
772
752
|
`,
|
@@ -774,7 +754,7 @@ const rule$1 = {
|
|
774
754
|
{
|
775
755
|
title: 'Incorrect',
|
776
756
|
code: /* GraphQL */ `
|
777
|
-
query
|
757
|
+
query {
|
778
758
|
users(
|
779
759
|
first: 100
|
780
760
|
skip: 50
|
@@ -789,9 +769,11 @@ const rule$1 = {
|
|
789
769
|
{
|
790
770
|
title: 'Incorrect',
|
791
771
|
code: /* GraphQL */ `
|
792
|
-
query
|
793
|
-
|
794
|
-
|
772
|
+
query (
|
773
|
+
$first: Int!
|
774
|
+
$first: Int! # duplicate variable
|
775
|
+
) {
|
776
|
+
users(first: $first, skip: 50) {
|
795
777
|
id
|
796
778
|
}
|
797
779
|
}
|
@@ -800,58 +782,47 @@ const rule$1 = {
|
|
800
782
|
],
|
801
783
|
},
|
802
784
|
messages: {
|
803
|
-
[AVOID_DUPLICATE_FIELDS]: `{{ type }} "{{ fieldName }}" defined multiple times
|
785
|
+
[AVOID_DUPLICATE_FIELDS]: `{{ type }} "{{ fieldName }}" defined multiple times`,
|
804
786
|
},
|
805
787
|
schema: [],
|
806
788
|
},
|
807
789
|
create(context) {
|
790
|
+
function checkNode(usedFields, fieldName, type, node) {
|
791
|
+
if (usedFields.has(fieldName)) {
|
792
|
+
context.report({
|
793
|
+
loc: getLocation((node.kind === Kind.FIELD && node.alias ? node.alias : node).loc, fieldName, {
|
794
|
+
offsetEnd: node.kind === Kind.VARIABLE_DEFINITION ? 0 : 1,
|
795
|
+
}),
|
796
|
+
messageId: AVOID_DUPLICATE_FIELDS,
|
797
|
+
data: {
|
798
|
+
type,
|
799
|
+
fieldName,
|
800
|
+
},
|
801
|
+
});
|
802
|
+
}
|
803
|
+
else {
|
804
|
+
usedFields.add(fieldName);
|
805
|
+
}
|
806
|
+
}
|
808
807
|
return {
|
809
808
|
OperationDefinition(node) {
|
810
|
-
const
|
811
|
-
for (const
|
812
|
-
|
813
|
-
context.report({
|
814
|
-
messageId: AVOID_DUPLICATE_FIELDS,
|
815
|
-
data: {
|
816
|
-
type: 'Operation variable',
|
817
|
-
fieldName: arg.variable.name.value,
|
818
|
-
},
|
819
|
-
node: arg,
|
820
|
-
});
|
821
|
-
});
|
809
|
+
const set = new Set();
|
810
|
+
for (const varDef of node.variableDefinitions) {
|
811
|
+
checkNode(set, varDef.variable.name.value, 'Operation variable', varDef);
|
822
812
|
}
|
823
813
|
},
|
824
814
|
Field(node) {
|
825
|
-
const
|
826
|
-
for (const arg of node.arguments
|
827
|
-
|
828
|
-
context.report({
|
829
|
-
messageId: AVOID_DUPLICATE_FIELDS,
|
830
|
-
data: {
|
831
|
-
type: 'Field argument',
|
832
|
-
fieldName: arg.name.value,
|
833
|
-
},
|
834
|
-
node: arg,
|
835
|
-
});
|
836
|
-
});
|
815
|
+
const set = new Set();
|
816
|
+
for (const arg of node.arguments) {
|
817
|
+
checkNode(set, arg.name.value, 'Field argument', arg);
|
837
818
|
}
|
838
819
|
},
|
839
820
|
SelectionSet(node) {
|
840
821
|
var _a;
|
841
|
-
const
|
842
|
-
for (const selection of node.selections
|
822
|
+
const set = new Set();
|
823
|
+
for (const selection of node.selections) {
|
843
824
|
if (selection.kind === Kind.FIELD) {
|
844
|
-
|
845
|
-
uniqueCheck.add(nameToCheck, () => {
|
846
|
-
context.report({
|
847
|
-
messageId: AVOID_DUPLICATE_FIELDS,
|
848
|
-
data: {
|
849
|
-
type: 'Field',
|
850
|
-
fieldName: nameToCheck,
|
851
|
-
},
|
852
|
-
node: selection,
|
853
|
-
});
|
854
|
-
});
|
825
|
+
checkNode(set, ((_a = selection.alias) === null || _a === void 0 ? void 0 : _a.value) || selection.name.value, 'Field', selection);
|
855
826
|
}
|
856
827
|
}
|
857
828
|
},
|
@@ -1110,7 +1081,7 @@ const rule$5 = {
|
|
1110
1081
|
'[description.type="StringValue"]': node => {
|
1111
1082
|
if (node.description.block !== (style === 'block')) {
|
1112
1083
|
context.report({
|
1113
|
-
|
1084
|
+
loc: getLocation(node.description.loc),
|
1114
1085
|
message: `Unexpected ${wrongDescriptionType} description`,
|
1115
1086
|
});
|
1116
1087
|
}
|
@@ -1197,10 +1168,11 @@ const rule$6 = {
|
|
1197
1168
|
const shouldCheckType = node => (options.checkMutations && isMutationType(node)) || (options.checkQueries && isQueryType(node));
|
1198
1169
|
const listeners = {
|
1199
1170
|
'FieldDefinition > InputValueDefinition': node => {
|
1200
|
-
|
1171
|
+
const name = node.name.value;
|
1172
|
+
if (name !== 'input' && shouldCheckType(node.parent.parent)) {
|
1201
1173
|
context.report({
|
1202
|
-
|
1203
|
-
message: `Input "${
|
1174
|
+
loc: getLocation(node.loc, name),
|
1175
|
+
message: `Input "${name}" should be called "input"`,
|
1204
1176
|
});
|
1205
1177
|
}
|
1206
1178
|
},
|
@@ -1217,11 +1189,12 @@ const rule$6 = {
|
|
1217
1189
|
const inputValueNode = findInputType(node);
|
1218
1190
|
if (shouldCheckType(inputValueNode.parent.parent)) {
|
1219
1191
|
const mutationName = `${inputValueNode.parent.name.value}Input`;
|
1192
|
+
const name = node.name.value;
|
1220
1193
|
if ((options.caseSensitiveInputType && node.name.value !== mutationName) ||
|
1221
|
-
|
1194
|
+
name.toLowerCase() !== mutationName.toLowerCase()) {
|
1222
1195
|
context.report({
|
1223
|
-
node,
|
1224
|
-
message: `InputType "${
|
1196
|
+
loc: getLocation(node.loc, name),
|
1197
|
+
message: `InputType "${name}" name should be "${mutationName}"`,
|
1225
1198
|
});
|
1226
1199
|
}
|
1227
1200
|
}
|
@@ -1610,7 +1583,7 @@ const rule$8 = {
|
|
1610
1583
|
});
|
1611
1584
|
if (result.ok === false) {
|
1612
1585
|
context.report({
|
1613
|
-
node,
|
1586
|
+
loc: getLocation(node.loc, node.value),
|
1614
1587
|
message: result.errorMessage,
|
1615
1588
|
data: {
|
1616
1589
|
prefix,
|
@@ -1637,10 +1610,16 @@ const rule$8 = {
|
|
1637
1610
|
return {
|
1638
1611
|
Name: node => {
|
1639
1612
|
if (node.value.startsWith('_') && options.leadingUnderscore === 'forbid') {
|
1640
|
-
context.report({
|
1613
|
+
context.report({
|
1614
|
+
loc: getLocation(node.loc, node.value),
|
1615
|
+
message: 'Leading underscores are not allowed',
|
1616
|
+
});
|
1641
1617
|
}
|
1642
1618
|
if (node.value.endsWith('_') && options.trailingUnderscore === 'forbid') {
|
1643
|
-
context.report({
|
1619
|
+
context.report({
|
1620
|
+
loc: getLocation(node.loc, node.value),
|
1621
|
+
message: 'Trailing underscores are not allowed',
|
1622
|
+
});
|
1644
1623
|
}
|
1645
1624
|
},
|
1646
1625
|
ObjectTypeDefinition: node => {
|
@@ -1868,8 +1847,8 @@ const rule$b = {
|
|
1868
1847
|
mutation {
|
1869
1848
|
changeSomething(
|
1870
1849
|
type: OLD # This is deprecated, so you'll get an error
|
1871
|
-
) {
|
1872
|
-
...
|
1850
|
+
) {
|
1851
|
+
...
|
1873
1852
|
}
|
1874
1853
|
}
|
1875
1854
|
`,
|
@@ -1907,8 +1886,9 @@ const rule$b = {
|
|
1907
1886
|
const typeInfo = node.typeInfo();
|
1908
1887
|
if (typeInfo && typeInfo.enumValue) {
|
1909
1888
|
if (typeInfo.enumValue.isDeprecated) {
|
1889
|
+
const enumValueName = node.value;
|
1910
1890
|
context.report({
|
1911
|
-
loc: node.loc,
|
1891
|
+
loc: getLocation(node.loc, enumValueName),
|
1912
1892
|
messageId: NO_DEPRECATED,
|
1913
1893
|
data: {
|
1914
1894
|
type: 'enum value',
|
@@ -1923,8 +1903,9 @@ const rule$b = {
|
|
1923
1903
|
const typeInfo = node.typeInfo();
|
1924
1904
|
if (typeInfo && typeInfo.fieldDef) {
|
1925
1905
|
if (typeInfo.fieldDef.isDeprecated) {
|
1906
|
+
const fieldName = node.name.value;
|
1926
1907
|
context.report({
|
1927
|
-
loc: node.loc,
|
1908
|
+
loc: getLocation(node.loc, fieldName),
|
1928
1909
|
messageId: NO_DEPRECATED,
|
1929
1910
|
data: {
|
1930
1911
|
type: 'field',
|
@@ -2000,10 +1981,7 @@ const rule$c = {
|
|
2000
1981
|
if (!isEslintComment && line !== prev.line && next.kind === TokenKind.NAME && linesAfter < 2) {
|
2001
1982
|
context.report({
|
2002
1983
|
messageId: HASHTAG_COMMENT,
|
2003
|
-
loc: {
|
2004
|
-
start: { line, column },
|
2005
|
-
end: { line, column },
|
2006
|
-
},
|
1984
|
+
loc: getLocation({ start: { line, column } }),
|
2007
1985
|
});
|
2008
1986
|
}
|
2009
1987
|
}
|
@@ -2132,7 +2110,7 @@ const rule$e = {
|
|
2132
2110
|
const typeName = node.name.value;
|
2133
2111
|
if (!reachableTypes.has(typeName)) {
|
2134
2112
|
context.report({
|
2135
|
-
node,
|
2113
|
+
loc: getLocation(node.name.loc, typeName, { offsetStart: node.kind === Kind.DIRECTIVE_DEFINITION ? 2 : 1 }),
|
2136
2114
|
messageId: UNREACHABLE_TYPE,
|
2137
2115
|
data: { typeName },
|
2138
2116
|
fix: fixer => fixer.remove(node),
|
@@ -2230,7 +2208,7 @@ const rule$f = {
|
|
2230
2208
|
return;
|
2231
2209
|
}
|
2232
2210
|
context.report({
|
2233
|
-
node,
|
2211
|
+
loc: getLocation(node.loc, fieldName),
|
2234
2212
|
messageId: UNUSED_FIELD,
|
2235
2213
|
data: { fieldName },
|
2236
2214
|
fix(fixer) {
|
@@ -2385,10 +2363,10 @@ const rule$g = {
|
|
2385
2363
|
],
|
2386
2364
|
},
|
2387
2365
|
messages: {
|
2388
|
-
[MESSAGE_REQUIRE_DATE]: 'Directive "@deprecated" must have a deletion date
|
2389
|
-
[MESSAGE_INVALID_FORMAT]: 'Deletion date must be in format "DD/MM/YYYY"
|
2390
|
-
[MESSAGE_INVALID_DATE]: 'Invalid "{{ deletionDate }}" deletion date
|
2391
|
-
[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',
|
2392
2370
|
},
|
2393
2371
|
schema: [
|
2394
2372
|
{
|
@@ -2409,13 +2387,16 @@ const rule$g = {
|
|
2409
2387
|
const argName = ((_a = context.options[0]) === null || _a === void 0 ? void 0 : _a.argumentName) || 'deletionDate';
|
2410
2388
|
const deletionDateNode = node.arguments.find(arg => arg.name.value === argName);
|
2411
2389
|
if (!deletionDateNode) {
|
2412
|
-
context.report({
|
2390
|
+
context.report({
|
2391
|
+
loc: getLocation(node.loc, node.name.value, { offsetEnd: 0 }),
|
2392
|
+
messageId: MESSAGE_REQUIRE_DATE,
|
2393
|
+
});
|
2413
2394
|
return;
|
2414
2395
|
}
|
2415
2396
|
const deletionDate = valueFromNode(deletionDateNode.value);
|
2416
2397
|
const isValidDate = DATE_REGEX.test(deletionDate);
|
2417
2398
|
if (!isValidDate) {
|
2418
|
-
context.report({ node:
|
2399
|
+
context.report({ node: deletionDateNode.value, messageId: MESSAGE_INVALID_FORMAT });
|
2419
2400
|
return;
|
2420
2401
|
}
|
2421
2402
|
let [day, month, year] = deletionDate.split('/');
|
@@ -2424,7 +2405,7 @@ const rule$g = {
|
|
2424
2405
|
const deletionDateInMS = Date.parse(`${year}-${month}-${day}`);
|
2425
2406
|
if (Number.isNaN(deletionDateInMS)) {
|
2426
2407
|
context.report({
|
2427
|
-
node:
|
2408
|
+
node: deletionDateNode.value,
|
2428
2409
|
messageId: MESSAGE_INVALID_DATE,
|
2429
2410
|
data: {
|
2430
2411
|
deletionDate,
|
@@ -2435,7 +2416,7 @@ const rule$g = {
|
|
2435
2416
|
const canRemove = Date.now() > deletionDateInMS;
|
2436
2417
|
if (canRemove) {
|
2437
2418
|
context.report({
|
2438
|
-
node
|
2419
|
+
node,
|
2439
2420
|
messageId: MESSAGE_CAN_BE_REMOVED,
|
2440
2421
|
data: {
|
2441
2422
|
nodeName: node.parent.name.value,
|
@@ -2486,17 +2467,15 @@ const rule$h = {
|
|
2486
2467
|
},
|
2487
2468
|
create(context) {
|
2488
2469
|
return {
|
2489
|
-
Directive(node) {
|
2490
|
-
|
2491
|
-
|
2492
|
-
|
2493
|
-
|
2494
|
-
|
2495
|
-
|
2496
|
-
|
2497
|
-
|
2498
|
-
});
|
2499
|
-
}
|
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
|
+
});
|
2500
2479
|
}
|
2501
2480
|
},
|
2502
2481
|
};
|
@@ -2641,18 +2620,22 @@ const rule$j = {
|
|
2641
2620
|
if (!mutationType || !queryType) {
|
2642
2621
|
return {};
|
2643
2622
|
}
|
2644
|
-
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(' ');
|
2645
2629
|
return {
|
2646
2630
|
[selector](node) {
|
2647
|
-
const
|
2648
|
-
const typeName = getTypeName(rawNode);
|
2631
|
+
const typeName = node.name.value;
|
2649
2632
|
const graphQLType = schema.getType(typeName);
|
2650
2633
|
if (isObjectType$1(graphQLType)) {
|
2651
2634
|
const { fields } = graphQLType.astNode;
|
2652
2635
|
const hasQueryType = fields.some(field => getTypeName(field) === queryType.name);
|
2653
2636
|
if (!hasQueryType) {
|
2654
2637
|
context.report({
|
2655
|
-
node,
|
2638
|
+
loc: getLocation(node.loc, typeName),
|
2656
2639
|
message: `Mutation result type "${graphQLType.name}" must contain field of type "${queryType.name}".`,
|
2657
2640
|
});
|
2658
2641
|
}
|
@@ -3147,15 +3130,16 @@ const rule$m = {
|
|
3147
3130
|
}
|
3148
3131
|
return isValidIdName && isValidIdType;
|
3149
3132
|
});
|
3133
|
+
const typeName = node.name.value;
|
3150
3134
|
// Usually, there should be only one unique identifier field per type.
|
3151
3135
|
// Some clients allow multiple fields to be used. If more people need this,
|
3152
3136
|
// we can extend this rule later.
|
3153
3137
|
if (validIds.length !== 1) {
|
3154
3138
|
context.report({
|
3155
|
-
node,
|
3156
|
-
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 }}`,
|
3157
3141
|
data: {
|
3158
|
-
|
3142
|
+
typeName,
|
3159
3143
|
acceptedNamesString: options.acceptedIdNames.join(','),
|
3160
3144
|
acceptedTypesString: options.acceptedIdTypes.join(','),
|
3161
3145
|
},
|
@@ -3751,7 +3735,25 @@ class GraphQLRuleTester extends RuleTester {
|
|
3751
3735
|
return readFileSync(resolve(__dirname, `../tests/mocks/${path}`), 'utf-8');
|
3752
3736
|
}
|
3753
3737
|
runGraphQLTests(name, rule, tests) {
|
3754
|
-
|
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);
|
3755
3757
|
// Skip snapshot testing if `expect` variable is not defined
|
3756
3758
|
if (typeof expect === 'undefined') {
|
3757
3759
|
return;
|
package/package.json
CHANGED
package/rules/index.d.ts
CHANGED
@@ -6,7 +6,7 @@ export declare const rules: {
|
|
6
6
|
variables?: "OperationDefinition"[];
|
7
7
|
arguments?: ("Field" | "Directive" | "FieldDefinition" | "DirectiveDefinition")[];
|
8
8
|
}], false>;
|
9
|
-
'avoid-duplicate-fields': import("..").GraphQLESLintRule<[], false>;
|
9
|
+
'avoid-duplicate-fields': import("..").GraphQLESLintRule<any[], false>;
|
10
10
|
'avoid-operation-name-prefix': import("..").GraphQLESLintRule<import("./avoid-operation-name-prefix").AvoidOperationNamePrefixConfig, false>;
|
11
11
|
'avoid-scalar-result-type-on-mutation': import("..").GraphQLESLintRule<any[], false>;
|
12
12
|
'avoid-typename-prefix': import("..").GraphQLESLintRule<any[], false>;
|
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
|
};
|