@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/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 = '', offset) {
318
- const { start } = loc;
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: start.line,
327
- column: start.column - offsetStart,
284
+ line,
285
+ column,
328
286
  },
329
287
  end: {
330
- line: start.line,
331
- column: start.column - offsetEnd + fieldName.length,
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: getLocation({ start: error.locations[0] }, token === null || token === void 0 ? void 0 : token.value),
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: 'Enforce arrange in alphabetical order for type fields, enum values, input object fields, operation selections and more.',
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
- let prevName = null;
853
- for (const node of nodes) {
854
- const currName = node.name.value;
855
- if (prevName && prevName > currName) {
856
- const isVariableNode = node.kind === graphql.Kind.VARIABLE;
857
- context.report({
858
- loc: getLocation(node.loc, node.name.value, { offsetEnd: isVariableNode ? 0 : 1 }),
859
- messageId: ALPHABETIZE,
860
- data: isVariableNode
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
- prevName = currName;
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
- loc: getLocation(node.loc, name),
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
- loc: getLocation(node.loc, name),
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
- loc: getLocation(node.loc, node.value),
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
- loc: getLocation(node.loc, name),
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
- loc: getLocation(duplicate.loc, enumName),
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
- loc: getLocation(node.loc, enumValueName),
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
- loc: getLocation(node.loc, fieldName),
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, fieldName, type, node) {
1880
+ function checkNode(usedFields, type, node) {
1881
+ const fieldName = node.value;
1899
1882
  if (usedFields.has(fieldName)) {
1900
1883
  context.report({
1901
- loc: getLocation((node.kind === graphql.Kind.FIELD && node.alias ? node.alias : node).loc, fieldName, {
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, varDef.variable.name.value, 'Operation variable', varDef);
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, arg.name.value, 'Field argument', arg);
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, ((_a = selection.alias) === null || _a === void 0 ? void 0 : _a.value) || selection.name.value, 'Field', selection);
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 = `${graphql.Kind.DOCUMENT}[definitions.0.kind!=/^(${graphql.Kind.OPERATION_DEFINITION}|${graphql.Kind.FRAGMENT_DEFINITION})$/]`;
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: getLocation({ start: { line, column } }),
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
- loc: getLocation(node.loc, typeName),
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
- loc: getLocation(node.loc, typeName),
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
- loc: getLocation(field.loc, lowerTypeName),
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
- loc: getLocation(node.name.loc, typeName, { offsetStart: node.kind === graphql.Kind.DIRECTIVE_DEFINITION ? 2 : 1 }),
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
- loc: getLocation(node.loc, fieldName),
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 convertRange(gqlLocation) {
2418
- return [gqlLocation.start, gqlLocation.end];
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
- const { kind, value, line, column, start, end, next } = token;
2428
- if (kind === graphql.TokenKind.COMMENT) {
2429
- comments.push({
2430
- type: 'Block',
2431
- value,
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
- loc: getLocation(node.loc, node.name.value, { offsetEnd: 0 }),
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
- loc: getLocation(node.loc, node.name.value, { offsetEnd: 0 }),
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) : getLocation(node.name.loc, node.name.value),
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
- loc: getLocation(node.loc, typeName),
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
- * Strips tokens information from `location` object - this is needed since it's created as linked list in GraphQL-JS,
2878
- * causing eslint to fail on circular JSON
2879
- * @param location
2880
- */
2881
- function stripTokens(location) {
2882
- return {
2883
- end: location.end,
2884
- start: location.start,
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: convertRange(node.loc),
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: (error) => {
3224
+ reportError(error) {
3225
+ const { line, column } = error.locations[0];
3206
3226
  context.report({
3207
- loc: getLocation({ start: error.locations[0] }),
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
- loc: getLocation(node.name.loc, typeName),
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
- loc: getLocation(node.name.loc, documentName),
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
- for (const message of messages) {
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
- const messageForSnapshot = visualizeEslintMessage(code, message);
4023
- // eslint-disable-next-line no-undef
4024
- expect(messageForSnapshot).toMatchSnapshot();
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;