@graphql-eslint/eslint-plugin 2.3.0 → 2.3.2-alpha-13a11c2.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/README.md +1 -1
- package/docs/README.md +1 -1
- package/docs/custom-rules.md +3 -3
- package/docs/rules/alphabetize.md +6 -1
- package/docs/rules/avoid-duplicate-fields.md +6 -1
- package/docs/rules/avoid-operation-name-prefix.md +5 -1
- package/docs/rules/avoid-scalar-result-type-on-mutation.md +6 -1
- package/docs/rules/avoid-typename-prefix.md +6 -1
- package/docs/rules/description-style.md +6 -1
- package/docs/rules/executable-definitions.md +6 -1
- package/docs/rules/fields-on-correct-type.md +6 -1
- package/docs/rules/fragments-on-composite-type.md +6 -1
- package/docs/rules/input-name.md +6 -1
- package/docs/rules/known-argument-names.md +6 -1
- package/docs/rules/known-directives.md +6 -1
- package/docs/rules/known-fragment-names.md +6 -1
- package/docs/rules/known-type-names.md +6 -1
- package/docs/rules/lone-anonymous-operation.md +6 -1
- package/docs/rules/lone-schema-definition.md +6 -1
- package/docs/rules/match-document-filename.md +6 -1
- package/docs/rules/naming-convention.md +6 -1
- package/docs/rules/no-anonymous-operations.md +6 -1
- package/docs/rules/no-case-insensitive-enum-values-duplicates.md +5 -1
- package/docs/rules/no-deprecated.md +6 -1
- package/docs/rules/no-fragment-cycles.md +6 -1
- package/docs/rules/no-hashtag-description.md +6 -1
- package/docs/rules/no-operation-name-suffix.md +5 -1
- package/docs/rules/no-undefined-variables.md +6 -1
- package/docs/rules/no-unreachable-types.md +6 -1
- package/docs/rules/no-unused-fields.md +6 -1
- package/docs/rules/no-unused-fragments.md +6 -1
- package/docs/rules/no-unused-variables.md +6 -1
- package/docs/rules/one-field-subscriptions.md +6 -1
- package/docs/rules/overlapping-fields-can-be-merged.md +6 -1
- package/docs/rules/possible-fragment-spread.md +6 -1
- package/docs/rules/possible-type-extension.md +6 -1
- package/docs/rules/provided-required-arguments.md +6 -1
- package/docs/rules/require-deprecation-date.md +6 -1
- package/docs/rules/require-deprecation-reason.md +6 -1
- package/docs/rules/require-description.md +6 -1
- package/docs/rules/require-field-of-type-query-in-mutation-result.md +6 -1
- package/docs/rules/require-id-when-available.md +6 -1
- package/docs/rules/scalar-leafs.md +6 -1
- package/docs/rules/selection-set-depth.md +6 -1
- package/docs/rules/strict-id-in-types.md +6 -1
- package/docs/rules/unique-argument-names.md +6 -1
- package/docs/rules/unique-directive-names-per-location.md +6 -1
- package/docs/rules/unique-directive-names.md +6 -1
- package/docs/rules/unique-enum-value-names.md +6 -1
- package/docs/rules/unique-field-definition-names.md +6 -1
- package/docs/rules/unique-fragment-name.md +6 -1
- package/docs/rules/unique-input-field-names.md +6 -1
- package/docs/rules/unique-operation-name.md +6 -1
- package/docs/rules/unique-operation-types.md +6 -1
- package/docs/rules/unique-type-names.md +6 -1
- package/docs/rules/unique-variable-names.md +6 -1
- package/docs/rules/value-literals-of-correct-type.md +6 -1
- package/docs/rules/variables-are-input-types.md +6 -1
- package/docs/rules/variables-in-allowed-position.md +6 -1
- package/index.js +183 -58
- package/index.mjs +183 -58
- package/package.json +1 -1
- package/testkit.d.ts +5 -3
- package/types.d.ts +2 -0
- package/utils.d.ts +4 -0
package/index.mjs
CHANGED
@@ -9,6 +9,8 @@ import depthLimit from 'graphql-depth-limit';
|
|
9
9
|
import { parseCode } from '@graphql-tools/graphql-tag-pluck';
|
10
10
|
import { loadConfigSync, GraphQLConfig } from 'graphql-config';
|
11
11
|
import { CodeFileLoader } from '@graphql-tools/code-file-loader';
|
12
|
+
import { RuleTester, Linter } from 'eslint';
|
13
|
+
import { codeFrameColumns } from '@babel/code-frame';
|
12
14
|
|
13
15
|
/*
|
14
16
|
* 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
|
@@ -236,6 +238,24 @@ const convertCase = (style, str) => {
|
|
236
238
|
return lowerCase(str).replace(/ /g, '-');
|
237
239
|
}
|
238
240
|
};
|
241
|
+
function getLocation(loc, fieldName = '', offset) {
|
242
|
+
const { start } = loc;
|
243
|
+
/*
|
244
|
+
* ESLint has 0-based column number
|
245
|
+
* https://eslint.org/docs/developer-guide/working-with-rules#contextreport
|
246
|
+
*/
|
247
|
+
const { offsetStart = 1, offsetEnd = 1 } = offset !== null && offset !== void 0 ? offset : {};
|
248
|
+
return {
|
249
|
+
start: {
|
250
|
+
line: start.line,
|
251
|
+
column: start.column - offsetStart,
|
252
|
+
},
|
253
|
+
end: {
|
254
|
+
line: start.line,
|
255
|
+
column: start.column - offsetEnd + fieldName.length,
|
256
|
+
},
|
257
|
+
};
|
258
|
+
}
|
239
259
|
|
240
260
|
function extractRuleName(stack) {
|
241
261
|
const match = (stack || '').match(/validation[/\\\\]rules[/\\\\](.*?)\.js:/) || [];
|
@@ -286,6 +306,7 @@ const validationToRule = (name, ruleName, docs, getDocumentNode) => {
|
|
286
306
|
meta: {
|
287
307
|
docs: {
|
288
308
|
...docs,
|
309
|
+
graphQLJSRuleName: ruleName,
|
289
310
|
category: 'Validation',
|
290
311
|
recommended: true,
|
291
312
|
requiresSchema,
|
@@ -650,10 +671,7 @@ const rule = {
|
|
650
671
|
line: start.line,
|
651
672
|
column: start.column - (isVariableNode ? 2 : 1),
|
652
673
|
},
|
653
|
-
end
|
654
|
-
line: end.line,
|
655
|
-
column: end.column,
|
656
|
-
},
|
674
|
+
end,
|
657
675
|
},
|
658
676
|
messageId: ALPHABETIZE,
|
659
677
|
data: isVariableNode
|
@@ -784,6 +802,7 @@ const rule$1 = {
|
|
784
802
|
messages: {
|
785
803
|
[AVOID_DUPLICATE_FIELDS]: `{{ type }} "{{ fieldName }}" defined multiple times.`,
|
786
804
|
},
|
805
|
+
schema: [],
|
787
806
|
},
|
788
807
|
create(context) {
|
789
808
|
return {
|
@@ -903,15 +922,16 @@ const rule$2 = {
|
|
903
922
|
const testKeyword = caseSensitive ? keyword : keyword.toLowerCase();
|
904
923
|
const testName = caseSensitive ? node.name.value : node.name.value.toLowerCase();
|
905
924
|
if (testName.startsWith(testKeyword)) {
|
925
|
+
const { start } = node.name.loc;
|
906
926
|
context.report({
|
907
927
|
loc: {
|
908
928
|
start: {
|
909
|
-
line:
|
910
|
-
column:
|
929
|
+
line: start.line,
|
930
|
+
column: start.column - 1,
|
911
931
|
},
|
912
932
|
end: {
|
913
|
-
line:
|
914
|
-
column:
|
933
|
+
line: start.line,
|
934
|
+
column: start.column - 1 + testKeyword.length,
|
915
935
|
},
|
916
936
|
},
|
917
937
|
data: {
|
@@ -954,6 +974,7 @@ const rule$3 = {
|
|
954
974
|
},
|
955
975
|
],
|
956
976
|
},
|
977
|
+
schema: [],
|
957
978
|
},
|
958
979
|
create(context) {
|
959
980
|
const schema = requireGraphQLSchemaFromContext('avoid-scalar-result-type-on-mutation', context);
|
@@ -961,16 +982,20 @@ const rule$3 = {
|
|
961
982
|
if (!mutationType) {
|
962
983
|
return {};
|
963
984
|
}
|
964
|
-
const selector =
|
985
|
+
const selector = [
|
986
|
+
`:matches(${Kind.OBJECT_TYPE_DEFINITION}, ${Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}]`,
|
987
|
+
'>',
|
988
|
+
Kind.FIELD_DEFINITION,
|
989
|
+
Kind.NAMED_TYPE,
|
990
|
+
].join(' ');
|
965
991
|
return {
|
966
992
|
[selector](node) {
|
967
|
-
const
|
968
|
-
const typeName = getTypeName(rawNode);
|
993
|
+
const typeName = node.name.value;
|
969
994
|
const graphQLType = schema.getType(typeName);
|
970
995
|
if (isScalarType(graphQLType)) {
|
971
996
|
context.report({
|
972
|
-
node,
|
973
|
-
message: `Unexpected scalar result type "${typeName}"
|
997
|
+
loc: getLocation(node.loc, typeName),
|
998
|
+
message: `Unexpected scalar result type "${typeName}"`,
|
974
999
|
});
|
975
1000
|
}
|
976
1001
|
},
|
@@ -1009,22 +1034,33 @@ const rule$4 = {
|
|
1009
1034
|
messages: {
|
1010
1035
|
[AVOID_TYPENAME_PREFIX]: `Field "{{ fieldName }}" starts with the name of the parent type "{{ typeName }}"`,
|
1011
1036
|
},
|
1037
|
+
schema: [],
|
1012
1038
|
},
|
1013
1039
|
create(context) {
|
1014
1040
|
return {
|
1015
1041
|
'ObjectTypeDefinition, ObjectTypeExtension, InterfaceTypeDefinition, InterfaceTypeExtension'(node) {
|
1016
1042
|
const typeName = node.name.value;
|
1017
|
-
const lowerTypeName =
|
1043
|
+
const lowerTypeName = typeName.toLowerCase();
|
1018
1044
|
for (const field of node.fields) {
|
1019
|
-
const fieldName = field.name.value
|
1020
|
-
if (fieldName
|
1045
|
+
const fieldName = field.name.value;
|
1046
|
+
if (fieldName.toLowerCase().startsWith(lowerTypeName)) {
|
1047
|
+
const { start } = field.loc;
|
1021
1048
|
context.report({
|
1022
|
-
node: field.name,
|
1023
1049
|
data: {
|
1024
1050
|
fieldName,
|
1025
1051
|
typeName,
|
1026
1052
|
},
|
1027
1053
|
messageId: AVOID_TYPENAME_PREFIX,
|
1054
|
+
loc: {
|
1055
|
+
start: {
|
1056
|
+
line: start.line,
|
1057
|
+
column: start.column - 1,
|
1058
|
+
},
|
1059
|
+
end: {
|
1060
|
+
line: start.line,
|
1061
|
+
column: start.column - 1 + lowerTypeName.length,
|
1062
|
+
},
|
1063
|
+
},
|
1028
1064
|
});
|
1029
1065
|
}
|
1030
1066
|
}
|
@@ -1171,10 +1207,11 @@ const rule$6 = {
|
|
1171
1207
|
const shouldCheckType = node => (options.checkMutations && isMutationType(node)) || (options.checkQueries && isQueryType(node));
|
1172
1208
|
const listeners = {
|
1173
1209
|
'FieldDefinition > InputValueDefinition': node => {
|
1174
|
-
|
1210
|
+
const name = node.name.value;
|
1211
|
+
if (name !== 'input' && shouldCheckType(node.parent.parent)) {
|
1175
1212
|
context.report({
|
1176
|
-
|
1177
|
-
message: `Input "${
|
1213
|
+
loc: getLocation(node.loc, name),
|
1214
|
+
message: `Input "${name}" should be called "input"`,
|
1178
1215
|
});
|
1179
1216
|
}
|
1180
1217
|
},
|
@@ -1191,11 +1228,12 @@ const rule$6 = {
|
|
1191
1228
|
const inputValueNode = findInputType(node);
|
1192
1229
|
if (shouldCheckType(inputValueNode.parent.parent)) {
|
1193
1230
|
const mutationName = `${inputValueNode.parent.name.value}Input`;
|
1231
|
+
const name = node.name.value;
|
1194
1232
|
if ((options.caseSensitiveInputType && node.name.value !== mutationName) ||
|
1195
|
-
|
1233
|
+
name.toLowerCase() !== mutationName.toLowerCase()) {
|
1196
1234
|
context.report({
|
1197
|
-
node,
|
1198
|
-
message: `InputType "${
|
1235
|
+
loc: getLocation(node.loc, name),
|
1236
|
+
message: `InputType "${name}" name should be "${mutationName}"`,
|
1199
1237
|
});
|
1200
1238
|
}
|
1201
1239
|
}
|
@@ -1352,7 +1390,8 @@ const rule$7 = {
|
|
1352
1390
|
var _a;
|
1353
1391
|
if (options.fileExtension && options.fileExtension !== fileExtension) {
|
1354
1392
|
context.report({
|
1355
|
-
|
1393
|
+
// Report on first character
|
1394
|
+
loc: { column: 0, line: 1 },
|
1356
1395
|
messageId: MATCH_EXTENSION,
|
1357
1396
|
data: {
|
1358
1397
|
fileExtension,
|
@@ -1384,7 +1423,8 @@ const rule$7 = {
|
|
1384
1423
|
const filenameWithExtension = filename + expectedExtension;
|
1385
1424
|
if (expectedFilename !== filenameWithExtension) {
|
1386
1425
|
context.report({
|
1387
|
-
|
1426
|
+
// Report on first character
|
1427
|
+
loc: { column: 0, line: 1 },
|
1388
1428
|
messageId: MATCH_STYLE,
|
1389
1429
|
data: {
|
1390
1430
|
expectedFilename,
|
@@ -1722,20 +1762,24 @@ const rule$9 = {
|
|
1722
1762
|
messages: {
|
1723
1763
|
[NO_ANONYMOUS_OPERATIONS]: `Anonymous GraphQL operations are forbidden. Please make sure to name your {{ operation }}!`,
|
1724
1764
|
},
|
1765
|
+
schema: [],
|
1725
1766
|
},
|
1726
1767
|
create(context) {
|
1727
1768
|
return {
|
1728
1769
|
OperationDefinition(node) {
|
1729
|
-
|
1770
|
+
var _a;
|
1771
|
+
const isAnonymous = (((_a = node.name) === null || _a === void 0 ? void 0 : _a.value) || '').length === 0;
|
1772
|
+
if (isAnonymous) {
|
1773
|
+
const { start } = node.loc;
|
1730
1774
|
context.report({
|
1731
1775
|
loc: {
|
1732
1776
|
start: {
|
1733
|
-
column:
|
1734
|
-
line:
|
1777
|
+
column: start.column - 1,
|
1778
|
+
line: start.line,
|
1735
1779
|
},
|
1736
1780
|
end: {
|
1737
|
-
column:
|
1738
|
-
line:
|
1781
|
+
column: start.column - 1 + node.operation.length,
|
1782
|
+
line: start.line,
|
1739
1783
|
},
|
1740
1784
|
},
|
1741
1785
|
data: {
|
@@ -1785,6 +1829,7 @@ const rule$a = {
|
|
1785
1829
|
messages: {
|
1786
1830
|
[ERROR_MESSAGE_ID]: `Case-insensitive enum values duplicates are not allowed! Found: "{{ found }}"`,
|
1787
1831
|
},
|
1832
|
+
schema: [],
|
1788
1833
|
},
|
1789
1834
|
create(context) {
|
1790
1835
|
return {
|
@@ -1879,6 +1924,7 @@ const rule$b = {
|
|
1879
1924
|
messages: {
|
1880
1925
|
[NO_DEPRECATED]: `This {{ type }} is marked as deprecated in your GraphQL schema {{ reason }}`,
|
1881
1926
|
},
|
1927
|
+
schema: [],
|
1882
1928
|
},
|
1883
1929
|
create(context) {
|
1884
1930
|
return {
|
@@ -1965,6 +2011,7 @@ const rule$c = {
|
|
1965
2011
|
],
|
1966
2012
|
},
|
1967
2013
|
type: 'suggestion',
|
2014
|
+
schema: [],
|
1968
2015
|
},
|
1969
2016
|
create(context) {
|
1970
2017
|
return {
|
@@ -2025,15 +2072,28 @@ const rule$d = {
|
|
2025
2072
|
messages: {
|
2026
2073
|
[NO_OPERATION_NAME_SUFFIX]: `Unnecessary "{{ invalidSuffix }}" suffix in your operation name!`,
|
2027
2074
|
},
|
2075
|
+
schema: [],
|
2028
2076
|
},
|
2029
2077
|
create(context) {
|
2030
2078
|
return {
|
2031
2079
|
'OperationDefinition, FragmentDefinition'(node) {
|
2032
|
-
|
2033
|
-
|
2034
|
-
|
2080
|
+
var _a;
|
2081
|
+
const name = ((_a = node.name) === null || _a === void 0 ? void 0 : _a.value) || '';
|
2082
|
+
if (name.length > 0) {
|
2083
|
+
const invalidSuffix = 'operation' in node ? node.operation : 'fragment';
|
2084
|
+
if (name.toLowerCase().endsWith(invalidSuffix)) {
|
2085
|
+
const { start, end } = node.name.loc;
|
2035
2086
|
context.report({
|
2036
|
-
|
2087
|
+
loc: {
|
2088
|
+
start: {
|
2089
|
+
column: start.column - 1 + name.length - invalidSuffix.length,
|
2090
|
+
line: start.line,
|
2091
|
+
},
|
2092
|
+
end: {
|
2093
|
+
column: end.column - 1 + name.length,
|
2094
|
+
line: end.line,
|
2095
|
+
},
|
2096
|
+
},
|
2037
2097
|
data: {
|
2038
2098
|
invalidSuffix,
|
2039
2099
|
},
|
@@ -2090,6 +2150,7 @@ const rule$e = {
|
|
2090
2150
|
},
|
2091
2151
|
fixable: 'code',
|
2092
2152
|
type: 'suggestion',
|
2153
|
+
schema: [],
|
2093
2154
|
},
|
2094
2155
|
create(context) {
|
2095
2156
|
const reachableTypes = requireReachableTypesFromContext(RULE_NAME, context);
|
@@ -2181,6 +2242,7 @@ const rule$f = {
|
|
2181
2242
|
},
|
2182
2243
|
fixable: 'code',
|
2183
2244
|
type: 'suggestion',
|
2245
|
+
schema: [],
|
2184
2246
|
},
|
2185
2247
|
create(context) {
|
2186
2248
|
const usedFields = requireUsedFieldsFromContext(RULE_NAME$1, context);
|
@@ -2446,6 +2508,7 @@ const rule$h = {
|
|
2446
2508
|
],
|
2447
2509
|
},
|
2448
2510
|
type: 'suggestion',
|
2511
|
+
schema: [],
|
2449
2512
|
},
|
2450
2513
|
create(context) {
|
2451
2514
|
return {
|
@@ -2482,15 +2545,18 @@ const DESCRIBABLE_NODES = [
|
|
2482
2545
|
function verifyRule(context, node) {
|
2483
2546
|
if (node) {
|
2484
2547
|
if (!node.description || !node.description.value || node.description.value.trim().length === 0) {
|
2548
|
+
const { start, end } = ('name' in node ? node.name : node).loc;
|
2485
2549
|
context.report({
|
2486
2550
|
loc: {
|
2487
2551
|
start: {
|
2488
|
-
line:
|
2489
|
-
column:
|
2552
|
+
line: start.line,
|
2553
|
+
column: start.column - 1,
|
2490
2554
|
},
|
2491
2555
|
end: {
|
2492
|
-
line:
|
2493
|
-
column:
|
2556
|
+
line: end.line,
|
2557
|
+
column:
|
2558
|
+
// node.name don't exist on SchemaDefinition
|
2559
|
+
'name' in node ? end.column - 1 + node.name.value.length : end.column,
|
2494
2560
|
},
|
2495
2561
|
},
|
2496
2562
|
messageId: REQUIRE_DESCRIPTION_ERROR,
|
@@ -2604,6 +2670,7 @@ const rule$j = {
|
|
2604
2670
|
},
|
2605
2671
|
],
|
2606
2672
|
},
|
2673
|
+
schema: [],
|
2607
2674
|
},
|
2608
2675
|
create(context) {
|
2609
2676
|
const schema = requireGraphQLSchemaFromContext(RULE_NAME$2, context);
|
@@ -3140,11 +3207,7 @@ const rule$m = {
|
|
3140
3207
|
const RULE_NAME$3 = 'unique-fragment-name';
|
3141
3208
|
const UNIQUE_FRAGMENT_NAME = 'UNIQUE_FRAGMENT_NAME';
|
3142
3209
|
const checkNode = (context, node, ruleName, messageId) => {
|
3143
|
-
|
3144
|
-
const documentName = (_a = node.name) === null || _a === void 0 ? void 0 : _a.value;
|
3145
|
-
if (!documentName) {
|
3146
|
-
return;
|
3147
|
-
}
|
3210
|
+
const documentName = node.name.value;
|
3148
3211
|
const siblings = requireSiblingsOperations(ruleName, context);
|
3149
3212
|
const siblingDocuments = node.kind === Kind.FRAGMENT_DEFINITION ? siblings.getFragment(documentName) : siblings.getOperation(documentName);
|
3150
3213
|
const filepath = context.getFilename();
|
@@ -3155,7 +3218,6 @@ const checkNode = (context, node, ruleName, messageId) => {
|
|
3155
3218
|
return isSameName && !isSamePath;
|
3156
3219
|
});
|
3157
3220
|
if (conflictingDocuments.length > 0) {
|
3158
|
-
const { start, end } = node.name.loc;
|
3159
3221
|
context.report({
|
3160
3222
|
messageId,
|
3161
3223
|
data: {
|
@@ -3164,16 +3226,7 @@ const checkNode = (context, node, ruleName, messageId) => {
|
|
3164
3226
|
.map(f => `\t${relative(process.cwd(), getOnDiskFilepath(f.filePath))}`)
|
3165
3227
|
.join('\n'),
|
3166
3228
|
},
|
3167
|
-
loc:
|
3168
|
-
start: {
|
3169
|
-
line: start.line,
|
3170
|
-
column: start.column - 1,
|
3171
|
-
},
|
3172
|
-
end: {
|
3173
|
-
line: end.line,
|
3174
|
-
column: end.column - 1,
|
3175
|
-
},
|
3176
|
-
},
|
3229
|
+
loc: getLocation(node.name.loc, documentName),
|
3177
3230
|
});
|
3178
3231
|
}
|
3179
3232
|
};
|
@@ -3223,6 +3276,7 @@ const rule$n = {
|
|
3223
3276
|
messages: {
|
3224
3277
|
[UNIQUE_FRAGMENT_NAME]: 'Fragment named "{{ documentName }}" already defined in:\n{{ summary }}',
|
3225
3278
|
},
|
3279
|
+
schema: [],
|
3226
3280
|
},
|
3227
3281
|
create(context) {
|
3228
3282
|
return {
|
@@ -3285,10 +3339,11 @@ const rule$o = {
|
|
3285
3339
|
messages: {
|
3286
3340
|
[UNIQUE_OPERATION_NAME]: 'Operation named "{{ documentName }}" already defined in:\n{{ summary }}',
|
3287
3341
|
},
|
3342
|
+
schema: [],
|
3288
3343
|
},
|
3289
3344
|
create(context) {
|
3290
3345
|
return {
|
3291
|
-
OperationDefinition(node) {
|
3346
|
+
'OperationDefinition[name!=undefined]'(node) {
|
3292
3347
|
checkNode(context, node, RULE_NAME$4, UNIQUE_OPERATION_NAME);
|
3293
3348
|
},
|
3294
3349
|
};
|
@@ -3718,22 +3773,92 @@ function parseForESLint(code, options = {}) {
|
|
3718
3773
|
}
|
3719
3774
|
}
|
3720
3775
|
|
3721
|
-
class GraphQLRuleTester extends
|
3776
|
+
class GraphQLRuleTester extends RuleTester {
|
3722
3777
|
constructor(parserOptions = {}) {
|
3723
|
-
|
3778
|
+
const config = {
|
3724
3779
|
parser: require.resolve('@graphql-eslint/eslint-plugin'),
|
3725
3780
|
parserOptions: {
|
3726
3781
|
...parserOptions,
|
3727
3782
|
skipGraphQLConfig: true,
|
3728
3783
|
},
|
3729
|
-
}
|
3784
|
+
};
|
3785
|
+
super(config);
|
3786
|
+
this.config = config;
|
3730
3787
|
}
|
3731
3788
|
fromMockFile(path) {
|
3732
3789
|
return readFileSync(resolve(__dirname, `../tests/mocks/${path}`), 'utf-8');
|
3733
3790
|
}
|
3734
3791
|
runGraphQLTests(name, rule, tests) {
|
3735
3792
|
super.run(name, rule, tests);
|
3793
|
+
// Skip snapshot testing if `expect` variable is not defined
|
3794
|
+
if (typeof expect === 'undefined') {
|
3795
|
+
return;
|
3796
|
+
}
|
3797
|
+
const linter = new Linter();
|
3798
|
+
linter.defineRule(name, rule);
|
3799
|
+
for (const testCase of tests.invalid) {
|
3800
|
+
const verifyConfig = getVerifyConfig(name, this.config, testCase);
|
3801
|
+
defineParser(linter, verifyConfig.parser);
|
3802
|
+
const { code, filename } = testCase;
|
3803
|
+
const messages = linter.verify(code, verifyConfig, { filename });
|
3804
|
+
for (const message of messages) {
|
3805
|
+
if (message.fatal) {
|
3806
|
+
throw new Error(message.message);
|
3807
|
+
}
|
3808
|
+
const messageForSnapshot = visualizeEslintMessage(code, message);
|
3809
|
+
// eslint-disable-next-line no-undef
|
3810
|
+
expect(messageForSnapshot).toMatchSnapshot();
|
3811
|
+
}
|
3812
|
+
}
|
3736
3813
|
}
|
3737
3814
|
}
|
3815
|
+
function getVerifyConfig(ruleId, testerConfig, testCase) {
|
3816
|
+
const { options, parserOptions, parser = testerConfig.parser } = testCase;
|
3817
|
+
return {
|
3818
|
+
...testerConfig,
|
3819
|
+
parser,
|
3820
|
+
parserOptions: {
|
3821
|
+
...testerConfig.parserOptions,
|
3822
|
+
...parserOptions,
|
3823
|
+
},
|
3824
|
+
rules: {
|
3825
|
+
[ruleId]: ['error', ...(Array.isArray(options) ? options : [])],
|
3826
|
+
},
|
3827
|
+
};
|
3828
|
+
}
|
3829
|
+
const parsers = new WeakMap();
|
3830
|
+
function defineParser(linter, parser) {
|
3831
|
+
if (!parser) {
|
3832
|
+
return;
|
3833
|
+
}
|
3834
|
+
if (!parsers.has(linter)) {
|
3835
|
+
parsers.set(linter, new Set());
|
3836
|
+
}
|
3837
|
+
const defined = parsers.get(linter);
|
3838
|
+
if (!defined.has(parser)) {
|
3839
|
+
defined.add(parser);
|
3840
|
+
linter.defineParser(parser, require(parser));
|
3841
|
+
}
|
3842
|
+
}
|
3843
|
+
function visualizeEslintMessage(text, result) {
|
3844
|
+
const { line, column, endLine, endColumn, message } = result;
|
3845
|
+
const location = {
|
3846
|
+
start: {
|
3847
|
+
line,
|
3848
|
+
column,
|
3849
|
+
},
|
3850
|
+
};
|
3851
|
+
if (typeof endLine === 'number' && typeof endColumn === 'number') {
|
3852
|
+
location.end = {
|
3853
|
+
line: endLine,
|
3854
|
+
column: endColumn,
|
3855
|
+
};
|
3856
|
+
}
|
3857
|
+
return codeFrameColumns(text, location, {
|
3858
|
+
linesAbove: Number.POSITIVE_INFINITY,
|
3859
|
+
linesBelow: Number.POSITIVE_INFINITY,
|
3860
|
+
message,
|
3861
|
+
});
|
3862
|
+
}
|
3738
3863
|
|
3739
3864
|
export { GraphQLRuleTester, configs, convertDescription, convertLocation, convertRange, convertToESTree, extractCommentsFromAst, getBaseType, isNodeWithDescription, parse, parseForESLint, processors, rules, valueFromNode };
|
package/package.json
CHANGED
package/testkit.d.ts
CHANGED
@@ -13,8 +13,11 @@ export declare type GraphQLInvalidTestCase<T> = GraphQLValidTestCase<T> & {
|
|
13
13
|
errors: number | Array<RuleTester.TestCaseError | string>;
|
14
14
|
output?: string | null;
|
15
15
|
};
|
16
|
-
declare
|
17
|
-
|
16
|
+
export declare class GraphQLRuleTester extends RuleTester {
|
17
|
+
config: {
|
18
|
+
parser: string;
|
19
|
+
parserOptions: ParserOptions;
|
20
|
+
};
|
18
21
|
constructor(parserOptions?: ParserOptions);
|
19
22
|
fromMockFile(path: string): string;
|
20
23
|
runGraphQLTests<Config>(name: string, rule: GraphQLESLintRule, tests: {
|
@@ -22,4 +25,3 @@ export declare class GraphQLRuleTester extends GraphQLRuleTester_base {
|
|
22
25
|
invalid: GraphQLInvalidTestCase<Config>[];
|
23
26
|
}): void;
|
24
27
|
}
|
25
|
-
export {};
|
package/types.d.ts
CHANGED
@@ -56,9 +56,11 @@ export declare type RuleDocsInfo<T> = Rule.RuleMetaData & {
|
|
56
56
|
usage?: T;
|
57
57
|
}[];
|
58
58
|
optionsForConfig?: T;
|
59
|
+
graphQLJSRuleName?: string;
|
59
60
|
};
|
60
61
|
};
|
61
62
|
export declare type GraphQLESLintRule<Options = any[], WithTypeInfo extends boolean = false> = {
|
62
63
|
create(context: GraphQLESLintRuleContext<Options>): GraphQLESLintRuleListener<WithTypeInfo>;
|
63
64
|
meta: Rule.RuleMetaData & RuleDocsInfo<Options>;
|
64
65
|
};
|
66
|
+
export declare type ValueOf<T> = T[keyof T];
|
package/utils.d.ts
CHANGED
@@ -33,4 +33,8 @@ export declare enum CaseStyle {
|
|
33
33
|
}
|
34
34
|
export declare const camelCase: (str: string) => string;
|
35
35
|
export declare const convertCase: (style: CaseStyle, str: string) => string;
|
36
|
+
export declare function getLocation(loc: Partial<AST.SourceLocation>, fieldName?: string, offset?: {
|
37
|
+
offsetStart?: number;
|
38
|
+
offsetEnd?: number;
|
39
|
+
}): AST.SourceLocation;
|
36
40
|
export {};
|