@graphql-eslint/eslint-plugin 3.8.0-alpha-a8fcc7b.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
@@ -17,7 +17,6 @@ const graphqlConfig = require('graphql-config');
17
17
  const codeFileLoader = require('@graphql-tools/code-file-loader');
18
18
  const eslint = require('eslint');
19
19
  const codeFrame = require('@babel/code-frame');
20
- const dedent = _interopDefault(require('dedent'));
21
20
 
22
21
  const base = {
23
22
  parser: '@graphql-eslint/eslint-plugin',
@@ -209,43 +208,6 @@ function requireUsedFieldsFromContext(ruleName, context) {
209
208
  const siblings = requireSiblingsOperations(ruleName, context);
210
209
  return context.parserServices.usedFields(schema, siblings);
211
210
  }
212
- function getLexer(source) {
213
- // GraphQL v14
214
- const gqlLanguage = require('graphql/language');
215
- if (gqlLanguage && gqlLanguage.createLexer) {
216
- return gqlLanguage.createLexer(source, {});
217
- }
218
- // GraphQL v15
219
- const { Lexer: LexerCls } = require('graphql');
220
- if (LexerCls && typeof LexerCls === 'function') {
221
- return new LexerCls(source);
222
- }
223
- throw new Error(`Unsupported GraphQL version! Please make sure to use GraphQL v14 or newer!`);
224
- }
225
- function extractTokens(source) {
226
- const lexer = getLexer(source);
227
- const tokens = [];
228
- let token = lexer.advance();
229
- while (token && token.kind !== '<EOF>') {
230
- tokens.push({
231
- type: token.kind,
232
- loc: {
233
- start: {
234
- line: token.line,
235
- column: token.column,
236
- },
237
- end: {
238
- line: token.line,
239
- column: token.column,
240
- },
241
- },
242
- value: token.value,
243
- range: [token.start, token.end],
244
- });
245
- token = lexer.advance();
246
- }
247
- return tokens;
248
- }
249
211
  const normalizePath = (path) => (path || '').replace(/\\/g, '/');
250
212
  /**
251
213
  * https://github.com/prettier/eslint-plugin-prettier/blob/76bd45ece6d56eb52f75db6b4a1efdd2efb56392/eslint-plugin-prettier.js#L71
@@ -315,21 +277,16 @@ const convertCase = (style, str) => {
315
277
  return lowerCase(str).replace(/ /g, '-');
316
278
  }
317
279
  };
318
- function getLocation(loc, fieldName = '', offset) {
319
- const { start } = loc;
320
- /*
321
- * ESLint has 0-based column number
322
- * https://eslint.org/docs/developer-guide/working-with-rules#contextreport
323
- */
324
- const { offsetStart = 1, offsetEnd = 1 } = offset !== null && offset !== void 0 ? offset : {};
280
+ function getLocation(loc, fieldName = '') {
281
+ const { line, column } = loc.start;
325
282
  return {
326
283
  start: {
327
- line: start.line,
328
- column: start.column - offsetStart,
284
+ line,
285
+ column,
329
286
  },
330
287
  end: {
331
- line: start.line,
332
- column: start.column - offsetEnd + fieldName.length,
288
+ line,
289
+ column: column + fieldName.length,
333
290
  },
334
291
  };
335
292
  }
@@ -343,23 +300,16 @@ function validateDocument(context, schema = null, documentNode, rule) {
343
300
  ? graphql.validate(schema, documentNode, [rule])
344
301
  : validate.validateSDL(documentNode, null, [rule]);
345
302
  for (const error of validationErrors) {
346
- /*
347
- * TODO: Fix ESTree-AST converter because currently it's incorrectly convert loc.end
348
- * Example: loc.end always equal loc.start
349
- * {
350
- * token: {
351
- * type: 'Name',
352
- * loc: { start: { line: 4, column: 13 }, end: { line: 4, column: 13 } },
353
- * value: 'veryBad',
354
- * range: [ 40, 47 ]
355
- * }
356
- * }
357
- */
358
303
  const { line, column } = error.locations[0];
359
304
  const ancestors = context.getAncestors();
360
- 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);
361
306
  context.report({
362
- 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
+ },
363
313
  message: error.message,
364
314
  });
365
315
  }
@@ -692,10 +642,7 @@ const rule = {
692
642
  fixable: 'code',
693
643
  docs: {
694
644
  category: ['Schema', 'Operations'],
695
- description: [
696
- 'Enforce arrange in alphabetical order for type fields, enum values, input object fields, operation selections and more.',
697
- '> Note: autofix will work only for fields without comments (between or around)',
698
- ].join('\n\n'),
645
+ description: `Enforce arrange in alphabetical order for type fields, enum values, input object fields, operation selections and more.`,
699
646
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/alphabetize.md',
700
647
  examples: [
701
648
  {
@@ -853,8 +800,27 @@ const rule = {
853
800
  },
854
801
  create(context) {
855
802
  var _a, _b, _c, _d, _e;
856
- function isOnSameLineNodeAndComment(beforeNode, afterNode) {
857
- return beforeNode.loc.end.line === afterNode.loc.start.line;
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]];
858
824
  }
859
825
  function checkNodes(nodes) {
860
826
  // Starts from 1, ignore nodes.length <= 1
@@ -863,39 +829,27 @@ const rule = {
863
829
  const currNode = nodes[i];
864
830
  const prevName = prevNode.name.value;
865
831
  const currName = currNode.name.value;
866
- if (prevName.localeCompare(currName) === 1) {
867
- const isVariableNode = currNode.kind === graphql.Kind.VARIABLE;
868
- context.report({
869
- loc: getLocation(currNode.loc, currName, { offsetEnd: isVariableNode ? 0 : 1 }),
870
- messageId: ALPHABETIZE,
871
- data: isVariableNode
872
- ? {
873
- currName: `$${currName}`,
874
- prevName: `$${prevName}`,
875
- }
876
- : { currName, prevName },
877
- *fix(fixer) {
878
- const prev = prevNode;
879
- const curr = currNode;
880
- const sourceCode = context.getSourceCode();
881
- const beforeComments = sourceCode.getCommentsBefore(prev);
882
- if (beforeComments.length > 0) {
883
- const tokenBefore = sourceCode.getTokenBefore(prev);
884
- const lastBeforeComment = beforeComments.at(-1);
885
- if (!tokenBefore || !isOnSameLineNodeAndComment(tokenBefore, lastBeforeComment))
886
- return;
887
- }
888
- const betweenComments = sourceCode.getCommentsBefore(curr);
889
- if (betweenComments.length > 0)
890
- return;
891
- const [firstAfterComment] = sourceCode.getCommentsAfter(curr);
892
- if (firstAfterComment && isOnSameLineNodeAndComment(curr, firstAfterComment))
893
- return;
894
- yield fixer.replaceText(prev, sourceCode.getText(curr));
895
- yield fixer.replaceText(curr, sourceCode.getText(prev));
896
- },
897
- });
832
+ // Compare with lexicographic order
833
+ if (prevName.localeCompare(currName) !== 1) {
834
+ continue;
898
835
  }
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
+ });
899
853
  }
900
854
  }
901
855
  const opts = context.options[0];
@@ -1092,7 +1046,7 @@ const rule$2 = {
1092
1046
  if (shouldCheckType(node.parent.parent)) {
1093
1047
  const name = node.name.value;
1094
1048
  context.report({
1095
- loc: getLocation(node.loc, name),
1049
+ node: node.name,
1096
1050
  message: `Input "${name}" should be called "input"`,
1097
1051
  });
1098
1052
  }
@@ -1114,7 +1068,7 @@ const rule$2 = {
1114
1068
  if ((options.caseSensitiveInputType && node.name.value !== mutationName) ||
1115
1069
  name.toLowerCase() !== mutationName.toLowerCase()) {
1116
1070
  context.report({
1117
- loc: getLocation(node.loc, name),
1071
+ node: node.name,
1118
1072
  message: `InputType "${name}" name should be "${mutationName}"`,
1119
1073
  });
1120
1074
  }
@@ -1128,7 +1082,7 @@ const rule$2 = {
1128
1082
  const MATCH_EXTENSION = 'MATCH_EXTENSION';
1129
1083
  const MATCH_STYLE = 'MATCH_STYLE';
1130
1084
  const ACCEPTED_EXTENSIONS = ['.gql', '.graphql'];
1131
- const CASE_STYLES = ['camelCase', 'PascalCase', 'snake_case', 'UPPER_CASE', 'kebab-case'];
1085
+ const CASE_STYLES = ['camelCase', 'PascalCase', 'snake_case', 'UPPER_CASE', 'kebab-case', 'matchDocumentStyle'];
1132
1086
  const schemaOption = {
1133
1087
  oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
1134
1088
  };
@@ -1302,7 +1256,14 @@ const rule$3 = {
1302
1256
  option = { style: option };
1303
1257
  }
1304
1258
  const expectedExtension = options.fileExtension || fileExtension;
1305
- const expectedFilename = (option.style ? convertCase(option.style, docName) : filename) + (option.suffix || '') + expectedExtension;
1259
+ let expectedFilename;
1260
+ if (option.style) {
1261
+ expectedFilename = option.style === 'matchDocumentStyle' ? docName : convertCase(option.style, docName);
1262
+ }
1263
+ else {
1264
+ expectedFilename = filename;
1265
+ }
1266
+ expectedFilename += (option.suffix || '') + expectedExtension;
1306
1267
  const filenameWithExtension = filename + expectedExtension;
1307
1268
  if (expectedFilename !== filenameWithExtension) {
1308
1269
  context.report({
@@ -1559,7 +1520,7 @@ const rule$4 = {
1559
1520
  const [trailingUnderscores] = nodeName.match(/_*$/);
1560
1521
  const suggestedName = leadingUnderscores + renameToName + trailingUnderscores;
1561
1522
  context.report({
1562
- loc: getLocation(node.loc, node.value),
1523
+ node,
1563
1524
  message: `${nodeType} "${nodeName}" should ${errorMessage}`,
1564
1525
  suggest: [
1565
1526
  {
@@ -1617,7 +1578,7 @@ const rule$4 = {
1617
1578
  const name = node.value;
1618
1579
  const renameToName = name.replace(new RegExp(isLeading ? '^_+' : '_+$'), '');
1619
1580
  context.report({
1620
- loc: getLocation(node.loc, name),
1581
+ node,
1621
1582
  message: `${isLeading ? 'Leading' : 'Trailing'} underscores are not allowed`,
1622
1583
  suggest: [
1623
1584
  {
@@ -1733,7 +1694,7 @@ const rule$6 = {
1733
1694
  for (const duplicate of duplicates) {
1734
1695
  const enumName = duplicate.name.value;
1735
1696
  context.report({
1736
- loc: getLocation(duplicate.loc, enumName),
1697
+ node: duplicate.name,
1737
1698
  message: `Case-insensitive enum values duplicates are not allowed! Found: "${enumName}"`,
1738
1699
  });
1739
1700
  }
@@ -1827,9 +1788,8 @@ const rule$7 = {
1827
1788
  const typeInfo = node.typeInfo();
1828
1789
  if (typeInfo && typeInfo.enumValue) {
1829
1790
  if (typeInfo.enumValue.deprecationReason) {
1830
- const enumValueName = node.value;
1831
1791
  context.report({
1832
- loc: getLocation(node.loc, enumValueName),
1792
+ node,
1833
1793
  messageId: NO_DEPRECATED,
1834
1794
  data: {
1835
1795
  type: 'enum value',
@@ -1844,9 +1804,8 @@ const rule$7 = {
1844
1804
  const typeInfo = node.typeInfo();
1845
1805
  if (typeInfo && typeInfo.fieldDef) {
1846
1806
  if (typeInfo.fieldDef.deprecationReason) {
1847
- const fieldName = node.name.value;
1848
1807
  context.report({
1849
- loc: getLocation(node.loc, fieldName),
1808
+ node: node.name,
1850
1809
  messageId: NO_DEPRECATED,
1851
1810
  data: {
1852
1811
  type: 'field',
@@ -1918,12 +1877,11 @@ const rule$8 = {
1918
1877
  schema: [],
1919
1878
  },
1920
1879
  create(context) {
1921
- function checkNode(usedFields, fieldName, type, node) {
1880
+ function checkNode(usedFields, type, node) {
1881
+ const fieldName = node.value;
1922
1882
  if (usedFields.has(fieldName)) {
1923
1883
  context.report({
1924
- loc: getLocation((node.kind === graphql.Kind.FIELD && node.alias ? node.alias : node).loc, fieldName, {
1925
- offsetEnd: node.kind === graphql.Kind.VARIABLE_DEFINITION ? 0 : 1,
1926
- }),
1884
+ node,
1927
1885
  messageId: NO_DUPLICATE_FIELDS,
1928
1886
  data: {
1929
1887
  type,
@@ -1939,21 +1897,20 @@ const rule$8 = {
1939
1897
  OperationDefinition(node) {
1940
1898
  const set = new Set();
1941
1899
  for (const varDef of node.variableDefinitions) {
1942
- checkNode(set, varDef.variable.name.value, 'Operation variable', varDef);
1900
+ checkNode(set, 'Operation variable', varDef.variable.name);
1943
1901
  }
1944
1902
  },
1945
1903
  Field(node) {
1946
1904
  const set = new Set();
1947
1905
  for (const arg of node.arguments) {
1948
- checkNode(set, arg.name.value, 'Field argument', arg);
1906
+ checkNode(set, 'Field argument', arg.name);
1949
1907
  }
1950
1908
  },
1951
1909
  SelectionSet(node) {
1952
- var _a;
1953
1910
  const set = new Set();
1954
1911
  for (const selection of node.selections) {
1955
1912
  if (selection.kind === graphql.Kind.FIELD) {
1956
- 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);
1957
1914
  }
1958
1915
  }
1959
1916
  },
@@ -2012,7 +1969,7 @@ const rule$9 = {
2012
1969
  schema: [],
2013
1970
  },
2014
1971
  create(context) {
2015
- 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)$/]';
2016
1973
  return {
2017
1974
  [selector](node) {
2018
1975
  const rawNode = node.rawNode();
@@ -2025,7 +1982,10 @@ const rule$9 = {
2025
1982
  if (!isEslintComment && line !== prev.line && next.kind === graphql.TokenKind.NAME && linesAfter < 2) {
2026
1983
  context.report({
2027
1984
  messageId: HASHTAG_COMMENT,
2028
- loc: getLocation({ start: { line, column } }),
1985
+ loc: {
1986
+ line,
1987
+ column: column - 1,
1988
+ },
2029
1989
  });
2030
1990
  }
2031
1991
  }
@@ -2108,7 +2068,7 @@ const rule$a = {
2108
2068
  [selector](node) {
2109
2069
  const typeName = node.value;
2110
2070
  context.report({
2111
- loc: getLocation(node.loc, typeName),
2071
+ node,
2112
2072
  message: `Root type "${typeName}" is forbidden`,
2113
2073
  });
2114
2074
  },
@@ -2161,7 +2121,7 @@ const rule$b = {
2161
2121
  const graphQLType = schema.getType(typeName);
2162
2122
  if (graphql.isScalarType(graphQLType)) {
2163
2123
  context.report({
2164
- loc: getLocation(node.loc, typeName),
2124
+ node,
2165
2125
  message: `Unexpected scalar result type "${typeName}"`,
2166
2126
  });
2167
2127
  }
@@ -2217,7 +2177,7 @@ const rule$c = {
2217
2177
  typeName,
2218
2178
  },
2219
2179
  messageId: NO_TYPENAME_PREFIX,
2220
- loc: getLocation(field.loc, lowerTypeName),
2180
+ node: field.name,
2221
2181
  });
2222
2182
  }
2223
2183
  }
@@ -2295,7 +2255,7 @@ const rule$d = {
2295
2255
  const typeName = node.name.value;
2296
2256
  if (!reachableTypes.has(typeName)) {
2297
2257
  context.report({
2298
- loc: getLocation(node.name.loc, typeName, { offsetStart: node.kind === graphql.Kind.DIRECTIVE_DEFINITION ? 2 : 1 }),
2258
+ node: node.name,
2299
2259
  messageId: UNREACHABLE_TYPE,
2300
2260
  data: { typeName },
2301
2261
  suggest: [
@@ -2384,7 +2344,7 @@ const rule$e = {
2384
2344
  return;
2385
2345
  }
2386
2346
  context.report({
2387
- loc: getLocation(node.loc, fieldName),
2347
+ node: node.name,
2388
2348
  messageId: UNUSED_FIELD,
2389
2349
  data: { fieldName },
2390
2350
  suggest: [
@@ -2437,8 +2397,51 @@ function getBaseType(type) {
2437
2397
  }
2438
2398
  return type;
2439
2399
  }
2440
- function convertRange(gqlLocation) {
2441
- 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;
2442
2445
  }
2443
2446
  function extractCommentsFromAst(loc) {
2444
2447
  if (!loc) {
@@ -2447,35 +2450,16 @@ function extractCommentsFromAst(loc) {
2447
2450
  const comments = [];
2448
2451
  let token = loc.startToken;
2449
2452
  while (token !== null) {
2450
- const { kind, value, line, column, start, end, next } = token;
2451
- if (kind === graphql.TokenKind.COMMENT) {
2452
- comments.push({
2453
- type: 'Block',
2454
- value,
2455
- loc: {
2456
- start: { line, column },
2457
- end: { line, column },
2458
- },
2459
- range: [start, end],
2460
- });
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);
2461
2458
  }
2462
- token = next;
2459
+ token = token.next;
2463
2460
  }
2464
2461
  return comments;
2465
2462
  }
2466
- function convertLocation(gqlLocation) {
2467
- return {
2468
- start: {
2469
- column: gqlLocation.startToken.column,
2470
- line: gqlLocation.startToken.line,
2471
- },
2472
- end: {
2473
- column: gqlLocation.endToken.column,
2474
- line: gqlLocation.endToken.line,
2475
- },
2476
- source: gqlLocation.source.body,
2477
- };
2478
- }
2479
2463
  function isNodeWithDescription(obj) {
2480
2464
  var _a;
2481
2465
  return (_a = obj) === null || _a === void 0 ? void 0 : _a.description;
@@ -2561,7 +2545,7 @@ const rule$f = {
2561
2545
  const deletionDateNode = node.arguments.find(arg => arg.name.value === argName);
2562
2546
  if (!deletionDateNode) {
2563
2547
  context.report({
2564
- loc: getLocation(node.loc, node.name.value, { offsetEnd: 0 }),
2548
+ node: node.name,
2565
2549
  messageId: MESSAGE_REQUIRE_DATE,
2566
2550
  });
2567
2551
  return;
@@ -2589,7 +2573,7 @@ const rule$f = {
2589
2573
  const canRemove = Date.now() > deletionDateInMS;
2590
2574
  if (canRemove) {
2591
2575
  context.report({
2592
- node,
2576
+ node: node.parent.name,
2593
2577
  messageId: MESSAGE_CAN_BE_REMOVED,
2594
2578
  data: {
2595
2579
  nodeName: node.parent.name.value,
@@ -2646,7 +2630,7 @@ const rule$g = {
2646
2630
  const value = reasonArg ? String(valueFromNode(reasonArg.value) || '').trim() : null;
2647
2631
  if (!value) {
2648
2632
  context.report({
2649
- loc: getLocation(node.loc, node.name.value, { offsetEnd: 0 }),
2633
+ node: node.name,
2650
2634
  message: 'Directive "@deprecated" must have a reason!',
2651
2635
  });
2652
2636
  }
@@ -2658,12 +2642,40 @@ const rule$g = {
2658
2642
  const RULE_ID$2 = 'require-description';
2659
2643
  const ALLOWED_KINDS$1 = [
2660
2644
  ...TYPES_KINDS,
2645
+ graphql.Kind.DIRECTIVE_DEFINITION,
2661
2646
  graphql.Kind.FIELD_DEFINITION,
2662
2647
  graphql.Kind.INPUT_VALUE_DEFINITION,
2663
2648
  graphql.Kind.ENUM_VALUE_DEFINITION,
2664
- graphql.Kind.DIRECTIVE_DEFINITION,
2665
2649
  graphql.Kind.OPERATION_DEFINITION,
2666
2650
  ];
2651
+ function getNodeName(node) {
2652
+ const DisplayNodeNameMap = {
2653
+ [graphql.Kind.OBJECT_TYPE_DEFINITION]: 'type',
2654
+ [graphql.Kind.INTERFACE_TYPE_DEFINITION]: 'interface',
2655
+ [graphql.Kind.ENUM_TYPE_DEFINITION]: 'enum',
2656
+ [graphql.Kind.SCALAR_TYPE_DEFINITION]: 'scalar',
2657
+ [graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION]: 'input',
2658
+ [graphql.Kind.UNION_TYPE_DEFINITION]: 'union',
2659
+ [graphql.Kind.DIRECTIVE_DEFINITION]: 'directive',
2660
+ };
2661
+ switch (node.kind) {
2662
+ case graphql.Kind.OBJECT_TYPE_DEFINITION:
2663
+ case graphql.Kind.INTERFACE_TYPE_DEFINITION:
2664
+ case graphql.Kind.ENUM_TYPE_DEFINITION:
2665
+ case graphql.Kind.SCALAR_TYPE_DEFINITION:
2666
+ case graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION:
2667
+ case graphql.Kind.UNION_TYPE_DEFINITION:
2668
+ return `${DisplayNodeNameMap[node.kind]} ${node.name.value}`;
2669
+ case graphql.Kind.DIRECTIVE_DEFINITION:
2670
+ return `${DisplayNodeNameMap[node.kind]} @${node.name.value}`;
2671
+ case graphql.Kind.FIELD_DEFINITION:
2672
+ case graphql.Kind.INPUT_VALUE_DEFINITION:
2673
+ case graphql.Kind.ENUM_VALUE_DEFINITION:
2674
+ return `${node.parent.name.value}.${node.name.value}`;
2675
+ case graphql.Kind.OPERATION_DEFINITION:
2676
+ return node.name ? `${node.operation} ${node.name.value}` : node.operation;
2677
+ }
2678
+ }
2667
2679
  const rule$h = {
2668
2680
  meta: {
2669
2681
  docs: {
@@ -2716,7 +2728,7 @@ const rule$h = {
2716
2728
  },
2717
2729
  type: 'suggestion',
2718
2730
  messages: {
2719
- [RULE_ID$2]: 'Description is required for nodes of type "{{ nodeType }}"',
2731
+ [RULE_ID$2]: 'Description is required for `{{ nodeName }}`.',
2720
2732
  },
2721
2733
  schema: {
2722
2734
  type: 'array',
@@ -2775,10 +2787,10 @@ const rule$h = {
2775
2787
  }
2776
2788
  if (description.length === 0) {
2777
2789
  context.report({
2778
- 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,
2779
2791
  messageId: RULE_ID$2,
2780
2792
  data: {
2781
- nodeType: node.kind,
2793
+ nodeName: getNodeName(node),
2782
2794
  },
2783
2795
  });
2784
2796
  }
@@ -2848,7 +2860,7 @@ const rule$i = {
2848
2860
  const hasQueryType = fields.some(field => getTypeName(field) === queryType.name);
2849
2861
  if (!hasQueryType) {
2850
2862
  context.report({
2851
- loc: getLocation(node.loc, typeName),
2863
+ node,
2852
2864
  message: `Mutation result type "${graphQLType.name}" must contain field of type "${queryType.name}"`,
2853
2865
  });
2854
2866
  }
@@ -2868,16 +2880,30 @@ function convertToESTree(node, typeInfo) {
2868
2880
  function hasTypeField(obj) {
2869
2881
  return obj && !!obj.type;
2870
2882
  }
2871
- /**
2872
- * Strips tokens information from `location` object - this is needed since it's created as linked list in GraphQL-JS,
2873
- * causing eslint to fail on circular JSON
2874
- * @param location
2875
- */
2876
- function stripTokens(location) {
2877
- return {
2878
- end: location.end,
2879
- 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,
2880
2902
  };
2903
+ if (loc.start.column === loc.end.column) {
2904
+ loc.end.column += end - start;
2905
+ }
2906
+ return loc;
2881
2907
  }
2882
2908
  const convertNode = (typeInfo) => (node, key, parent) => {
2883
2909
  const calculatedTypeInfo = typeInfo
@@ -2897,7 +2923,7 @@ const convertNode = (typeInfo) => (node, key, parent) => {
2897
2923
  typeInfo: () => calculatedTypeInfo,
2898
2924
  leadingComments: convertDescription(node),
2899
2925
  loc: convertLocation(node.loc),
2900
- range: convertRange(node.loc),
2926
+ range: [node.loc.start, node.loc.end],
2901
2927
  };
2902
2928
  if (hasTypeField(node)) {
2903
2929
  const { type: gqlType, loc: gqlLocation, ...rest } = node;
@@ -2922,7 +2948,6 @@ const convertNode = (typeInfo) => (node, key, parent) => {
2922
2948
  }
2923
2949
  return parent[key];
2924
2950
  },
2925
- gqlLocation: stripTokens(gqlLocation),
2926
2951
  };
2927
2952
  return estreeNode;
2928
2953
  }
@@ -2946,7 +2971,6 @@ const convertNode = (typeInfo) => (node, key, parent) => {
2946
2971
  }
2947
2972
  return parent[key];
2948
2973
  },
2949
- gqlLocation: stripTokens(gqlLocation),
2950
2974
  };
2951
2975
  return estreeNode;
2952
2976
  }
@@ -3197,9 +3221,13 @@ const rule$k = {
3197
3221
  };
3198
3222
  checkFn({
3199
3223
  getDocument: () => document,
3200
- reportError: (error) => {
3224
+ reportError(error) {
3225
+ const { line, column } = error.locations[0];
3201
3226
  context.report({
3202
- loc: getLocation({ start: error.locations[0] }),
3227
+ loc: {
3228
+ line,
3229
+ column: column - 1,
3230
+ },
3203
3231
  message: error.message,
3204
3232
  });
3205
3233
  },
@@ -3374,7 +3402,7 @@ const rule$l = {
3374
3402
  // we can extend this rule later.
3375
3403
  if (validIds.length !== 1) {
3376
3404
  context.report({
3377
- loc: getLocation(node.name.loc, typeName),
3405
+ node: node.name,
3378
3406
  messageId: RULE_ID$5,
3379
3407
  data: {
3380
3408
  typeName,
@@ -3410,7 +3438,7 @@ const checkNode = (context, node, ruleName, messageId) => {
3410
3438
  .map(f => `\t${path.relative(process.cwd(), getOnDiskFilepath(f.filePath))}`)
3411
3439
  .join('\n'),
3412
3440
  },
3413
- loc: getLocation(node.name.loc, documentName),
3441
+ node: node.name,
3414
3442
  });
3415
3443
  }
3416
3444
  };
@@ -3964,6 +3992,15 @@ function parseForESLint(code, options = {}) {
3964
3992
  }
3965
3993
  }
3966
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
+ }
3967
4004
  class GraphQLRuleTester extends eslint.RuleTester {
3968
4005
  constructor(parserOptions = {}) {
3969
4006
  const config = {
@@ -4019,15 +4056,14 @@ class GraphQLRuleTester extends eslint.RuleTester {
4019
4056
  if (message.fatal) {
4020
4057
  throw new Error(message.message);
4021
4058
  }
4022
- messageForSnapshot.push(`Error ${index + 1}/${messages.length}`, visualizeEslintMessage(code, message));
4059
+ messageForSnapshot.push(`❌ Error ${index + 1}/${messages.length}`, visualizeEslintMessage(code, message));
4023
4060
  }
4024
4061
  if (rule.meta.fixable) {
4025
4062
  const { fixed, output } = linter.verifyAndFix(code, verifyConfig, { filename });
4026
4063
  if (fixed) {
4027
- messageForSnapshot.push('Autofix output', dedent(output));
4064
+ messageForSnapshot.push('🔧 Autofix output', indentCode(printCode(output), 2));
4028
4065
  }
4029
4066
  }
4030
- // eslint-disable-next-line no-undef
4031
4067
  expect(messageForSnapshot.join('\n\n')).toMatchSnapshot();
4032
4068
  }
4033
4069
  }
@@ -4084,10 +4120,10 @@ function visualizeEslintMessage(text, result) {
4084
4120
  exports.GraphQLRuleTester = GraphQLRuleTester;
4085
4121
  exports.configs = configs;
4086
4122
  exports.convertDescription = convertDescription;
4087
- exports.convertLocation = convertLocation;
4088
- exports.convertRange = convertRange;
4089
4123
  exports.convertToESTree = convertToESTree;
4124
+ exports.convertToken = convertToken;
4090
4125
  exports.extractCommentsFromAst = extractCommentsFromAst;
4126
+ exports.extractTokens = extractTokens;
4091
4127
  exports.getBaseType = getBaseType;
4092
4128
  exports.isNodeWithDescription = isNodeWithDescription;
4093
4129
  exports.parse = parse;