@graphql-eslint/eslint-plugin 2.4.0-alpha-60dfe26.0 → 3.0.0-alpha-5388f29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/configs/all.d.ts +29 -1
- package/configs/index.d.ts +58 -2
- package/configs/recommended.d.ts +29 -1
- package/docs/custom-rules.md +1 -1
- package/docs/rules/alphabetize.md +35 -10
- package/docs/rules/avoid-duplicate-fields.md +9 -7
- package/docs/rules/description-style.md +2 -2
- package/docs/rules/fragments-on-composite-type.md +2 -2
- package/docs/rules/match-document-filename.md +7 -7
- package/docs/rules/naming-convention.md +54 -83
- package/docs/rules/no-deprecated.md +2 -2
- package/docs/rules/one-field-subscriptions.md +2 -2
- package/docs/rules/possible-fragment-spread.md +2 -2
- package/docs/rules/possible-type-extension.md +2 -2
- package/docs/rules/strict-id-in-types.md +4 -4
- package/docs/rules/unique-directive-names-per-location.md +2 -2
- package/docs/rules/value-literals-of-correct-type.md +2 -2
- package/estree-parser/converter.d.ts +3 -2
- package/index.js +484 -440
- package/index.mjs +484 -440
- package/package.json +1 -1
- package/rules/avoid-duplicate-fields.d.ts +1 -1
- package/rules/index.d.ts +117 -15
- package/rules/naming-convention.d.ts +32 -25
- package/testkit.d.ts +6 -3
- package/types.d.ts +1 -0
- package/utils.d.ts +6 -8
package/index.js
CHANGED
@@ -15,6 +15,8 @@ const depthLimit = _interopDefault(require('graphql-depth-limit'));
|
|
15
15
|
const graphqlTagPluck = require('@graphql-tools/graphql-tag-pluck');
|
16
16
|
const graphqlConfig$1 = require('graphql-config');
|
17
17
|
const codeFileLoader = require('@graphql-tools/code-file-loader');
|
18
|
+
const eslint = require('eslint');
|
19
|
+
const codeFrame = require('@babel/code-frame');
|
18
20
|
|
19
21
|
/*
|
20
22
|
* 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
|
@@ -33,7 +35,34 @@ const recommendedConfig = {
|
|
33
35
|
'@graphql-eslint/known-type-names': 'error',
|
34
36
|
'@graphql-eslint/lone-anonymous-operation': 'error',
|
35
37
|
'@graphql-eslint/lone-schema-definition': 'error',
|
36
|
-
'@graphql-eslint/naming-convention':
|
38
|
+
'@graphql-eslint/naming-convention': [
|
39
|
+
'error',
|
40
|
+
{
|
41
|
+
types: 'PascalCase',
|
42
|
+
fields: 'camelCase',
|
43
|
+
overrides: {
|
44
|
+
EnumValueDefinition: 'UPPER_CASE',
|
45
|
+
OperationDefinition: {
|
46
|
+
style: 'PascalCase',
|
47
|
+
forbiddenPrefixes: ['Query', 'Mutation', 'Subscription', 'Get'],
|
48
|
+
forbiddenSuffixes: ['Query', 'Mutation', 'Subscription'],
|
49
|
+
},
|
50
|
+
FragmentDefinition: { style: 'PascalCase', forbiddenPrefixes: ['Fragment'], forbiddenSuffixes: ['Fragment'] },
|
51
|
+
'FieldDefinition[parent.name.value=Query]': {
|
52
|
+
forbiddenPrefixes: ['query', 'get'],
|
53
|
+
forbiddenSuffixes: ['Query'],
|
54
|
+
},
|
55
|
+
'FieldDefinition[parent.name.value=Mutation]': {
|
56
|
+
forbiddenPrefixes: ['mutation'],
|
57
|
+
forbiddenSuffixes: ['Mutation'],
|
58
|
+
},
|
59
|
+
'FieldDefinition[parent.name.value=Subscription]': {
|
60
|
+
forbiddenPrefixes: ['subscription'],
|
61
|
+
forbiddenSuffixes: ['Subscription'],
|
62
|
+
},
|
63
|
+
},
|
64
|
+
},
|
65
|
+
],
|
37
66
|
'@graphql-eslint/no-anonymous-operations': 'error',
|
38
67
|
'@graphql-eslint/no-case-insensitive-enum-values-duplicates': 'error',
|
39
68
|
'@graphql-eslint/no-fragment-cycles': 'error',
|
@@ -209,9 +238,14 @@ const loaderCache = new Proxy(Object.create(null), {
|
|
209
238
|
return true;
|
210
239
|
},
|
211
240
|
});
|
212
|
-
const
|
213
|
-
|
214
|
-
|
241
|
+
const TYPES_KINDS = [
|
242
|
+
graphql.Kind.OBJECT_TYPE_DEFINITION,
|
243
|
+
graphql.Kind.INTERFACE_TYPE_DEFINITION,
|
244
|
+
graphql.Kind.ENUM_TYPE_DEFINITION,
|
245
|
+
graphql.Kind.SCALAR_TYPE_DEFINITION,
|
246
|
+
graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION,
|
247
|
+
graphql.Kind.UNION_TYPE_DEFINITION,
|
248
|
+
];
|
215
249
|
var CaseStyle;
|
216
250
|
(function (CaseStyle) {
|
217
251
|
CaseStyle["camelCase"] = "camelCase";
|
@@ -242,9 +276,27 @@ const convertCase = (style, str) => {
|
|
242
276
|
return lowerCase(str).replace(/ /g, '-');
|
243
277
|
}
|
244
278
|
};
|
279
|
+
function getLocation(loc, fieldName = '', offset) {
|
280
|
+
const { start } = loc;
|
281
|
+
/*
|
282
|
+
* ESLint has 0-based column number
|
283
|
+
* https://eslint.org/docs/developer-guide/working-with-rules#contextreport
|
284
|
+
*/
|
285
|
+
const { offsetStart = 1, offsetEnd = 1 } = offset !== null && offset !== void 0 ? offset : {};
|
286
|
+
return {
|
287
|
+
start: {
|
288
|
+
line: start.line,
|
289
|
+
column: start.column - offsetStart,
|
290
|
+
},
|
291
|
+
end: {
|
292
|
+
line: start.line,
|
293
|
+
column: start.column - offsetEnd + fieldName.length,
|
294
|
+
},
|
295
|
+
};
|
296
|
+
}
|
245
297
|
|
246
298
|
function extractRuleName(stack) {
|
247
|
-
const match = (stack || '').match(/validation[
|
299
|
+
const match = (stack || '').match(/validation[/\\]rules[/\\](.*?)\.js:/) || [];
|
248
300
|
return match[1] || null;
|
249
301
|
}
|
250
302
|
function validateDoc(sourceNode, context, schema, documentNode, rules, ruleName = null) {
|
@@ -255,7 +307,7 @@ function validateDoc(sourceNode, context, schema, documentNode, rules, ruleName
|
|
255
307
|
for (const error of validationErrors) {
|
256
308
|
const validateRuleName = ruleName || `[${extractRuleName(error.stack)}]`;
|
257
309
|
context.report({
|
258
|
-
loc: error.locations[0],
|
310
|
+
loc: getLocation({ start: error.locations[0] }),
|
259
311
|
message: ruleName ? error.message : `${validateRuleName} ${error.message}`,
|
260
312
|
});
|
261
313
|
}
|
@@ -292,6 +344,7 @@ const validationToRule = (name, ruleName, docs, getDocumentNode) => {
|
|
292
344
|
meta: {
|
293
345
|
docs: {
|
294
346
|
...docs,
|
347
|
+
graphQLJSRuleName: ruleName,
|
295
348
|
category: 'Validation',
|
296
349
|
recommended: true,
|
297
350
|
requiresSchema,
|
@@ -591,7 +644,7 @@ const rule = {
|
|
591
644
|
],
|
592
645
|
},
|
593
646
|
messages: {
|
594
|
-
[ALPHABETIZE]: '"{{ currName }}" should be before "{{ prevName }}"
|
647
|
+
[ALPHABETIZE]: '"{{ currName }}" should be before "{{ prevName }}"',
|
595
648
|
},
|
596
649
|
schema: {
|
597
650
|
type: 'array',
|
@@ -604,38 +657,48 @@ const rule = {
|
|
604
657
|
properties: {
|
605
658
|
fields: {
|
606
659
|
type: 'array',
|
607
|
-
|
660
|
+
uniqueItems: true,
|
661
|
+
minItems: 1,
|
662
|
+
items: {
|
608
663
|
enum: fieldsEnum,
|
609
664
|
},
|
610
|
-
description: 'Fields of `type`, `interface`, and `input
|
665
|
+
description: 'Fields of `type`, `interface`, and `input`',
|
611
666
|
},
|
612
667
|
values: {
|
613
668
|
type: 'array',
|
614
|
-
|
669
|
+
uniqueItems: true,
|
670
|
+
minItems: 1,
|
671
|
+
items: {
|
615
672
|
enum: valuesEnum,
|
616
673
|
},
|
617
|
-
description: 'Values of `enum
|
674
|
+
description: 'Values of `enum`',
|
618
675
|
},
|
619
676
|
selections: {
|
620
677
|
type: 'array',
|
621
|
-
|
678
|
+
uniqueItems: true,
|
679
|
+
minItems: 1,
|
680
|
+
items: {
|
622
681
|
enum: selectionsEnum,
|
623
682
|
},
|
624
|
-
description: 'Selections of operations (`query`, `mutation` and `subscription`) and `fragment
|
683
|
+
description: 'Selections of operations (`query`, `mutation` and `subscription`) and `fragment`',
|
625
684
|
},
|
626
685
|
variables: {
|
627
686
|
type: 'array',
|
628
|
-
|
687
|
+
uniqueItems: true,
|
688
|
+
minItems: 1,
|
689
|
+
items: {
|
629
690
|
enum: variablesEnum,
|
630
691
|
},
|
631
|
-
description: 'Variables of operations (`query`, `mutation` and `subscription`)
|
692
|
+
description: 'Variables of operations (`query`, `mutation` and `subscription`)',
|
632
693
|
},
|
633
694
|
arguments: {
|
634
695
|
type: 'array',
|
635
|
-
|
696
|
+
uniqueItems: true,
|
697
|
+
minItems: 1,
|
698
|
+
items: {
|
636
699
|
enum: argumentsEnum,
|
637
700
|
},
|
638
|
-
description: 'Arguments of fields and directives
|
701
|
+
description: 'Arguments of fields and directives',
|
639
702
|
},
|
640
703
|
},
|
641
704
|
},
|
@@ -648,16 +711,9 @@ const rule = {
|
|
648
711
|
for (const node of nodes) {
|
649
712
|
const currName = node.name.value;
|
650
713
|
if (prevName && prevName > currName) {
|
651
|
-
const { start, end } = node.name.loc;
|
652
714
|
const isVariableNode = node.kind === graphql.Kind.VARIABLE;
|
653
715
|
context.report({
|
654
|
-
loc: {
|
655
|
-
start: {
|
656
|
-
line: start.line,
|
657
|
-
column: start.column - (isVariableNode ? 2 : 1),
|
658
|
-
},
|
659
|
-
end,
|
660
|
-
},
|
716
|
+
loc: getLocation(node.loc, node.name.value, { offsetEnd: isVariableNode ? 0 : 1 }),
|
661
717
|
messageId: ALPHABETIZE,
|
662
718
|
data: isVariableNode
|
663
719
|
? {
|
@@ -673,7 +729,7 @@ const rule = {
|
|
673
729
|
const opts = context.options[0];
|
674
730
|
const fields = new Set((_a = opts.fields) !== null && _a !== void 0 ? _a : []);
|
675
731
|
const listeners = {};
|
676
|
-
const
|
732
|
+
const kinds = [
|
677
733
|
fields.has(graphql.Kind.OBJECT_TYPE_DEFINITION) && [graphql.Kind.OBJECT_TYPE_DEFINITION, graphql.Kind.OBJECT_TYPE_EXTENSION],
|
678
734
|
fields.has(graphql.Kind.INTERFACE_TYPE_DEFINITION) && [graphql.Kind.INTERFACE_TYPE_DEFINITION, graphql.Kind.INTERFACE_TYPE_EXTENSION],
|
679
735
|
fields.has(graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION) && [
|
@@ -681,8 +737,9 @@ const rule = {
|
|
681
737
|
graphql.Kind.INPUT_OBJECT_TYPE_EXTENSION,
|
682
738
|
],
|
683
739
|
]
|
684
|
-
.
|
685
|
-
.
|
740
|
+
.filter(Boolean)
|
741
|
+
.flat();
|
742
|
+
const fieldsSelector = kinds.join(',');
|
686
743
|
const hasEnumValues = ((_b = opts.values) === null || _b === void 0 ? void 0 : _b[0]) === graphql.Kind.ENUM_TYPE_DEFINITION;
|
687
744
|
const selectionsSelector = (_c = opts.selections) === null || _c === void 0 ? void 0 : _c.join(',');
|
688
745
|
const hasVariables = ((_d = opts.variables) === null || _d === void 0 ? void 0 : _d[0]) === graphql.Kind.OPERATION_DEFINITION;
|
@@ -723,35 +780,22 @@ const rule = {
|
|
723
780
|
};
|
724
781
|
|
725
782
|
const AVOID_DUPLICATE_FIELDS = 'AVOID_DUPLICATE_FIELDS';
|
726
|
-
const ensureUnique = () => {
|
727
|
-
const set = new Set();
|
728
|
-
return {
|
729
|
-
add: (item, onError) => {
|
730
|
-
if (set.has(item)) {
|
731
|
-
onError();
|
732
|
-
}
|
733
|
-
else {
|
734
|
-
set.add(item);
|
735
|
-
}
|
736
|
-
},
|
737
|
-
};
|
738
|
-
};
|
739
783
|
const rule$1 = {
|
740
784
|
meta: {
|
741
785
|
type: 'suggestion',
|
742
786
|
docs: {
|
743
|
-
description:
|
787
|
+
description: `Checks for duplicate fields in selection set, variables in operation definition, or in arguments set of a field.`,
|
744
788
|
category: 'Stylistic Issues',
|
745
789
|
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-duplicate-fields.md',
|
746
790
|
examples: [
|
747
791
|
{
|
748
792
|
title: 'Incorrect',
|
749
793
|
code: /* GraphQL */ `
|
750
|
-
query
|
794
|
+
query {
|
751
795
|
user {
|
752
|
-
name
|
796
|
+
name
|
753
797
|
email
|
754
|
-
name #
|
798
|
+
name # duplicate field
|
755
799
|
}
|
756
800
|
}
|
757
801
|
`,
|
@@ -759,7 +803,7 @@ const rule$1 = {
|
|
759
803
|
{
|
760
804
|
title: 'Incorrect',
|
761
805
|
code: /* GraphQL */ `
|
762
|
-
query
|
806
|
+
query {
|
763
807
|
users(
|
764
808
|
first: 100
|
765
809
|
skip: 50
|
@@ -774,9 +818,11 @@ const rule$1 = {
|
|
774
818
|
{
|
775
819
|
title: 'Incorrect',
|
776
820
|
code: /* GraphQL */ `
|
777
|
-
query
|
778
|
-
|
779
|
-
|
821
|
+
query (
|
822
|
+
$first: Int!
|
823
|
+
$first: Int! # duplicate variable
|
824
|
+
) {
|
825
|
+
users(first: $first, skip: 50) {
|
780
826
|
id
|
781
827
|
}
|
782
828
|
}
|
@@ -785,58 +831,47 @@ const rule$1 = {
|
|
785
831
|
],
|
786
832
|
},
|
787
833
|
messages: {
|
788
|
-
[AVOID_DUPLICATE_FIELDS]: `{{ type }} "{{ fieldName }}" defined multiple times
|
834
|
+
[AVOID_DUPLICATE_FIELDS]: `{{ type }} "{{ fieldName }}" defined multiple times`,
|
789
835
|
},
|
790
836
|
schema: [],
|
791
837
|
},
|
792
838
|
create(context) {
|
839
|
+
function checkNode(usedFields, fieldName, type, node) {
|
840
|
+
if (usedFields.has(fieldName)) {
|
841
|
+
context.report({
|
842
|
+
loc: getLocation((node.kind === graphql.Kind.FIELD && node.alias ? node.alias : node).loc, fieldName, {
|
843
|
+
offsetEnd: node.kind === graphql.Kind.VARIABLE_DEFINITION ? 0 : 1,
|
844
|
+
}),
|
845
|
+
messageId: AVOID_DUPLICATE_FIELDS,
|
846
|
+
data: {
|
847
|
+
type,
|
848
|
+
fieldName,
|
849
|
+
},
|
850
|
+
});
|
851
|
+
}
|
852
|
+
else {
|
853
|
+
usedFields.add(fieldName);
|
854
|
+
}
|
855
|
+
}
|
793
856
|
return {
|
794
857
|
OperationDefinition(node) {
|
795
|
-
const
|
796
|
-
for (const
|
797
|
-
|
798
|
-
context.report({
|
799
|
-
messageId: AVOID_DUPLICATE_FIELDS,
|
800
|
-
data: {
|
801
|
-
type: 'Operation variable',
|
802
|
-
fieldName: arg.variable.name.value,
|
803
|
-
},
|
804
|
-
node: arg,
|
805
|
-
});
|
806
|
-
});
|
858
|
+
const set = new Set();
|
859
|
+
for (const varDef of node.variableDefinitions) {
|
860
|
+
checkNode(set, varDef.variable.name.value, 'Operation variable', varDef);
|
807
861
|
}
|
808
862
|
},
|
809
863
|
Field(node) {
|
810
|
-
const
|
811
|
-
for (const arg of node.arguments
|
812
|
-
|
813
|
-
context.report({
|
814
|
-
messageId: AVOID_DUPLICATE_FIELDS,
|
815
|
-
data: {
|
816
|
-
type: 'Field argument',
|
817
|
-
fieldName: arg.name.value,
|
818
|
-
},
|
819
|
-
node: arg,
|
820
|
-
});
|
821
|
-
});
|
864
|
+
const set = new Set();
|
865
|
+
for (const arg of node.arguments) {
|
866
|
+
checkNode(set, arg.name.value, 'Field argument', arg);
|
822
867
|
}
|
823
868
|
},
|
824
869
|
SelectionSet(node) {
|
825
870
|
var _a;
|
826
|
-
const
|
827
|
-
for (const selection of node.selections
|
871
|
+
const set = new Set();
|
872
|
+
for (const selection of node.selections) {
|
828
873
|
if (selection.kind === graphql.Kind.FIELD) {
|
829
|
-
|
830
|
-
uniqueCheck.add(nameToCheck, () => {
|
831
|
-
context.report({
|
832
|
-
messageId: AVOID_DUPLICATE_FIELDS,
|
833
|
-
data: {
|
834
|
-
type: 'Field',
|
835
|
-
fieldName: nameToCheck,
|
836
|
-
},
|
837
|
-
node: selection,
|
838
|
-
});
|
839
|
-
});
|
874
|
+
checkNode(set, ((_a = selection.alias) === null || _a === void 0 ? void 0 : _a.value) || selection.name.value, 'Field', selection);
|
840
875
|
}
|
841
876
|
}
|
842
877
|
},
|
@@ -967,16 +1002,20 @@ const rule$3 = {
|
|
967
1002
|
if (!mutationType) {
|
968
1003
|
return {};
|
969
1004
|
}
|
970
|
-
const selector =
|
1005
|
+
const selector = [
|
1006
|
+
`:matches(${graphql.Kind.OBJECT_TYPE_DEFINITION}, ${graphql.Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}]`,
|
1007
|
+
'>',
|
1008
|
+
graphql.Kind.FIELD_DEFINITION,
|
1009
|
+
graphql.Kind.NAMED_TYPE,
|
1010
|
+
].join(' ');
|
971
1011
|
return {
|
972
1012
|
[selector](node) {
|
973
|
-
const
|
974
|
-
const typeName = getTypeName(rawNode);
|
1013
|
+
const typeName = node.name.value;
|
975
1014
|
const graphQLType = schema.getType(typeName);
|
976
1015
|
if (graphql.isScalarType(graphQLType)) {
|
977
1016
|
context.report({
|
978
|
-
node,
|
979
|
-
message: `Unexpected scalar result type "${typeName}"
|
1017
|
+
loc: getLocation(node.loc, typeName),
|
1018
|
+
message: `Unexpected scalar result type "${typeName}"`,
|
980
1019
|
});
|
981
1020
|
}
|
982
1021
|
},
|
@@ -1025,23 +1064,13 @@ const rule$4 = {
|
|
1025
1064
|
for (const field of node.fields) {
|
1026
1065
|
const fieldName = field.name.value;
|
1027
1066
|
if (fieldName.toLowerCase().startsWith(lowerTypeName)) {
|
1028
|
-
const { start } = field.loc;
|
1029
1067
|
context.report({
|
1030
1068
|
data: {
|
1031
1069
|
fieldName,
|
1032
1070
|
typeName,
|
1033
1071
|
},
|
1034
1072
|
messageId: AVOID_TYPENAME_PREFIX,
|
1035
|
-
loc:
|
1036
|
-
start: {
|
1037
|
-
line: start.line,
|
1038
|
-
column: start.column - 1,
|
1039
|
-
},
|
1040
|
-
end: {
|
1041
|
-
line: start.line,
|
1042
|
-
column: start.column - 1 + lowerTypeName.length,
|
1043
|
-
},
|
1044
|
-
},
|
1073
|
+
loc: getLocation(field.loc, lowerTypeName),
|
1045
1074
|
});
|
1046
1075
|
}
|
1047
1076
|
}
|
@@ -1101,7 +1130,7 @@ const rule$5 = {
|
|
1101
1130
|
'[description.type="StringValue"]': node => {
|
1102
1131
|
if (node.description.block !== (style === 'block')) {
|
1103
1132
|
context.report({
|
1104
|
-
|
1133
|
+
loc: getLocation(node.description.loc),
|
1105
1134
|
message: `Unexpected ${wrongDescriptionType} description`,
|
1106
1135
|
});
|
1107
1136
|
}
|
@@ -1110,6 +1139,9 @@ const rule$5 = {
|
|
1110
1139
|
},
|
1111
1140
|
};
|
1112
1141
|
|
1142
|
+
const isObjectType = (node) => [graphql.Kind.OBJECT_TYPE_DEFINITION, graphql.Kind.OBJECT_TYPE_EXTENSION].includes(node.type);
|
1143
|
+
const isQueryType = (node) => isObjectType(node) && node.name.value === 'Query';
|
1144
|
+
const isMutationType = (node) => isObjectType(node) && node.name.value === 'Mutation';
|
1113
1145
|
const rule$6 = {
|
1114
1146
|
meta: {
|
1115
1147
|
type: 'suggestion',
|
@@ -1150,6 +1182,7 @@ const rule$6 = {
|
|
1150
1182
|
schema: [
|
1151
1183
|
{
|
1152
1184
|
type: 'object',
|
1185
|
+
additionalProperties: false,
|
1153
1186
|
properties: {
|
1154
1187
|
checkInputType: {
|
1155
1188
|
type: 'boolean',
|
@@ -1172,35 +1205,34 @@ const rule$6 = {
|
|
1172
1205
|
description: 'Apply the rule to Mutations',
|
1173
1206
|
},
|
1174
1207
|
},
|
1175
|
-
additionalProperties: false,
|
1176
1208
|
},
|
1177
1209
|
],
|
1178
1210
|
},
|
1179
1211
|
create(context) {
|
1180
|
-
var _a;
|
1181
1212
|
const options = {
|
1182
|
-
caseSensitiveInputType: true,
|
1183
1213
|
checkInputType: false,
|
1184
|
-
|
1214
|
+
caseSensitiveInputType: true,
|
1185
1215
|
checkQueries: false,
|
1186
|
-
|
1216
|
+
checkMutations: true,
|
1217
|
+
...context.options[0],
|
1187
1218
|
};
|
1188
1219
|
const shouldCheckType = node => (options.checkMutations && isMutationType(node)) || (options.checkQueries && isQueryType(node));
|
1189
1220
|
const listeners = {
|
1190
|
-
'FieldDefinition > InputValueDefinition'
|
1191
|
-
if (
|
1221
|
+
'FieldDefinition > InputValueDefinition[name.value!=input]'(node) {
|
1222
|
+
if (shouldCheckType(node.parent.parent)) {
|
1223
|
+
const name = node.name.value;
|
1192
1224
|
context.report({
|
1193
|
-
|
1194
|
-
message: `Input "${
|
1225
|
+
loc: getLocation(node.loc, name),
|
1226
|
+
message: `Input "${name}" should be called "input"`,
|
1195
1227
|
});
|
1196
1228
|
}
|
1197
1229
|
},
|
1198
1230
|
};
|
1199
|
-
if (options
|
1200
|
-
listeners['FieldDefinition > InputValueDefinition NamedType'] = node => {
|
1231
|
+
if (options.checkInputType) {
|
1232
|
+
listeners['FieldDefinition > InputValueDefinition NamedType'] = (node) => {
|
1201
1233
|
const findInputType = item => {
|
1202
1234
|
let currentNode = item;
|
1203
|
-
while (currentNode.type !==
|
1235
|
+
while (currentNode.type !== graphql.Kind.INPUT_VALUE_DEFINITION) {
|
1204
1236
|
currentNode = currentNode.parent;
|
1205
1237
|
}
|
1206
1238
|
return currentNode;
|
@@ -1208,11 +1240,12 @@ const rule$6 = {
|
|
1208
1240
|
const inputValueNode = findInputType(node);
|
1209
1241
|
if (shouldCheckType(inputValueNode.parent.parent)) {
|
1210
1242
|
const mutationName = `${inputValueNode.parent.name.value}Input`;
|
1243
|
+
const name = node.name.value;
|
1211
1244
|
if ((options.caseSensitiveInputType && node.name.value !== mutationName) ||
|
1212
|
-
|
1245
|
+
name.toLowerCase() !== mutationName.toLowerCase()) {
|
1213
1246
|
context.report({
|
1214
|
-
node,
|
1215
|
-
message: `InputType "${
|
1247
|
+
loc: getLocation(node.loc, name),
|
1248
|
+
message: `InputType "${name}" name should be "${mutationName}"`,
|
1216
1249
|
});
|
1217
1250
|
}
|
1218
1251
|
}
|
@@ -1369,7 +1402,8 @@ const rule$7 = {
|
|
1369
1402
|
var _a;
|
1370
1403
|
if (options.fileExtension && options.fileExtension !== fileExtension) {
|
1371
1404
|
context.report({
|
1372
|
-
|
1405
|
+
// Report on first character
|
1406
|
+
loc: { column: 0, line: 1 },
|
1373
1407
|
messageId: MATCH_EXTENSION,
|
1374
1408
|
data: {
|
1375
1409
|
fileExtension,
|
@@ -1401,7 +1435,8 @@ const rule$7 = {
|
|
1401
1435
|
const filenameWithExtension = filename + expectedExtension;
|
1402
1436
|
if (expectedFilename !== filenameWithExtension) {
|
1403
1437
|
context.report({
|
1404
|
-
|
1438
|
+
// Report on first character
|
1439
|
+
loc: { column: 0, line: 1 },
|
1405
1440
|
messageId: MATCH_STYLE,
|
1406
1441
|
data: {
|
1407
1442
|
expectedFilename,
|
@@ -1414,69 +1449,40 @@ const rule$7 = {
|
|
1414
1449
|
},
|
1415
1450
|
};
|
1416
1451
|
|
1417
|
-
const
|
1418
|
-
|
1419
|
-
|
1420
|
-
|
1421
|
-
|
1422
|
-
|
1423
|
-
const acceptedStyles = [
|
1424
|
-
'camelCase',
|
1425
|
-
'PascalCase',
|
1426
|
-
'snake_case',
|
1427
|
-
'UPPER_CASE',
|
1452
|
+
const FIELDS_KINDS = [
|
1453
|
+
graphql.Kind.FIELD_DEFINITION,
|
1454
|
+
graphql.Kind.INPUT_VALUE_DEFINITION,
|
1455
|
+
graphql.Kind.VARIABLE_DEFINITION,
|
1456
|
+
graphql.Kind.ARGUMENT,
|
1457
|
+
graphql.Kind.DIRECTIVE_DEFINITION,
|
1428
1458
|
];
|
1429
|
-
|
1430
|
-
|
1431
|
-
|
1432
|
-
|
1433
|
-
|
1434
|
-
|
1435
|
-
|
1436
|
-
|
1437
|
-
|
1438
|
-
|
1439
|
-
|
1440
|
-
|
1441
|
-
|
1442
|
-
|
1443
|
-
|
1444
|
-
|
1445
|
-
|
1446
|
-
|
1447
|
-
|
1448
|
-
|
1449
|
-
|
1450
|
-
|
1451
|
-
|
1452
|
-
|
1453
|
-
|
1454
|
-
|
1455
|
-
|
1456
|
-
if (forbiddenPrefixes.some(forbiddenPrefix => name.startsWith(forbiddenPrefix))) {
|
1457
|
-
return {
|
1458
|
-
ok: false,
|
1459
|
-
errorMessage: '{{nodeType}} "{{nodeName}}" should not have one of the following prefix(es): {{forbiddenPrefixes}}',
|
1460
|
-
};
|
1461
|
-
}
|
1462
|
-
if (forbiddenSuffixes.some(forbiddenSuffix => name.endsWith(forbiddenSuffix))) {
|
1463
|
-
return {
|
1464
|
-
ok: false,
|
1465
|
-
errorMessage: '{{nodeType}} "{{nodeName}}" should not have one of the following suffix(es): {{forbiddenSuffixes}}',
|
1466
|
-
};
|
1467
|
-
}
|
1468
|
-
if (!formats[style]) {
|
1469
|
-
return { ok: true };
|
1470
|
-
}
|
1471
|
-
const ok = new RegExp(formats[style]).test(name);
|
1472
|
-
if (ok) {
|
1473
|
-
return { ok: true };
|
1474
|
-
}
|
1475
|
-
return {
|
1476
|
-
ok: false,
|
1477
|
-
errorMessage: '{{nodeType}} name "{{nodeName}}" should be in {{format}} format',
|
1478
|
-
};
|
1479
|
-
}
|
1459
|
+
const KindToDisplayName = {
|
1460
|
+
// types
|
1461
|
+
[graphql.Kind.OBJECT_TYPE_DEFINITION]: 'Type',
|
1462
|
+
[graphql.Kind.INTERFACE_TYPE_DEFINITION]: 'Interface',
|
1463
|
+
[graphql.Kind.ENUM_TYPE_DEFINITION]: 'Enumerator',
|
1464
|
+
[graphql.Kind.SCALAR_TYPE_DEFINITION]: 'Scalar',
|
1465
|
+
[graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION]: 'Input type',
|
1466
|
+
[graphql.Kind.UNION_TYPE_DEFINITION]: 'Union',
|
1467
|
+
// fields
|
1468
|
+
[graphql.Kind.FIELD_DEFINITION]: 'Field',
|
1469
|
+
[graphql.Kind.INPUT_VALUE_DEFINITION]: 'Input property',
|
1470
|
+
[graphql.Kind.VARIABLE_DEFINITION]: 'Variable',
|
1471
|
+
[graphql.Kind.ARGUMENT]: 'Argument',
|
1472
|
+
[graphql.Kind.DIRECTIVE_DEFINITION]: 'Directive',
|
1473
|
+
// rest
|
1474
|
+
[graphql.Kind.ENUM_VALUE_DEFINITION]: 'Enumeration value',
|
1475
|
+
[graphql.Kind.OPERATION_DEFINITION]: 'Operation',
|
1476
|
+
[graphql.Kind.FRAGMENT_DEFINITION]: 'Fragment',
|
1477
|
+
};
|
1478
|
+
const StyleToRegex = {
|
1479
|
+
camelCase: /^[a-z][\dA-Za-z]*$/,
|
1480
|
+
PascalCase: /^[A-Z][\dA-Za-z]*$/,
|
1481
|
+
snake_case: /^[a-z][\d_a-z]*[\da-z]$/,
|
1482
|
+
UPPER_CASE: /^[A-Z][\dA-Z_]*[\dA-Z]$/,
|
1483
|
+
};
|
1484
|
+
const ALLOWED_KINDS = Object.keys(KindToDisplayName).sort();
|
1485
|
+
const ALLOWED_STYLES = Object.keys(StyleToRegex);
|
1480
1486
|
const schemaOption$1 = {
|
1481
1487
|
oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
|
1482
1488
|
};
|
@@ -1491,89 +1497,120 @@ const rule$8 = {
|
|
1491
1497
|
examples: [
|
1492
1498
|
{
|
1493
1499
|
title: 'Incorrect',
|
1494
|
-
usage: [{
|
1500
|
+
usage: [{ types: 'PascalCase', fields: 'camelCase' }],
|
1495
1501
|
code: /* GraphQL */ `
|
1496
|
-
type
|
1497
|
-
|
1502
|
+
type user {
|
1503
|
+
first_name: String!
|
1498
1504
|
}
|
1499
1505
|
`,
|
1500
1506
|
},
|
1501
1507
|
{
|
1502
1508
|
title: 'Correct',
|
1503
|
-
usage: [{
|
1509
|
+
usage: [{ types: 'PascalCase', fields: 'camelCase' }],
|
1504
1510
|
code: /* GraphQL */ `
|
1505
|
-
type
|
1506
|
-
|
1511
|
+
type User {
|
1512
|
+
firstName: String
|
1507
1513
|
}
|
1508
1514
|
`,
|
1509
1515
|
},
|
1510
1516
|
],
|
1517
|
+
optionsForConfig: [
|
1518
|
+
{
|
1519
|
+
types: 'PascalCase',
|
1520
|
+
fields: 'camelCase',
|
1521
|
+
overrides: {
|
1522
|
+
EnumValueDefinition: 'UPPER_CASE',
|
1523
|
+
OperationDefinition: {
|
1524
|
+
style: 'PascalCase',
|
1525
|
+
forbiddenPrefixes: ['Query', 'Mutation', 'Subscription', 'Get'],
|
1526
|
+
forbiddenSuffixes: ['Query', 'Mutation', 'Subscription'],
|
1527
|
+
},
|
1528
|
+
FragmentDefinition: {
|
1529
|
+
style: 'PascalCase',
|
1530
|
+
forbiddenPrefixes: ['Fragment'],
|
1531
|
+
forbiddenSuffixes: ['Fragment'],
|
1532
|
+
},
|
1533
|
+
'FieldDefinition[parent.name.value=Query]': {
|
1534
|
+
forbiddenPrefixes: ['query', 'get'],
|
1535
|
+
forbiddenSuffixes: ['Query'],
|
1536
|
+
},
|
1537
|
+
'FieldDefinition[parent.name.value=Mutation]': {
|
1538
|
+
forbiddenPrefixes: ['mutation'],
|
1539
|
+
forbiddenSuffixes: ['Mutation'],
|
1540
|
+
},
|
1541
|
+
'FieldDefinition[parent.name.value=Subscription]': {
|
1542
|
+
forbiddenPrefixes: ['subscription'],
|
1543
|
+
forbiddenSuffixes: ['Subscription'],
|
1544
|
+
},
|
1545
|
+
},
|
1546
|
+
},
|
1547
|
+
],
|
1511
1548
|
},
|
1512
1549
|
schema: {
|
1513
1550
|
definitions: {
|
1514
1551
|
asString: {
|
1515
|
-
|
1516
|
-
description: `One of: ${
|
1517
|
-
enum: acceptedStyles,
|
1552
|
+
enum: ALLOWED_STYLES,
|
1553
|
+
description: `One of: ${ALLOWED_STYLES.map(t => `\`${t}\``).join(', ')}`,
|
1518
1554
|
},
|
1519
1555
|
asObject: {
|
1520
1556
|
type: 'object',
|
1557
|
+
additionalProperties: false,
|
1521
1558
|
properties: {
|
1522
|
-
style: {
|
1523
|
-
|
1524
|
-
|
1525
|
-
},
|
1526
|
-
prefix: {
|
1527
|
-
type: 'string',
|
1528
|
-
},
|
1529
|
-
suffix: {
|
1530
|
-
type: 'string',
|
1531
|
-
},
|
1559
|
+
style: { enum: ALLOWED_STYLES },
|
1560
|
+
prefix: { type: 'string' },
|
1561
|
+
suffix: { type: 'string' },
|
1532
1562
|
forbiddenPrefixes: {
|
1533
|
-
additionalItems: false,
|
1534
1563
|
type: 'array',
|
1564
|
+
uniqueItems: true,
|
1535
1565
|
minItems: 1,
|
1536
|
-
items: {
|
1537
|
-
type: 'string',
|
1538
|
-
},
|
1566
|
+
items: { type: 'string' },
|
1539
1567
|
},
|
1540
1568
|
forbiddenSuffixes: {
|
1541
|
-
additionalItems: false,
|
1542
1569
|
type: 'array',
|
1570
|
+
uniqueItems: true,
|
1543
1571
|
minItems: 1,
|
1544
|
-
items: {
|
1545
|
-
type: 'string',
|
1546
|
-
},
|
1572
|
+
items: { type: 'string' },
|
1547
1573
|
},
|
1548
1574
|
},
|
1549
1575
|
},
|
1550
1576
|
},
|
1551
|
-
$schema: 'http://json-schema.org/draft-04/schema#',
|
1552
1577
|
type: 'array',
|
1578
|
+
maxItems: 1,
|
1553
1579
|
items: {
|
1554
1580
|
type: 'object',
|
1581
|
+
additionalProperties: false,
|
1555
1582
|
properties: {
|
1556
|
-
|
1557
|
-
|
1558
|
-
|
1559
|
-
[graphql.Kind.INPUT_VALUE_DEFINITION]: schemaOption$1,
|
1560
|
-
[graphql.Kind.OBJECT_TYPE_DEFINITION]: schemaOption$1,
|
1561
|
-
[graphql.Kind.INTERFACE_TYPE_DEFINITION]: schemaOption$1,
|
1562
|
-
[graphql.Kind.ENUM_TYPE_DEFINITION]: schemaOption$1,
|
1563
|
-
[graphql.Kind.UNION_TYPE_DEFINITION]: schemaOption$1,
|
1564
|
-
[graphql.Kind.SCALAR_TYPE_DEFINITION]: schemaOption$1,
|
1565
|
-
[graphql.Kind.OPERATION_DEFINITION]: schemaOption$1,
|
1566
|
-
[graphql.Kind.FRAGMENT_DEFINITION]: schemaOption$1,
|
1567
|
-
QueryDefinition: schemaOption$1,
|
1568
|
-
leadingUnderscore: {
|
1569
|
-
type: 'string',
|
1570
|
-
enum: ['allow', 'forbid'],
|
1571
|
-
default: 'forbid',
|
1583
|
+
types: {
|
1584
|
+
...schemaOption$1,
|
1585
|
+
description: `Includes:\n\n${TYPES_KINDS.map(kind => `- \`${kind}\``).join('\n')}`,
|
1572
1586
|
},
|
1573
|
-
|
1574
|
-
|
1575
|
-
|
1576
|
-
|
1587
|
+
fields: {
|
1588
|
+
...schemaOption$1,
|
1589
|
+
description: `Includes:\n\n${FIELDS_KINDS.map(kind => `- \`${kind}\``).join('\n')}`,
|
1590
|
+
},
|
1591
|
+
allowLeadingUnderscore: {
|
1592
|
+
type: 'boolean',
|
1593
|
+
default: false,
|
1594
|
+
},
|
1595
|
+
allowTrailingUnderscore: {
|
1596
|
+
type: 'boolean',
|
1597
|
+
default: false,
|
1598
|
+
},
|
1599
|
+
overrides: {
|
1600
|
+
type: 'object',
|
1601
|
+
additionalProperties: false,
|
1602
|
+
description: [
|
1603
|
+
'May contain the following `ASTNode` names:',
|
1604
|
+
'',
|
1605
|
+
...ALLOWED_KINDS.map(kind => `- \`${kind}\``),
|
1606
|
+
'',
|
1607
|
+
"> It's also possible to use a [`selector`](https://eslint.org/docs/developer-guide/selectors) that starts with `ASTNode` name",
|
1608
|
+
'>',
|
1609
|
+
'> Example: pattern property `FieldDefinition[parent.name.value=Query]` will match only fields for type `Query`',
|
1610
|
+
].join('\n'),
|
1611
|
+
patternProperties: {
|
1612
|
+
[`^(${ALLOWED_KINDS.join('|')})(.+)?$`]: schemaOption$1,
|
1613
|
+
},
|
1577
1614
|
},
|
1578
1615
|
},
|
1579
1616
|
},
|
@@ -1581,130 +1618,83 @@ const rule$8 = {
|
|
1581
1618
|
},
|
1582
1619
|
create(context) {
|
1583
1620
|
const options = {
|
1584
|
-
|
1585
|
-
|
1586
|
-
...(context.options[0] || {}),
|
1621
|
+
overrides: {},
|
1622
|
+
...context.options[0],
|
1587
1623
|
};
|
1588
|
-
|
1589
|
-
|
1590
|
-
|
1591
|
-
|
1592
|
-
|
1593
|
-
|
1594
|
-
|
1595
|
-
|
1596
|
-
|
1597
|
-
|
1598
|
-
|
1599
|
-
}
|
1600
|
-
|
1624
|
+
function normalisePropertyOption(kind) {
|
1625
|
+
let style = options.overrides[kind];
|
1626
|
+
if (!style) {
|
1627
|
+
style = TYPES_KINDS.includes(kind) ? options.types : options.fields;
|
1628
|
+
}
|
1629
|
+
return typeof style === 'object' ? style : { style };
|
1630
|
+
}
|
1631
|
+
const checkNode = (selector) => (node) => {
|
1632
|
+
const { name } = node.kind === graphql.Kind.VARIABLE_DEFINITION ? node.variable : node;
|
1633
|
+
if (!name) {
|
1634
|
+
return;
|
1635
|
+
}
|
1636
|
+
const { prefix, suffix, forbiddenPrefixes, forbiddenSuffixes, style } = normalisePropertyOption(selector);
|
1637
|
+
const nodeType = KindToDisplayName[node.kind] || node.kind;
|
1638
|
+
const nodeName = name.value;
|
1639
|
+
const errorMessage = getErrorMessage();
|
1640
|
+
if (errorMessage) {
|
1601
1641
|
context.report({
|
1602
|
-
|
1603
|
-
message:
|
1604
|
-
data: {
|
1605
|
-
prefix,
|
1606
|
-
suffix,
|
1607
|
-
format: style,
|
1608
|
-
forbiddenPrefixes: forbiddenPrefixes.join(', '),
|
1609
|
-
forbiddenSuffixes: forbiddenSuffixes.join(', '),
|
1610
|
-
nodeType,
|
1611
|
-
nodeName: node.value,
|
1612
|
-
},
|
1642
|
+
loc: getLocation(name.loc, name.value),
|
1643
|
+
message: `${nodeType} "${nodeName}" should ${errorMessage}`,
|
1613
1644
|
});
|
1614
1645
|
}
|
1615
|
-
|
1616
|
-
|
1617
|
-
|
1618
|
-
|
1619
|
-
}
|
1620
|
-
return {
|
1621
|
-
style: value,
|
1622
|
-
prefix: '',
|
1623
|
-
suffix: '',
|
1624
|
-
};
|
1625
|
-
};
|
1626
|
-
return {
|
1627
|
-
Name: node => {
|
1628
|
-
if (node.value.startsWith('_') && options.leadingUnderscore === 'forbid') {
|
1629
|
-
context.report({ node, message: 'Leading underscores are not allowed' });
|
1646
|
+
function getErrorMessage() {
|
1647
|
+
let name = nodeName;
|
1648
|
+
if (options.allowLeadingUnderscore) {
|
1649
|
+
name = name.replace(/^_*/, '');
|
1630
1650
|
}
|
1631
|
-
if (
|
1632
|
-
|
1651
|
+
if (options.allowTrailingUnderscore) {
|
1652
|
+
name = name.replace(/_*$/, '');
|
1633
1653
|
}
|
1634
|
-
|
1635
|
-
|
1636
|
-
if (options.ObjectTypeDefinition) {
|
1637
|
-
const property = normalisePropertyOption(options.ObjectTypeDefinition);
|
1638
|
-
checkNode(node.name, property, 'Type');
|
1654
|
+
if (prefix && !name.startsWith(prefix)) {
|
1655
|
+
return `have "${prefix}" prefix`;
|
1639
1656
|
}
|
1640
|
-
|
1641
|
-
|
1642
|
-
if (options.InterfaceTypeDefinition) {
|
1643
|
-
const property = normalisePropertyOption(options.InterfaceTypeDefinition);
|
1644
|
-
checkNode(node.name, property, 'Interface');
|
1657
|
+
if (suffix && !name.endsWith(suffix)) {
|
1658
|
+
return `have "${suffix}" suffix`;
|
1645
1659
|
}
|
1646
|
-
|
1647
|
-
|
1648
|
-
|
1649
|
-
const property = normalisePropertyOption(options.EnumTypeDefinition);
|
1650
|
-
checkNode(node.name, property, 'Enumerator');
|
1660
|
+
const forbiddenPrefix = forbiddenPrefixes === null || forbiddenPrefixes === void 0 ? void 0 : forbiddenPrefixes.find(prefix => name.startsWith(prefix));
|
1661
|
+
if (forbiddenPrefix) {
|
1662
|
+
return `not have "${forbiddenPrefix}" prefix`;
|
1651
1663
|
}
|
1652
|
-
|
1653
|
-
|
1654
|
-
|
1655
|
-
const property = normalisePropertyOption(options.InputObjectTypeDefinition);
|
1656
|
-
checkNode(node.name, property, 'Input type');
|
1664
|
+
const forbiddenSuffix = forbiddenSuffixes === null || forbiddenSuffixes === void 0 ? void 0 : forbiddenSuffixes.find(suffix => name.endsWith(suffix));
|
1665
|
+
if (forbiddenSuffix) {
|
1666
|
+
return `not have "${forbiddenSuffix}" suffix`;
|
1657
1667
|
}
|
1658
|
-
|
1659
|
-
|
1660
|
-
if (options.QueryDefinition && isQueryType(node.parent)) {
|
1661
|
-
const property = normalisePropertyOption(options.QueryDefinition);
|
1662
|
-
checkNode(node.name, property, 'Query');
|
1668
|
+
if (style && !ALLOWED_STYLES.includes(style)) {
|
1669
|
+
return `be in one of the following options: ${ALLOWED_STYLES.join(', ')}`;
|
1663
1670
|
}
|
1664
|
-
|
1665
|
-
|
1666
|
-
|
1667
|
-
}
|
1668
|
-
},
|
1669
|
-
EnumValueDefinition: node => {
|
1670
|
-
if (options.EnumValueDefinition) {
|
1671
|
-
const property = normalisePropertyOption(options.EnumValueDefinition);
|
1672
|
-
checkNode(node.name, property, 'Enumeration value');
|
1673
|
-
}
|
1674
|
-
},
|
1675
|
-
InputValueDefinition: node => {
|
1676
|
-
if (options.InputValueDefinition) {
|
1677
|
-
const property = normalisePropertyOption(options.InputValueDefinition);
|
1678
|
-
checkNode(node.name, property, 'Input property');
|
1671
|
+
const caseRegex = StyleToRegex[style];
|
1672
|
+
if (caseRegex && !caseRegex.test(name)) {
|
1673
|
+
return `be in ${style} format`;
|
1679
1674
|
}
|
1680
|
-
}
|
1681
|
-
|
1682
|
-
|
1683
|
-
|
1684
|
-
|
1685
|
-
|
1686
|
-
|
1687
|
-
|
1688
|
-
},
|
1689
|
-
FragmentDefinition: node => {
|
1690
|
-
if (options.FragmentDefinition) {
|
1691
|
-
const property = normalisePropertyOption(options.FragmentDefinition);
|
1692
|
-
checkNode(node.name, property, 'Fragment');
|
1693
|
-
}
|
1694
|
-
},
|
1695
|
-
ScalarTypeDefinition: node => {
|
1696
|
-
if (options.ScalarTypeDefinition) {
|
1697
|
-
const property = normalisePropertyOption(options.ScalarTypeDefinition);
|
1698
|
-
checkNode(node.name, property, 'Scalar');
|
1699
|
-
}
|
1700
|
-
},
|
1701
|
-
UnionTypeDefinition: node => {
|
1702
|
-
if (options.UnionTypeDefinition) {
|
1703
|
-
const property = normalisePropertyOption(options.UnionTypeDefinition);
|
1704
|
-
checkNode(node.name, property, 'Union');
|
1705
|
-
}
|
1706
|
-
},
|
1675
|
+
}
|
1676
|
+
};
|
1677
|
+
const checkUnderscore = (node) => {
|
1678
|
+
const name = node.value;
|
1679
|
+
context.report({
|
1680
|
+
loc: getLocation(node.loc, name),
|
1681
|
+
message: `${name.startsWith('_') ? 'Leading' : 'Trailing'} underscores are not allowed`,
|
1682
|
+
});
|
1707
1683
|
};
|
1684
|
+
const listeners = {};
|
1685
|
+
if (!options.allowLeadingUnderscore) {
|
1686
|
+
listeners['Name[value=/^_/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore;
|
1687
|
+
}
|
1688
|
+
if (!options.allowTrailingUnderscore) {
|
1689
|
+
listeners['Name[value=/_$/]:matches([parent.kind!=Field], [parent.kind=Field][parent.alias])'] = checkUnderscore;
|
1690
|
+
}
|
1691
|
+
const selectors = new Set([options.types && TYPES_KINDS, options.fields && FIELDS_KINDS, Object.keys(options.overrides)]
|
1692
|
+
.flat()
|
1693
|
+
.filter(Boolean));
|
1694
|
+
for (const selector of selectors) {
|
1695
|
+
listeners[selector] = checkNode(selector);
|
1696
|
+
}
|
1697
|
+
return listeners;
|
1708
1698
|
},
|
1709
1699
|
};
|
1710
1700
|
|
@@ -1743,28 +1733,14 @@ const rule$9 = {
|
|
1743
1733
|
},
|
1744
1734
|
create(context) {
|
1745
1735
|
return {
|
1746
|
-
OperationDefinition(node) {
|
1747
|
-
|
1748
|
-
|
1749
|
-
|
1750
|
-
|
1751
|
-
|
1752
|
-
|
1753
|
-
|
1754
|
-
column: start.column - 1,
|
1755
|
-
line: start.line,
|
1756
|
-
},
|
1757
|
-
end: {
|
1758
|
-
column: start.column - 1 + node.operation.length,
|
1759
|
-
line: start.line,
|
1760
|
-
},
|
1761
|
-
},
|
1762
|
-
data: {
|
1763
|
-
operation: node.operation,
|
1764
|
-
},
|
1765
|
-
messageId: NO_ANONYMOUS_OPERATIONS,
|
1766
|
-
});
|
1767
|
-
}
|
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
|
+
});
|
1768
1744
|
},
|
1769
1745
|
};
|
1770
1746
|
},
|
@@ -1871,8 +1847,8 @@ const rule$b = {
|
|
1871
1847
|
mutation {
|
1872
1848
|
changeSomething(
|
1873
1849
|
type: OLD # This is deprecated, so you'll get an error
|
1874
|
-
) {
|
1875
|
-
...
|
1850
|
+
) {
|
1851
|
+
...
|
1876
1852
|
}
|
1877
1853
|
}
|
1878
1854
|
`,
|
@@ -1909,9 +1885,10 @@ const rule$b = {
|
|
1909
1885
|
requireGraphQLSchemaFromContext('no-deprecated', context);
|
1910
1886
|
const typeInfo = node.typeInfo();
|
1911
1887
|
if (typeInfo && typeInfo.enumValue) {
|
1912
|
-
if (typeInfo.enumValue.
|
1888
|
+
if (typeInfo.enumValue.isDeprecated) {
|
1889
|
+
const enumValueName = node.value;
|
1913
1890
|
context.report({
|
1914
|
-
loc: node.loc,
|
1891
|
+
loc: getLocation(node.loc, enumValueName),
|
1915
1892
|
messageId: NO_DEPRECATED,
|
1916
1893
|
data: {
|
1917
1894
|
type: 'enum value',
|
@@ -1925,9 +1902,10 @@ const rule$b = {
|
|
1925
1902
|
requireGraphQLSchemaFromContext('no-deprecated', context);
|
1926
1903
|
const typeInfo = node.typeInfo();
|
1927
1904
|
if (typeInfo && typeInfo.fieldDef) {
|
1928
|
-
if (typeInfo.fieldDef.
|
1905
|
+
if (typeInfo.fieldDef.isDeprecated) {
|
1906
|
+
const fieldName = node.name.value;
|
1929
1907
|
context.report({
|
1930
|
-
loc: node.loc,
|
1908
|
+
loc: getLocation(node.loc, fieldName),
|
1931
1909
|
messageId: NO_DEPRECATED,
|
1932
1910
|
data: {
|
1933
1911
|
type: 'field',
|
@@ -2003,10 +1981,7 @@ const rule$c = {
|
|
2003
1981
|
if (!isEslintComment && line !== prev.line && next.kind === graphql.TokenKind.NAME && linesAfter < 2) {
|
2004
1982
|
context.report({
|
2005
1983
|
messageId: HASHTAG_COMMENT,
|
2006
|
-
loc: {
|
2007
|
-
start: { line, column },
|
2008
|
-
end: { line, column },
|
2009
|
-
},
|
1984
|
+
loc: getLocation({ start: { line, column } }),
|
2010
1985
|
});
|
2011
1986
|
}
|
2012
1987
|
}
|
@@ -2135,7 +2110,7 @@ const rule$e = {
|
|
2135
2110
|
const typeName = node.name.value;
|
2136
2111
|
if (!reachableTypes.has(typeName)) {
|
2137
2112
|
context.report({
|
2138
|
-
node,
|
2113
|
+
loc: getLocation(node.name.loc, typeName, { offsetStart: node.kind === graphql.Kind.DIRECTIVE_DEFINITION ? 2 : 1 }),
|
2139
2114
|
messageId: UNREACHABLE_TYPE,
|
2140
2115
|
data: { typeName },
|
2141
2116
|
fix: fixer => fixer.remove(node),
|
@@ -2233,7 +2208,7 @@ const rule$f = {
|
|
2233
2208
|
return;
|
2234
2209
|
}
|
2235
2210
|
context.report({
|
2236
|
-
node,
|
2211
|
+
loc: getLocation(node.loc, fieldName),
|
2237
2212
|
messageId: UNUSED_FIELD,
|
2238
2213
|
data: { fieldName },
|
2239
2214
|
fix(fixer) {
|
@@ -2345,6 +2320,7 @@ function convertDescription(node) {
|
|
2345
2320
|
return [];
|
2346
2321
|
}
|
2347
2322
|
|
2323
|
+
// eslint-disable-next-line unicorn/better-regex
|
2348
2324
|
const DATE_REGEX = /^\d{2}\/\d{2}\/\d{4}$/;
|
2349
2325
|
const MESSAGE_REQUIRE_DATE = 'MESSAGE_REQUIRE_DATE';
|
2350
2326
|
const MESSAGE_INVALID_FORMAT = 'MESSAGE_INVALID_FORMAT';
|
@@ -2388,10 +2364,10 @@ const rule$g = {
|
|
2388
2364
|
],
|
2389
2365
|
},
|
2390
2366
|
messages: {
|
2391
|
-
[MESSAGE_REQUIRE_DATE]: 'Directive "@deprecated" must have a deletion date
|
2392
|
-
[MESSAGE_INVALID_FORMAT]: 'Deletion date must be in format "DD/MM/YYYY"
|
2393
|
-
[MESSAGE_INVALID_DATE]: 'Invalid "{{ deletionDate }}" deletion date
|
2394
|
-
[MESSAGE_CAN_BE_REMOVED]: '"{{ nodeName }}" сan be removed
|
2367
|
+
[MESSAGE_REQUIRE_DATE]: 'Directive "@deprecated" must have a deletion date',
|
2368
|
+
[MESSAGE_INVALID_FORMAT]: 'Deletion date must be in format "DD/MM/YYYY"',
|
2369
|
+
[MESSAGE_INVALID_DATE]: 'Invalid "{{ deletionDate }}" deletion date',
|
2370
|
+
[MESSAGE_CAN_BE_REMOVED]: '"{{ nodeName }}" сan be removed',
|
2395
2371
|
},
|
2396
2372
|
schema: [
|
2397
2373
|
{
|
@@ -2412,13 +2388,16 @@ const rule$g = {
|
|
2412
2388
|
const argName = ((_a = context.options[0]) === null || _a === void 0 ? void 0 : _a.argumentName) || 'deletionDate';
|
2413
2389
|
const deletionDateNode = node.arguments.find(arg => arg.name.value === argName);
|
2414
2390
|
if (!deletionDateNode) {
|
2415
|
-
context.report({
|
2391
|
+
context.report({
|
2392
|
+
loc: getLocation(node.loc, node.name.value, { offsetEnd: 0 }),
|
2393
|
+
messageId: MESSAGE_REQUIRE_DATE,
|
2394
|
+
});
|
2416
2395
|
return;
|
2417
2396
|
}
|
2418
2397
|
const deletionDate = valueFromNode(deletionDateNode.value);
|
2419
2398
|
const isValidDate = DATE_REGEX.test(deletionDate);
|
2420
2399
|
if (!isValidDate) {
|
2421
|
-
context.report({ node:
|
2400
|
+
context.report({ node: deletionDateNode.value, messageId: MESSAGE_INVALID_FORMAT });
|
2422
2401
|
return;
|
2423
2402
|
}
|
2424
2403
|
let [day, month, year] = deletionDate.split('/');
|
@@ -2427,7 +2406,7 @@ const rule$g = {
|
|
2427
2406
|
const deletionDateInMS = Date.parse(`${year}-${month}-${day}`);
|
2428
2407
|
if (Number.isNaN(deletionDateInMS)) {
|
2429
2408
|
context.report({
|
2430
|
-
node:
|
2409
|
+
node: deletionDateNode.value,
|
2431
2410
|
messageId: MESSAGE_INVALID_DATE,
|
2432
2411
|
data: {
|
2433
2412
|
deletionDate,
|
@@ -2438,7 +2417,7 @@ const rule$g = {
|
|
2438
2417
|
const canRemove = Date.now() > deletionDateInMS;
|
2439
2418
|
if (canRemove) {
|
2440
2419
|
context.report({
|
2441
|
-
node
|
2420
|
+
node,
|
2442
2421
|
messageId: MESSAGE_CAN_BE_REMOVED,
|
2443
2422
|
data: {
|
2444
2423
|
nodeName: node.parent.name.value,
|
@@ -2489,17 +2468,15 @@ const rule$h = {
|
|
2489
2468
|
},
|
2490
2469
|
create(context) {
|
2491
2470
|
return {
|
2492
|
-
Directive(node) {
|
2493
|
-
|
2494
|
-
|
2495
|
-
|
2496
|
-
|
2497
|
-
|
2498
|
-
|
2499
|
-
|
2500
|
-
|
2501
|
-
});
|
2502
|
-
}
|
2471
|
+
'Directive[name.value=deprecated]'(node) {
|
2472
|
+
const args = node.arguments || [];
|
2473
|
+
const reasonArg = args.find(arg => arg.name && arg.name.value === 'reason');
|
2474
|
+
const value = reasonArg ? String(valueFromNode(reasonArg.value) || '').trim() : null;
|
2475
|
+
if (!value) {
|
2476
|
+
context.report({
|
2477
|
+
loc: getLocation(node.loc, node.name.value, { offsetEnd: 0 }),
|
2478
|
+
message: 'Directive "@deprecated" must have a reason!',
|
2479
|
+
});
|
2503
2480
|
}
|
2504
2481
|
},
|
2505
2482
|
};
|
@@ -2522,20 +2499,8 @@ const DESCRIBABLE_NODES = [
|
|
2522
2499
|
function verifyRule(context, node) {
|
2523
2500
|
if (node) {
|
2524
2501
|
if (!node.description || !node.description.value || node.description.value.trim().length === 0) {
|
2525
|
-
const { start, end } = ('name' in node ? node.name : node).loc;
|
2526
2502
|
context.report({
|
2527
|
-
loc:
|
2528
|
-
start: {
|
2529
|
-
line: start.line,
|
2530
|
-
column: start.column - 1,
|
2531
|
-
},
|
2532
|
-
end: {
|
2533
|
-
line: end.line,
|
2534
|
-
column:
|
2535
|
-
// node.name don't exist on SchemaDefinition
|
2536
|
-
'name' in node ? end.column - 1 + node.name.value.length : end.column,
|
2537
|
-
},
|
2538
|
-
},
|
2503
|
+
loc: getLocation(('name' in node ? node.name : node).loc, 'name' in node ? node.name.value : 'schema'),
|
2539
2504
|
messageId: REQUIRE_DESCRIPTION_ERROR,
|
2540
2505
|
data: {
|
2541
2506
|
nodeType: node.kind,
|
@@ -2553,7 +2518,7 @@ const rule$i = {
|
|
2553
2518
|
examples: [
|
2554
2519
|
{
|
2555
2520
|
title: 'Incorrect',
|
2556
|
-
usage: [{ on: [
|
2521
|
+
usage: [{ on: ['ObjectTypeDefinition', 'FieldDefinition'] }],
|
2557
2522
|
code: /* GraphQL */ `
|
2558
2523
|
type someTypeName {
|
2559
2524
|
name: String
|
@@ -2562,7 +2527,7 @@ const rule$i = {
|
|
2562
2527
|
},
|
2563
2528
|
{
|
2564
2529
|
title: 'Correct',
|
2565
|
-
usage: [{ on: [
|
2530
|
+
usage: [{ on: ['ObjectTypeDefinition', 'FieldDefinition'] }],
|
2566
2531
|
code: /* GraphQL */ `
|
2567
2532
|
"""
|
2568
2533
|
Some type description
|
@@ -2656,18 +2621,22 @@ const rule$j = {
|
|
2656
2621
|
if (!mutationType || !queryType) {
|
2657
2622
|
return {};
|
2658
2623
|
}
|
2659
|
-
const selector =
|
2624
|
+
const selector = [
|
2625
|
+
`:matches(${graphql.Kind.OBJECT_TYPE_DEFINITION}, ${graphql.Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}]`,
|
2626
|
+
'>',
|
2627
|
+
graphql.Kind.FIELD_DEFINITION,
|
2628
|
+
graphql.Kind.NAMED_TYPE,
|
2629
|
+
].join(' ');
|
2660
2630
|
return {
|
2661
2631
|
[selector](node) {
|
2662
|
-
const
|
2663
|
-
const typeName = getTypeName(rawNode);
|
2632
|
+
const typeName = node.name.value;
|
2664
2633
|
const graphQLType = schema.getType(typeName);
|
2665
2634
|
if (graphql.isObjectType(graphQLType)) {
|
2666
2635
|
const { fields } = graphQLType.astNode;
|
2667
2636
|
const hasQueryType = fields.some(field => getTypeName(field) === queryType.name);
|
2668
2637
|
if (!hasQueryType) {
|
2669
2638
|
context.report({
|
2670
|
-
node,
|
2639
|
+
loc: getLocation(node.loc, typeName),
|
2671
2640
|
message: `Mutation result type "${graphQLType.name}" must contain field of type "${queryType.name}".`,
|
2672
2641
|
});
|
2673
2642
|
}
|
@@ -3006,7 +2975,7 @@ const rule$l = {
|
|
3006
2975
|
getDocument: () => document,
|
3007
2976
|
reportError: (error) => {
|
3008
2977
|
context.report({
|
3009
|
-
loc: error.locations[0],
|
2978
|
+
loc: getLocation({ start: error.locations[0] }),
|
3010
2979
|
message: error.message,
|
3011
2980
|
});
|
3012
2981
|
},
|
@@ -3145,7 +3114,7 @@ const rule$m = {
|
|
3145
3114
|
acceptedIdNames: ['id'],
|
3146
3115
|
acceptedIdTypes: ['ID'],
|
3147
3116
|
exceptions: {},
|
3148
|
-
...
|
3117
|
+
...context.options[0],
|
3149
3118
|
};
|
3150
3119
|
return {
|
3151
3120
|
ObjectTypeDefinition(node) {
|
@@ -3162,15 +3131,16 @@ const rule$m = {
|
|
3162
3131
|
}
|
3163
3132
|
return isValidIdName && isValidIdType;
|
3164
3133
|
});
|
3134
|
+
const typeName = node.name.value;
|
3165
3135
|
// Usually, there should be only one unique identifier field per type.
|
3166
3136
|
// Some clients allow multiple fields to be used. If more people need this,
|
3167
3137
|
// we can extend this rule later.
|
3168
3138
|
if (validIds.length !== 1) {
|
3169
3139
|
context.report({
|
3170
|
-
node,
|
3171
|
-
message:
|
3140
|
+
loc: getLocation(node.name.loc, typeName),
|
3141
|
+
message: `{{ typeName }} must have exactly one non-nullable unique identifier. Accepted name(s): {{ acceptedNamesString }} ; Accepted type(s): {{ acceptedTypesString }}`,
|
3172
3142
|
data: {
|
3173
|
-
|
3143
|
+
typeName,
|
3174
3144
|
acceptedNamesString: options.acceptedIdNames.join(','),
|
3175
3145
|
acceptedTypesString: options.acceptedIdTypes.join(','),
|
3176
3146
|
},
|
@@ -3184,11 +3154,7 @@ const rule$m = {
|
|
3184
3154
|
const RULE_NAME$3 = 'unique-fragment-name';
|
3185
3155
|
const UNIQUE_FRAGMENT_NAME = 'UNIQUE_FRAGMENT_NAME';
|
3186
3156
|
const checkNode = (context, node, ruleName, messageId) => {
|
3187
|
-
|
3188
|
-
const documentName = (_a = node.name) === null || _a === void 0 ? void 0 : _a.value;
|
3189
|
-
if (!documentName) {
|
3190
|
-
return;
|
3191
|
-
}
|
3157
|
+
const documentName = node.name.value;
|
3192
3158
|
const siblings = requireSiblingsOperations(ruleName, context);
|
3193
3159
|
const siblingDocuments = node.kind === graphql.Kind.FRAGMENT_DEFINITION ? siblings.getFragment(documentName) : siblings.getOperation(documentName);
|
3194
3160
|
const filepath = context.getFilename();
|
@@ -3199,7 +3165,6 @@ const checkNode = (context, node, ruleName, messageId) => {
|
|
3199
3165
|
return isSameName && !isSamePath;
|
3200
3166
|
});
|
3201
3167
|
if (conflictingDocuments.length > 0) {
|
3202
|
-
const { start, end } = node.name.loc;
|
3203
3168
|
context.report({
|
3204
3169
|
messageId,
|
3205
3170
|
data: {
|
@@ -3208,16 +3173,7 @@ const checkNode = (context, node, ruleName, messageId) => {
|
|
3208
3173
|
.map(f => `\t${path.relative(process.cwd(), getOnDiskFilepath(f.filePath))}`)
|
3209
3174
|
.join('\n'),
|
3210
3175
|
},
|
3211
|
-
loc:
|
3212
|
-
start: {
|
3213
|
-
line: start.line,
|
3214
|
-
column: start.column - 1,
|
3215
|
-
},
|
3216
|
-
end: {
|
3217
|
-
line: end.line,
|
3218
|
-
column: end.column - 1,
|
3219
|
-
},
|
3220
|
-
},
|
3176
|
+
loc: getLocation(node.name.loc, documentName),
|
3221
3177
|
});
|
3222
3178
|
}
|
3223
3179
|
};
|
@@ -3334,7 +3290,7 @@ const rule$o = {
|
|
3334
3290
|
},
|
3335
3291
|
create(context) {
|
3336
3292
|
return {
|
3337
|
-
OperationDefinition(node) {
|
3293
|
+
'OperationDefinition[name!=undefined]'(node) {
|
3338
3294
|
checkNode(context, node, RULE_NAME$4, UNIQUE_OPERATION_NAME);
|
3339
3295
|
},
|
3340
3296
|
};
|
@@ -3764,23 +3720,111 @@ function parseForESLint(code, options = {}) {
|
|
3764
3720
|
}
|
3765
3721
|
}
|
3766
3722
|
|
3767
|
-
class GraphQLRuleTester extends
|
3723
|
+
class GraphQLRuleTester extends eslint.RuleTester {
|
3768
3724
|
constructor(parserOptions = {}) {
|
3769
|
-
|
3725
|
+
const config = {
|
3770
3726
|
parser: require.resolve('@graphql-eslint/eslint-plugin'),
|
3771
3727
|
parserOptions: {
|
3772
3728
|
...parserOptions,
|
3773
3729
|
skipGraphQLConfig: true,
|
3774
3730
|
},
|
3775
|
-
}
|
3731
|
+
};
|
3732
|
+
super(config);
|
3733
|
+
this.config = config;
|
3776
3734
|
}
|
3777
3735
|
fromMockFile(path$1) {
|
3778
3736
|
return fs.readFileSync(path.resolve(__dirname, `../tests/mocks/${path$1}`), 'utf-8');
|
3779
3737
|
}
|
3780
3738
|
runGraphQLTests(name, rule, tests) {
|
3781
|
-
|
3739
|
+
const ruleTests = eslint.Linter.version.startsWith('8')
|
3740
|
+
? tests
|
3741
|
+
: {
|
3742
|
+
valid: tests.valid.map(test => {
|
3743
|
+
if (typeof test === 'string') {
|
3744
|
+
return test;
|
3745
|
+
}
|
3746
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
3747
|
+
const { name, ...testCaseOptions } = test;
|
3748
|
+
return testCaseOptions;
|
3749
|
+
}),
|
3750
|
+
invalid: tests.invalid.map(test => {
|
3751
|
+
// ESLint 7 throws an error on CI - Unexpected top-level property "name"
|
3752
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
3753
|
+
const { name, ...testCaseOptions } = test;
|
3754
|
+
return testCaseOptions;
|
3755
|
+
}),
|
3756
|
+
};
|
3757
|
+
super.run(name, rule, ruleTests);
|
3758
|
+
// Skip snapshot testing if `expect` variable is not defined
|
3759
|
+
if (typeof expect === 'undefined') {
|
3760
|
+
return;
|
3761
|
+
}
|
3762
|
+
const linter = new eslint.Linter();
|
3763
|
+
linter.defineRule(name, rule);
|
3764
|
+
for (const testCase of tests.invalid) {
|
3765
|
+
const verifyConfig = getVerifyConfig(name, this.config, testCase);
|
3766
|
+
defineParser(linter, verifyConfig.parser);
|
3767
|
+
const { code, filename } = testCase;
|
3768
|
+
const messages = linter.verify(code, verifyConfig, { filename });
|
3769
|
+
for (const message of messages) {
|
3770
|
+
if (message.fatal) {
|
3771
|
+
throw new Error(message.message);
|
3772
|
+
}
|
3773
|
+
const messageForSnapshot = visualizeEslintMessage(code, message);
|
3774
|
+
// eslint-disable-next-line no-undef
|
3775
|
+
expect(messageForSnapshot).toMatchSnapshot();
|
3776
|
+
}
|
3777
|
+
}
|
3782
3778
|
}
|
3783
3779
|
}
|
3780
|
+
function getVerifyConfig(ruleId, testerConfig, testCase) {
|
3781
|
+
const { options, parserOptions, parser = testerConfig.parser } = testCase;
|
3782
|
+
return {
|
3783
|
+
...testerConfig,
|
3784
|
+
parser,
|
3785
|
+
parserOptions: {
|
3786
|
+
...testerConfig.parserOptions,
|
3787
|
+
...parserOptions,
|
3788
|
+
},
|
3789
|
+
rules: {
|
3790
|
+
[ruleId]: ['error', ...(Array.isArray(options) ? options : [])],
|
3791
|
+
},
|
3792
|
+
};
|
3793
|
+
}
|
3794
|
+
const parsers = new WeakMap();
|
3795
|
+
function defineParser(linter, parser) {
|
3796
|
+
if (!parser) {
|
3797
|
+
return;
|
3798
|
+
}
|
3799
|
+
if (!parsers.has(linter)) {
|
3800
|
+
parsers.set(linter, new Set());
|
3801
|
+
}
|
3802
|
+
const defined = parsers.get(linter);
|
3803
|
+
if (!defined.has(parser)) {
|
3804
|
+
defined.add(parser);
|
3805
|
+
linter.defineParser(parser, require(parser));
|
3806
|
+
}
|
3807
|
+
}
|
3808
|
+
function visualizeEslintMessage(text, result) {
|
3809
|
+
const { line, column, endLine, endColumn, message } = result;
|
3810
|
+
const location = {
|
3811
|
+
start: {
|
3812
|
+
line,
|
3813
|
+
column,
|
3814
|
+
},
|
3815
|
+
};
|
3816
|
+
if (typeof endLine === 'number' && typeof endColumn === 'number') {
|
3817
|
+
location.end = {
|
3818
|
+
line: endLine,
|
3819
|
+
column: endColumn,
|
3820
|
+
};
|
3821
|
+
}
|
3822
|
+
return codeFrame.codeFrameColumns(text, location, {
|
3823
|
+
linesAbove: Number.POSITIVE_INFINITY,
|
3824
|
+
linesBelow: Number.POSITIVE_INFINITY,
|
3825
|
+
message,
|
3826
|
+
});
|
3827
|
+
}
|
3784
3828
|
|
3785
3829
|
exports.GraphQLRuleTester = GraphQLRuleTester;
|
3786
3830
|
exports.configs = configs;
|