@graphql-eslint/eslint-plugin 3.8.0-alpha-fb12b01.0 → 3.8.0-alpha-8ddf2a4.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/docs/parser.md +1 -1
- package/docs/rules/alphabetize.md +2 -0
- package/estree-parser/estree-ast.d.ts +1 -2
- package/estree-parser/utils.d.ts +8 -5
- package/index.js +200 -158
- package/index.mjs +199 -157
- package/package.json +1 -1
- package/rules/alphabetize.d.ts +1 -1
- package/rules/index.d.ts +1 -7
- package/testkit.d.ts +0 -1
- package/utils.d.ts +4 -8
package/index.js
CHANGED
@@ -208,43 +208,6 @@ function requireUsedFieldsFromContext(ruleName, context) {
|
|
208
208
|
const siblings = requireSiblingsOperations(ruleName, context);
|
209
209
|
return context.parserServices.usedFields(schema, siblings);
|
210
210
|
}
|
211
|
-
function getLexer(source) {
|
212
|
-
// GraphQL v14
|
213
|
-
const gqlLanguage = require('graphql/language');
|
214
|
-
if (gqlLanguage && gqlLanguage.createLexer) {
|
215
|
-
return gqlLanguage.createLexer(source, {});
|
216
|
-
}
|
217
|
-
// GraphQL v15
|
218
|
-
const { Lexer: LexerCls } = require('graphql');
|
219
|
-
if (LexerCls && typeof LexerCls === 'function') {
|
220
|
-
return new LexerCls(source);
|
221
|
-
}
|
222
|
-
throw new Error(`Unsupported GraphQL version! Please make sure to use GraphQL v14 or newer!`);
|
223
|
-
}
|
224
|
-
function extractTokens(source) {
|
225
|
-
const lexer = getLexer(source);
|
226
|
-
const tokens = [];
|
227
|
-
let token = lexer.advance();
|
228
|
-
while (token && token.kind !== '<EOF>') {
|
229
|
-
tokens.push({
|
230
|
-
type: token.kind,
|
231
|
-
loc: {
|
232
|
-
start: {
|
233
|
-
line: token.line,
|
234
|
-
column: token.column,
|
235
|
-
},
|
236
|
-
end: {
|
237
|
-
line: token.line,
|
238
|
-
column: token.column,
|
239
|
-
},
|
240
|
-
},
|
241
|
-
value: token.value,
|
242
|
-
range: [token.start, token.end],
|
243
|
-
});
|
244
|
-
token = lexer.advance();
|
245
|
-
}
|
246
|
-
return tokens;
|
247
|
-
}
|
248
211
|
const normalizePath = (path) => (path || '').replace(/\\/g, '/');
|
249
212
|
/**
|
250
213
|
* https://github.com/prettier/eslint-plugin-prettier/blob/76bd45ece6d56eb52f75db6b4a1efdd2efb56392/eslint-plugin-prettier.js#L71
|
@@ -314,21 +277,16 @@ const convertCase = (style, str) => {
|
|
314
277
|
return lowerCase(str).replace(/ /g, '-');
|
315
278
|
}
|
316
279
|
};
|
317
|
-
function getLocation(loc, fieldName = ''
|
318
|
-
const {
|
319
|
-
/*
|
320
|
-
* ESLint has 0-based column number
|
321
|
-
* https://eslint.org/docs/developer-guide/working-with-rules#contextreport
|
322
|
-
*/
|
323
|
-
const { offsetStart = 1, offsetEnd = 1 } = offset !== null && offset !== void 0 ? offset : {};
|
280
|
+
function getLocation(loc, fieldName = '') {
|
281
|
+
const { line, column } = loc.start;
|
324
282
|
return {
|
325
283
|
start: {
|
326
|
-
line
|
327
|
-
column
|
284
|
+
line,
|
285
|
+
column,
|
328
286
|
},
|
329
287
|
end: {
|
330
|
-
line
|
331
|
-
column:
|
288
|
+
line,
|
289
|
+
column: column + fieldName.length,
|
332
290
|
},
|
333
291
|
};
|
334
292
|
}
|
@@ -342,23 +300,16 @@ function validateDocument(context, schema = null, documentNode, rule) {
|
|
342
300
|
? graphql.validate(schema, documentNode, [rule])
|
343
301
|
: validate.validateSDL(documentNode, null, [rule]);
|
344
302
|
for (const error of validationErrors) {
|
345
|
-
/*
|
346
|
-
* TODO: Fix ESTree-AST converter because currently it's incorrectly convert loc.end
|
347
|
-
* Example: loc.end always equal loc.start
|
348
|
-
* {
|
349
|
-
* token: {
|
350
|
-
* type: 'Name',
|
351
|
-
* loc: { start: { line: 4, column: 13 }, end: { line: 4, column: 13 } },
|
352
|
-
* value: 'veryBad',
|
353
|
-
* range: [ 40, 47 ]
|
354
|
-
* }
|
355
|
-
* }
|
356
|
-
*/
|
357
303
|
const { line, column } = error.locations[0];
|
358
304
|
const ancestors = context.getAncestors();
|
359
|
-
const token = ancestors[0].tokens.find(token => token.loc.start.line === line && token.loc.start.column === column);
|
305
|
+
const token = ancestors[0].tokens.find(token => token.loc.start.line === line && token.loc.start.column === column - 1);
|
360
306
|
context.report({
|
361
|
-
loc:
|
307
|
+
loc: token
|
308
|
+
? token.loc
|
309
|
+
: {
|
310
|
+
line,
|
311
|
+
column: column - 1,
|
312
|
+
},
|
362
313
|
message: error.message,
|
363
314
|
});
|
364
315
|
}
|
@@ -688,9 +639,10 @@ const argumentsEnum = [
|
|
688
639
|
const rule = {
|
689
640
|
meta: {
|
690
641
|
type: 'suggestion',
|
642
|
+
fixable: 'code',
|
691
643
|
docs: {
|
692
644
|
category: ['Schema', 'Operations'],
|
693
|
-
description:
|
645
|
+
description: `Enforce arrange in alphabetical order for type fields, enum values, input object fields, operation selections and more.`,
|
694
646
|
url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/alphabetize.md',
|
695
647
|
examples: [
|
696
648
|
{
|
@@ -848,24 +800,56 @@ const rule = {
|
|
848
800
|
},
|
849
801
|
create(context) {
|
850
802
|
var _a, _b, _c, _d, _e;
|
803
|
+
const sourceCode = context.getSourceCode();
|
804
|
+
function isNodeAndCommentOnSameLine(node, comment) {
|
805
|
+
return node.loc.end.line === comment.loc.start.line;
|
806
|
+
}
|
807
|
+
function getBeforeComments(node) {
|
808
|
+
const commentsBefore = sourceCode.getCommentsBefore(node);
|
809
|
+
if (commentsBefore.length === 0) {
|
810
|
+
return [];
|
811
|
+
}
|
812
|
+
const tokenBefore = sourceCode.getTokenBefore(node);
|
813
|
+
if (tokenBefore) {
|
814
|
+
return commentsBefore.filter(comment => !isNodeAndCommentOnSameLine(tokenBefore, comment));
|
815
|
+
}
|
816
|
+
return commentsBefore;
|
817
|
+
}
|
818
|
+
function getRangeWithComments(node) {
|
819
|
+
const [firstBeforeComment] = getBeforeComments(node);
|
820
|
+
const [firstAfterComment] = sourceCode.getCommentsAfter(node);
|
821
|
+
const from = firstBeforeComment || node;
|
822
|
+
const to = firstAfterComment && isNodeAndCommentOnSameLine(node, firstAfterComment) ? firstAfterComment : node;
|
823
|
+
return [from.range[0], to.range[1]];
|
824
|
+
}
|
851
825
|
function checkNodes(nodes) {
|
852
|
-
|
853
|
-
for (
|
854
|
-
const
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
? {
|
862
|
-
currName: `$${currName}`,
|
863
|
-
prevName: `$${prevName}`,
|
864
|
-
}
|
865
|
-
: { currName, prevName },
|
866
|
-
});
|
826
|
+
// Starts from 1, ignore nodes.length <= 1
|
827
|
+
for (let i = 1; i < nodes.length; i += 1) {
|
828
|
+
const prevNode = nodes[i - 1];
|
829
|
+
const currNode = nodes[i];
|
830
|
+
const prevName = prevNode.name.value;
|
831
|
+
const currName = currNode.name.value;
|
832
|
+
// Compare with lexicographic order
|
833
|
+
if (prevName.localeCompare(currName) !== 1) {
|
834
|
+
continue;
|
867
835
|
}
|
868
|
-
|
836
|
+
const isVariableNode = currNode.kind === graphql.Kind.VARIABLE;
|
837
|
+
context.report({
|
838
|
+
node: currNode.name,
|
839
|
+
messageId: ALPHABETIZE,
|
840
|
+
data: isVariableNode
|
841
|
+
? {
|
842
|
+
currName: `$${currName}`,
|
843
|
+
prevName: `$${prevName}`,
|
844
|
+
}
|
845
|
+
: { currName, prevName },
|
846
|
+
*fix(fixer) {
|
847
|
+
const prevRange = getRangeWithComments(prevNode);
|
848
|
+
const currRange = getRangeWithComments(currNode);
|
849
|
+
yield fixer.replaceTextRange(prevRange, sourceCode.getText({ range: currRange }));
|
850
|
+
yield fixer.replaceTextRange(currRange, sourceCode.getText({ range: prevRange }));
|
851
|
+
},
|
852
|
+
});
|
869
853
|
}
|
870
854
|
}
|
871
855
|
const opts = context.options[0];
|
@@ -1062,7 +1046,7 @@ const rule$2 = {
|
|
1062
1046
|
if (shouldCheckType(node.parent.parent)) {
|
1063
1047
|
const name = node.name.value;
|
1064
1048
|
context.report({
|
1065
|
-
|
1049
|
+
node: node.name,
|
1066
1050
|
message: `Input "${name}" should be called "input"`,
|
1067
1051
|
});
|
1068
1052
|
}
|
@@ -1084,7 +1068,7 @@ const rule$2 = {
|
|
1084
1068
|
if ((options.caseSensitiveInputType && node.name.value !== mutationName) ||
|
1085
1069
|
name.toLowerCase() !== mutationName.toLowerCase()) {
|
1086
1070
|
context.report({
|
1087
|
-
|
1071
|
+
node: node.name,
|
1088
1072
|
message: `InputType "${name}" name should be "${mutationName}"`,
|
1089
1073
|
});
|
1090
1074
|
}
|
@@ -1536,7 +1520,7 @@ const rule$4 = {
|
|
1536
1520
|
const [trailingUnderscores] = nodeName.match(/_*$/);
|
1537
1521
|
const suggestedName = leadingUnderscores + renameToName + trailingUnderscores;
|
1538
1522
|
context.report({
|
1539
|
-
|
1523
|
+
node,
|
1540
1524
|
message: `${nodeType} "${nodeName}" should ${errorMessage}`,
|
1541
1525
|
suggest: [
|
1542
1526
|
{
|
@@ -1594,7 +1578,7 @@ const rule$4 = {
|
|
1594
1578
|
const name = node.value;
|
1595
1579
|
const renameToName = name.replace(new RegExp(isLeading ? '^_+' : '_+$'), '');
|
1596
1580
|
context.report({
|
1597
|
-
|
1581
|
+
node,
|
1598
1582
|
message: `${isLeading ? 'Leading' : 'Trailing'} underscores are not allowed`,
|
1599
1583
|
suggest: [
|
1600
1584
|
{
|
@@ -1710,7 +1694,7 @@ const rule$6 = {
|
|
1710
1694
|
for (const duplicate of duplicates) {
|
1711
1695
|
const enumName = duplicate.name.value;
|
1712
1696
|
context.report({
|
1713
|
-
|
1697
|
+
node: duplicate.name,
|
1714
1698
|
message: `Case-insensitive enum values duplicates are not allowed! Found: "${enumName}"`,
|
1715
1699
|
});
|
1716
1700
|
}
|
@@ -1804,9 +1788,8 @@ const rule$7 = {
|
|
1804
1788
|
const typeInfo = node.typeInfo();
|
1805
1789
|
if (typeInfo && typeInfo.enumValue) {
|
1806
1790
|
if (typeInfo.enumValue.deprecationReason) {
|
1807
|
-
const enumValueName = node.value;
|
1808
1791
|
context.report({
|
1809
|
-
|
1792
|
+
node,
|
1810
1793
|
messageId: NO_DEPRECATED,
|
1811
1794
|
data: {
|
1812
1795
|
type: 'enum value',
|
@@ -1821,9 +1804,8 @@ const rule$7 = {
|
|
1821
1804
|
const typeInfo = node.typeInfo();
|
1822
1805
|
if (typeInfo && typeInfo.fieldDef) {
|
1823
1806
|
if (typeInfo.fieldDef.deprecationReason) {
|
1824
|
-
const fieldName = node.name.value;
|
1825
1807
|
context.report({
|
1826
|
-
|
1808
|
+
node: node.name,
|
1827
1809
|
messageId: NO_DEPRECATED,
|
1828
1810
|
data: {
|
1829
1811
|
type: 'field',
|
@@ -1895,12 +1877,11 @@ const rule$8 = {
|
|
1895
1877
|
schema: [],
|
1896
1878
|
},
|
1897
1879
|
create(context) {
|
1898
|
-
function checkNode(usedFields,
|
1880
|
+
function checkNode(usedFields, type, node) {
|
1881
|
+
const fieldName = node.value;
|
1899
1882
|
if (usedFields.has(fieldName)) {
|
1900
1883
|
context.report({
|
1901
|
-
|
1902
|
-
offsetEnd: node.kind === graphql.Kind.VARIABLE_DEFINITION ? 0 : 1,
|
1903
|
-
}),
|
1884
|
+
node,
|
1904
1885
|
messageId: NO_DUPLICATE_FIELDS,
|
1905
1886
|
data: {
|
1906
1887
|
type,
|
@@ -1916,21 +1897,20 @@ const rule$8 = {
|
|
1916
1897
|
OperationDefinition(node) {
|
1917
1898
|
const set = new Set();
|
1918
1899
|
for (const varDef of node.variableDefinitions) {
|
1919
|
-
checkNode(set,
|
1900
|
+
checkNode(set, 'Operation variable', varDef.variable.name);
|
1920
1901
|
}
|
1921
1902
|
},
|
1922
1903
|
Field(node) {
|
1923
1904
|
const set = new Set();
|
1924
1905
|
for (const arg of node.arguments) {
|
1925
|
-
checkNode(set,
|
1906
|
+
checkNode(set, 'Field argument', arg.name);
|
1926
1907
|
}
|
1927
1908
|
},
|
1928
1909
|
SelectionSet(node) {
|
1929
|
-
var _a;
|
1930
1910
|
const set = new Set();
|
1931
1911
|
for (const selection of node.selections) {
|
1932
1912
|
if (selection.kind === graphql.Kind.FIELD) {
|
1933
|
-
checkNode(set,
|
1913
|
+
checkNode(set, 'Field', selection.alias || selection.name);
|
1934
1914
|
}
|
1935
1915
|
}
|
1936
1916
|
},
|
@@ -1989,7 +1969,7 @@ const rule$9 = {
|
|
1989
1969
|
schema: [],
|
1990
1970
|
},
|
1991
1971
|
create(context) {
|
1992
|
-
const selector =
|
1972
|
+
const selector = 'Document[definitions.0.kind!=/^(OperationDefinition|FragmentDefinition)$/]';
|
1993
1973
|
return {
|
1994
1974
|
[selector](node) {
|
1995
1975
|
const rawNode = node.rawNode();
|
@@ -2002,7 +1982,10 @@ const rule$9 = {
|
|
2002
1982
|
if (!isEslintComment && line !== prev.line && next.kind === graphql.TokenKind.NAME && linesAfter < 2) {
|
2003
1983
|
context.report({
|
2004
1984
|
messageId: HASHTAG_COMMENT,
|
2005
|
-
loc:
|
1985
|
+
loc: {
|
1986
|
+
line,
|
1987
|
+
column: column - 1,
|
1988
|
+
},
|
2006
1989
|
});
|
2007
1990
|
}
|
2008
1991
|
}
|
@@ -2085,7 +2068,7 @@ const rule$a = {
|
|
2085
2068
|
[selector](node) {
|
2086
2069
|
const typeName = node.value;
|
2087
2070
|
context.report({
|
2088
|
-
|
2071
|
+
node,
|
2089
2072
|
message: `Root type "${typeName}" is forbidden`,
|
2090
2073
|
});
|
2091
2074
|
},
|
@@ -2138,7 +2121,7 @@ const rule$b = {
|
|
2138
2121
|
const graphQLType = schema.getType(typeName);
|
2139
2122
|
if (graphql.isScalarType(graphQLType)) {
|
2140
2123
|
context.report({
|
2141
|
-
|
2124
|
+
node,
|
2142
2125
|
message: `Unexpected scalar result type "${typeName}"`,
|
2143
2126
|
});
|
2144
2127
|
}
|
@@ -2194,7 +2177,7 @@ const rule$c = {
|
|
2194
2177
|
typeName,
|
2195
2178
|
},
|
2196
2179
|
messageId: NO_TYPENAME_PREFIX,
|
2197
|
-
|
2180
|
+
node: field.name,
|
2198
2181
|
});
|
2199
2182
|
}
|
2200
2183
|
}
|
@@ -2272,7 +2255,7 @@ const rule$d = {
|
|
2272
2255
|
const typeName = node.name.value;
|
2273
2256
|
if (!reachableTypes.has(typeName)) {
|
2274
2257
|
context.report({
|
2275
|
-
|
2258
|
+
node: node.name,
|
2276
2259
|
messageId: UNREACHABLE_TYPE,
|
2277
2260
|
data: { typeName },
|
2278
2261
|
suggest: [
|
@@ -2361,7 +2344,7 @@ const rule$e = {
|
|
2361
2344
|
return;
|
2362
2345
|
}
|
2363
2346
|
context.report({
|
2364
|
-
|
2347
|
+
node: node.name,
|
2365
2348
|
messageId: UNUSED_FIELD,
|
2366
2349
|
data: { fieldName },
|
2367
2350
|
suggest: [
|
@@ -2414,8 +2397,51 @@ function getBaseType(type) {
|
|
2414
2397
|
}
|
2415
2398
|
return type;
|
2416
2399
|
}
|
2417
|
-
function
|
2418
|
-
|
2400
|
+
function convertToken(token, type) {
|
2401
|
+
const { line, column, end, start, value } = token;
|
2402
|
+
return {
|
2403
|
+
type,
|
2404
|
+
value,
|
2405
|
+
/*
|
2406
|
+
* ESLint has 0-based column number
|
2407
|
+
* https://eslint.org/docs/developer-guide/working-with-rules#contextreport
|
2408
|
+
*/
|
2409
|
+
loc: {
|
2410
|
+
start: {
|
2411
|
+
line,
|
2412
|
+
column: column - 1,
|
2413
|
+
},
|
2414
|
+
end: {
|
2415
|
+
line,
|
2416
|
+
column: column - 1 + (end - start),
|
2417
|
+
},
|
2418
|
+
},
|
2419
|
+
range: [start, end],
|
2420
|
+
};
|
2421
|
+
}
|
2422
|
+
function getLexer(source) {
|
2423
|
+
// GraphQL v14
|
2424
|
+
const gqlLanguage = require('graphql/language');
|
2425
|
+
if (gqlLanguage && gqlLanguage.createLexer) {
|
2426
|
+
return gqlLanguage.createLexer(source, {});
|
2427
|
+
}
|
2428
|
+
// GraphQL v15
|
2429
|
+
const { Lexer: LexerCls } = require('graphql');
|
2430
|
+
if (LexerCls && typeof LexerCls === 'function') {
|
2431
|
+
return new LexerCls(source);
|
2432
|
+
}
|
2433
|
+
throw new Error('Unsupported GraphQL version! Please make sure to use GraphQL v14 or newer!');
|
2434
|
+
}
|
2435
|
+
function extractTokens(source) {
|
2436
|
+
const lexer = getLexer(source);
|
2437
|
+
const tokens = [];
|
2438
|
+
let token = lexer.advance();
|
2439
|
+
while (token && token.kind !== graphql.TokenKind.EOF) {
|
2440
|
+
const result = convertToken(token, token.kind);
|
2441
|
+
tokens.push(result);
|
2442
|
+
token = lexer.advance();
|
2443
|
+
}
|
2444
|
+
return tokens;
|
2419
2445
|
}
|
2420
2446
|
function extractCommentsFromAst(loc) {
|
2421
2447
|
if (!loc) {
|
@@ -2424,35 +2450,16 @@ function extractCommentsFromAst(loc) {
|
|
2424
2450
|
const comments = [];
|
2425
2451
|
let token = loc.startToken;
|
2426
2452
|
while (token !== null) {
|
2427
|
-
|
2428
|
-
|
2429
|
-
|
2430
|
-
|
2431
|
-
|
2432
|
-
loc: {
|
2433
|
-
start: { line, column },
|
2434
|
-
end: { line, column },
|
2435
|
-
},
|
2436
|
-
range: [start, end],
|
2437
|
-
});
|
2453
|
+
if (token.kind === graphql.TokenKind.COMMENT) {
|
2454
|
+
const comment = convertToken(token,
|
2455
|
+
// `eslint-disable` directive works only with `Block` type comment
|
2456
|
+
token.value.trimStart().startsWith('eslint') ? 'Block' : 'Line');
|
2457
|
+
comments.push(comment);
|
2438
2458
|
}
|
2439
|
-
token = next;
|
2459
|
+
token = token.next;
|
2440
2460
|
}
|
2441
2461
|
return comments;
|
2442
2462
|
}
|
2443
|
-
function convertLocation(gqlLocation) {
|
2444
|
-
return {
|
2445
|
-
start: {
|
2446
|
-
column: gqlLocation.startToken.column,
|
2447
|
-
line: gqlLocation.startToken.line,
|
2448
|
-
},
|
2449
|
-
end: {
|
2450
|
-
column: gqlLocation.endToken.column,
|
2451
|
-
line: gqlLocation.endToken.line,
|
2452
|
-
},
|
2453
|
-
source: gqlLocation.source.body,
|
2454
|
-
};
|
2455
|
-
}
|
2456
2463
|
function isNodeWithDescription(obj) {
|
2457
2464
|
var _a;
|
2458
2465
|
return (_a = obj) === null || _a === void 0 ? void 0 : _a.description;
|
@@ -2538,7 +2545,7 @@ const rule$f = {
|
|
2538
2545
|
const deletionDateNode = node.arguments.find(arg => arg.name.value === argName);
|
2539
2546
|
if (!deletionDateNode) {
|
2540
2547
|
context.report({
|
2541
|
-
|
2548
|
+
node: node.name,
|
2542
2549
|
messageId: MESSAGE_REQUIRE_DATE,
|
2543
2550
|
});
|
2544
2551
|
return;
|
@@ -2566,7 +2573,7 @@ const rule$f = {
|
|
2566
2573
|
const canRemove = Date.now() > deletionDateInMS;
|
2567
2574
|
if (canRemove) {
|
2568
2575
|
context.report({
|
2569
|
-
node,
|
2576
|
+
node: node.parent.name,
|
2570
2577
|
messageId: MESSAGE_CAN_BE_REMOVED,
|
2571
2578
|
data: {
|
2572
2579
|
nodeName: node.parent.name.value,
|
@@ -2623,7 +2630,7 @@ const rule$g = {
|
|
2623
2630
|
const value = reasonArg ? String(valueFromNode(reasonArg.value) || '').trim() : null;
|
2624
2631
|
if (!value) {
|
2625
2632
|
context.report({
|
2626
|
-
|
2633
|
+
node: node.name,
|
2627
2634
|
message: 'Directive "@deprecated" must have a reason!',
|
2628
2635
|
});
|
2629
2636
|
}
|
@@ -2780,7 +2787,7 @@ const rule$h = {
|
|
2780
2787
|
}
|
2781
2788
|
if (description.length === 0) {
|
2782
2789
|
context.report({
|
2783
|
-
loc: isOperation ? getLocation(node.loc, node.operation) :
|
2790
|
+
loc: isOperation ? getLocation(node.loc, node.operation) : node.name.loc,
|
2784
2791
|
messageId: RULE_ID$2,
|
2785
2792
|
data: {
|
2786
2793
|
nodeName: getNodeName(node),
|
@@ -2853,7 +2860,7 @@ const rule$i = {
|
|
2853
2860
|
const hasQueryType = fields.some(field => getTypeName(field) === queryType.name);
|
2854
2861
|
if (!hasQueryType) {
|
2855
2862
|
context.report({
|
2856
|
-
|
2863
|
+
node,
|
2857
2864
|
message: `Mutation result type "${graphQLType.name}" must contain field of type "${queryType.name}"`,
|
2858
2865
|
});
|
2859
2866
|
}
|
@@ -2873,16 +2880,30 @@ function convertToESTree(node, typeInfo) {
|
|
2873
2880
|
function hasTypeField(obj) {
|
2874
2881
|
return obj && !!obj.type;
|
2875
2882
|
}
|
2876
|
-
|
2877
|
-
|
2878
|
-
|
2879
|
-
|
2880
|
-
|
2881
|
-
|
2882
|
-
|
2883
|
-
|
2884
|
-
|
2883
|
+
function convertLocation(location) {
|
2884
|
+
const { startToken, endToken, source, start, end } = location;
|
2885
|
+
/*
|
2886
|
+
* ESLint has 0-based column number
|
2887
|
+
* https://eslint.org/docs/developer-guide/working-with-rules#contextreport
|
2888
|
+
*/
|
2889
|
+
const loc = {
|
2890
|
+
start: {
|
2891
|
+
/*
|
2892
|
+
* Kind.Document has startToken: { line: 0, column: 0 }, we set line as 1 and column as 0
|
2893
|
+
*/
|
2894
|
+
line: startToken.line === 0 ? 1 : startToken.line,
|
2895
|
+
column: startToken.column === 0 ? 0 : startToken.column - 1,
|
2896
|
+
},
|
2897
|
+
end: {
|
2898
|
+
line: endToken.line,
|
2899
|
+
column: endToken.column - 1,
|
2900
|
+
},
|
2901
|
+
source: source.body,
|
2885
2902
|
};
|
2903
|
+
if (loc.start.column === loc.end.column) {
|
2904
|
+
loc.end.column += end - start;
|
2905
|
+
}
|
2906
|
+
return loc;
|
2886
2907
|
}
|
2887
2908
|
const convertNode = (typeInfo) => (node, key, parent) => {
|
2888
2909
|
const calculatedTypeInfo = typeInfo
|
@@ -2902,7 +2923,7 @@ const convertNode = (typeInfo) => (node, key, parent) => {
|
|
2902
2923
|
typeInfo: () => calculatedTypeInfo,
|
2903
2924
|
leadingComments: convertDescription(node),
|
2904
2925
|
loc: convertLocation(node.loc),
|
2905
|
-
range:
|
2926
|
+
range: [node.loc.start, node.loc.end],
|
2906
2927
|
};
|
2907
2928
|
if (hasTypeField(node)) {
|
2908
2929
|
const { type: gqlType, loc: gqlLocation, ...rest } = node;
|
@@ -2927,7 +2948,6 @@ const convertNode = (typeInfo) => (node, key, parent) => {
|
|
2927
2948
|
}
|
2928
2949
|
return parent[key];
|
2929
2950
|
},
|
2930
|
-
gqlLocation: stripTokens(gqlLocation),
|
2931
2951
|
};
|
2932
2952
|
return estreeNode;
|
2933
2953
|
}
|
@@ -2951,7 +2971,6 @@ const convertNode = (typeInfo) => (node, key, parent) => {
|
|
2951
2971
|
}
|
2952
2972
|
return parent[key];
|
2953
2973
|
},
|
2954
|
-
gqlLocation: stripTokens(gqlLocation),
|
2955
2974
|
};
|
2956
2975
|
return estreeNode;
|
2957
2976
|
}
|
@@ -3202,9 +3221,13 @@ const rule$k = {
|
|
3202
3221
|
};
|
3203
3222
|
checkFn({
|
3204
3223
|
getDocument: () => document,
|
3205
|
-
reportError
|
3224
|
+
reportError(error) {
|
3225
|
+
const { line, column } = error.locations[0];
|
3206
3226
|
context.report({
|
3207
|
-
loc:
|
3227
|
+
loc: {
|
3228
|
+
line,
|
3229
|
+
column: column - 1,
|
3230
|
+
},
|
3208
3231
|
message: error.message,
|
3209
3232
|
});
|
3210
3233
|
},
|
@@ -3379,7 +3402,7 @@ const rule$l = {
|
|
3379
3402
|
// we can extend this rule later.
|
3380
3403
|
if (validIds.length !== 1) {
|
3381
3404
|
context.report({
|
3382
|
-
|
3405
|
+
node: node.name,
|
3383
3406
|
messageId: RULE_ID$5,
|
3384
3407
|
data: {
|
3385
3408
|
typeName,
|
@@ -3415,7 +3438,7 @@ const checkNode = (context, node, ruleName, messageId) => {
|
|
3415
3438
|
.map(f => `\t${path.relative(process.cwd(), getOnDiskFilepath(f.filePath))}`)
|
3416
3439
|
.join('\n'),
|
3417
3440
|
},
|
3418
|
-
|
3441
|
+
node: node.name,
|
3419
3442
|
});
|
3420
3443
|
}
|
3421
3444
|
};
|
@@ -3969,6 +3992,15 @@ function parseForESLint(code, options = {}) {
|
|
3969
3992
|
}
|
3970
3993
|
}
|
3971
3994
|
|
3995
|
+
function indentCode(code, indent = 4) {
|
3996
|
+
return code.replace(/^/gm, ' '.repeat(indent));
|
3997
|
+
}
|
3998
|
+
function printCode(code) {
|
3999
|
+
return codeFrame.codeFrameColumns(code, { start: { line: 0, column: 0 } }, {
|
4000
|
+
linesAbove: Number.POSITIVE_INFINITY,
|
4001
|
+
linesBelow: Number.POSITIVE_INFINITY,
|
4002
|
+
});
|
4003
|
+
}
|
3972
4004
|
class GraphQLRuleTester extends eslint.RuleTester {
|
3973
4005
|
constructor(parserOptions = {}) {
|
3974
4006
|
const config = {
|
@@ -4010,19 +4042,29 @@ class GraphQLRuleTester extends eslint.RuleTester {
|
|
4010
4042
|
}
|
4011
4043
|
const linter = new eslint.Linter();
|
4012
4044
|
linter.defineRule(name, rule);
|
4045
|
+
const hasOnlyTest = tests.invalid.some(t => t.only);
|
4013
4046
|
for (const testCase of tests.invalid) {
|
4047
|
+
const { only, code, filename } = testCase;
|
4048
|
+
if (hasOnlyTest && !only) {
|
4049
|
+
continue;
|
4050
|
+
}
|
4014
4051
|
const verifyConfig = getVerifyConfig(name, this.config, testCase);
|
4015
4052
|
defineParser(linter, verifyConfig.parser);
|
4016
|
-
const { code, filename } = testCase;
|
4017
4053
|
const messages = linter.verify(code, verifyConfig, { filename });
|
4018
|
-
|
4054
|
+
const messageForSnapshot = [];
|
4055
|
+
for (const [index, message] of messages.entries()) {
|
4019
4056
|
if (message.fatal) {
|
4020
4057
|
throw new Error(message.message);
|
4021
4058
|
}
|
4022
|
-
|
4023
|
-
|
4024
|
-
|
4059
|
+
messageForSnapshot.push(`❌ Error ${index + 1}/${messages.length}`, visualizeEslintMessage(code, message));
|
4060
|
+
}
|
4061
|
+
if (rule.meta.fixable) {
|
4062
|
+
const { fixed, output } = linter.verifyAndFix(code, verifyConfig, { filename });
|
4063
|
+
if (fixed) {
|
4064
|
+
messageForSnapshot.push('🔧 Autofix output', indentCode(printCode(output), 2));
|
4065
|
+
}
|
4025
4066
|
}
|
4067
|
+
expect(messageForSnapshot.join('\n\n')).toMatchSnapshot();
|
4026
4068
|
}
|
4027
4069
|
}
|
4028
4070
|
}
|
@@ -4078,10 +4120,10 @@ function visualizeEslintMessage(text, result) {
|
|
4078
4120
|
exports.GraphQLRuleTester = GraphQLRuleTester;
|
4079
4121
|
exports.configs = configs;
|
4080
4122
|
exports.convertDescription = convertDescription;
|
4081
|
-
exports.convertLocation = convertLocation;
|
4082
|
-
exports.convertRange = convertRange;
|
4083
4123
|
exports.convertToESTree = convertToESTree;
|
4124
|
+
exports.convertToken = convertToken;
|
4084
4125
|
exports.extractCommentsFromAst = extractCommentsFromAst;
|
4126
|
+
exports.extractTokens = extractTokens;
|
4085
4127
|
exports.getBaseType = getBaseType;
|
4086
4128
|
exports.isNodeWithDescription = isNodeWithDescription;
|
4087
4129
|
exports.parse = parse;
|