@graphql-eslint/eslint-plugin 3.8.0-alpha-db02c77.0 → 3.8.0-alpha-269d044.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.mjs CHANGED
@@ -11,7 +11,6 @@ import { loadConfigSync, GraphQLConfig } from 'graphql-config';
11
11
  import { CodeFileLoader } from '@graphql-tools/code-file-loader';
12
12
  import { RuleTester, Linter } from 'eslint';
13
13
  import { codeFrameColumns } from '@babel/code-frame';
14
- import dedent from 'dedent';
15
14
 
16
15
  const base = {
17
16
  parser: '@graphql-eslint/eslint-plugin',
@@ -203,43 +202,6 @@ function requireUsedFieldsFromContext(ruleName, context) {
203
202
  const siblings = requireSiblingsOperations(ruleName, context);
204
203
  return context.parserServices.usedFields(schema, siblings);
205
204
  }
206
- function getLexer(source) {
207
- // GraphQL v14
208
- const gqlLanguage = require('graphql/language');
209
- if (gqlLanguage && gqlLanguage.createLexer) {
210
- return gqlLanguage.createLexer(source, {});
211
- }
212
- // GraphQL v15
213
- const { Lexer: LexerCls } = require('graphql');
214
- if (LexerCls && typeof LexerCls === 'function') {
215
- return new LexerCls(source);
216
- }
217
- throw new Error(`Unsupported GraphQL version! Please make sure to use GraphQL v14 or newer!`);
218
- }
219
- function extractTokens(source) {
220
- const lexer = getLexer(source);
221
- const tokens = [];
222
- let token = lexer.advance();
223
- while (token && token.kind !== '<EOF>') {
224
- tokens.push({
225
- type: token.kind,
226
- loc: {
227
- start: {
228
- line: token.line,
229
- column: token.column,
230
- },
231
- end: {
232
- line: token.line,
233
- column: token.column,
234
- },
235
- },
236
- value: token.value,
237
- range: [token.start, token.end],
238
- });
239
- token = lexer.advance();
240
- }
241
- return tokens;
242
- }
243
205
  const normalizePath = (path) => (path || '').replace(/\\/g, '/');
244
206
  /**
245
207
  * https://github.com/prettier/eslint-plugin-prettier/blob/76bd45ece6d56eb52f75db6b4a1efdd2efb56392/eslint-plugin-prettier.js#L71
@@ -309,21 +271,16 @@ const convertCase = (style, str) => {
309
271
  return lowerCase(str).replace(/ /g, '-');
310
272
  }
311
273
  };
312
- function getLocation(loc, fieldName = '', offset) {
313
- const { start } = loc;
314
- /*
315
- * ESLint has 0-based column number
316
- * https://eslint.org/docs/developer-guide/working-with-rules#contextreport
317
- */
318
- const { offsetStart = 1, offsetEnd = 1 } = offset !== null && offset !== void 0 ? offset : {};
274
+ function getLocation(loc, fieldName = '') {
275
+ const { line, column } = loc.start;
319
276
  return {
320
277
  start: {
321
- line: start.line,
322
- column: start.column - offsetStart,
278
+ line,
279
+ column,
323
280
  },
324
281
  end: {
325
- line: start.line,
326
- column: start.column - offsetEnd + fieldName.length,
282
+ line,
283
+ column: column + fieldName.length,
327
284
  },
328
285
  };
329
286
  }
@@ -337,23 +294,16 @@ function validateDocument(context, schema = null, documentNode, rule) {
337
294
  ? validate(schema, documentNode, [rule])
338
295
  : validateSDL(documentNode, null, [rule]);
339
296
  for (const error of validationErrors) {
340
- /*
341
- * TODO: Fix ESTree-AST converter because currently it's incorrectly convert loc.end
342
- * Example: loc.end always equal loc.start
343
- * {
344
- * token: {
345
- * type: 'Name',
346
- * loc: { start: { line: 4, column: 13 }, end: { line: 4, column: 13 } },
347
- * value: 'veryBad',
348
- * range: [ 40, 47 ]
349
- * }
350
- * }
351
- */
352
297
  const { line, column } = error.locations[0];
353
298
  const ancestors = context.getAncestors();
354
- const token = ancestors[0].tokens.find(token => token.loc.start.line === line && token.loc.start.column === column);
299
+ const token = ancestors[0].tokens.find(token => token.loc.start.line === line && token.loc.start.column === column - 1);
355
300
  context.report({
356
- loc: getLocation({ start: error.locations[0] }, token === null || token === void 0 ? void 0 : token.value),
301
+ loc: token
302
+ ? token.loc
303
+ : {
304
+ line,
305
+ column: column - 1,
306
+ },
357
307
  message: error.message,
358
308
  });
359
309
  }
@@ -686,10 +636,7 @@ const rule = {
686
636
  fixable: 'code',
687
637
  docs: {
688
638
  category: ['Schema', 'Operations'],
689
- description: [
690
- 'Enforce arrange in alphabetical order for type fields, enum values, input object fields, operation selections and more.',
691
- '> Note: autofix will work only for fields without comments (between or around)',
692
- ].join('\n\n'),
639
+ description: `Enforce arrange in alphabetical order for type fields, enum values, input object fields, operation selections and more.`,
693
640
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/alphabetize.md',
694
641
  examples: [
695
642
  {
@@ -847,8 +794,27 @@ const rule = {
847
794
  },
848
795
  create(context) {
849
796
  var _a, _b, _c, _d, _e;
850
- function isOnSameLineNodeAndComment(beforeNode, afterNode) {
851
- return beforeNode.loc.end.line === afterNode.loc.start.line;
797
+ const sourceCode = context.getSourceCode();
798
+ function isNodeAndCommentOnSameLine(node, comment) {
799
+ return node.loc.end.line === comment.loc.start.line;
800
+ }
801
+ function getBeforeComments(node) {
802
+ const commentsBefore = sourceCode.getCommentsBefore(node);
803
+ if (commentsBefore.length === 0) {
804
+ return [];
805
+ }
806
+ const tokenBefore = sourceCode.getTokenBefore(node);
807
+ if (tokenBefore) {
808
+ return commentsBefore.filter(comment => !isNodeAndCommentOnSameLine(tokenBefore, comment));
809
+ }
810
+ return commentsBefore;
811
+ }
812
+ function getRangeWithComments(node) {
813
+ const [firstBeforeComment] = getBeforeComments(node);
814
+ const [firstAfterComment] = sourceCode.getCommentsAfter(node);
815
+ const from = firstBeforeComment || node;
816
+ const to = firstAfterComment && isNodeAndCommentOnSameLine(node, firstAfterComment) ? firstAfterComment : node;
817
+ return [from.range[0], to.range[1]];
852
818
  }
853
819
  function checkNodes(nodes) {
854
820
  // Starts from 1, ignore nodes.length <= 1
@@ -857,39 +823,27 @@ const rule = {
857
823
  const currNode = nodes[i];
858
824
  const prevName = prevNode.name.value;
859
825
  const currName = currNode.name.value;
860
- if (prevName.localeCompare(currName) === 1) {
861
- const isVariableNode = currNode.kind === Kind.VARIABLE;
862
- context.report({
863
- loc: getLocation(currNode.loc, currName, { offsetEnd: isVariableNode ? 0 : 1 }),
864
- messageId: ALPHABETIZE,
865
- data: isVariableNode
866
- ? {
867
- currName: `$${currName}`,
868
- prevName: `$${prevName}`,
869
- }
870
- : { currName, prevName },
871
- *fix(fixer) {
872
- const prev = prevNode;
873
- const curr = currNode;
874
- const sourceCode = context.getSourceCode();
875
- const beforeComments = sourceCode.getCommentsBefore(prev);
876
- if (beforeComments.length > 0) {
877
- const tokenBefore = sourceCode.getTokenBefore(prev);
878
- const lastBeforeComment = beforeComments[beforeComments.length - 1];
879
- if (!tokenBefore || !isOnSameLineNodeAndComment(tokenBefore, lastBeforeComment))
880
- return;
881
- }
882
- const betweenComments = sourceCode.getCommentsBefore(curr);
883
- if (betweenComments.length > 0)
884
- return;
885
- const [firstAfterComment] = sourceCode.getCommentsAfter(curr);
886
- if (firstAfterComment && isOnSameLineNodeAndComment(curr, firstAfterComment))
887
- return;
888
- yield fixer.replaceText(prev, sourceCode.getText(curr));
889
- yield fixer.replaceText(curr, sourceCode.getText(prev));
890
- },
891
- });
826
+ // Compare with lexicographic order
827
+ if (prevName.localeCompare(currName) !== 1) {
828
+ continue;
892
829
  }
830
+ const isVariableNode = currNode.kind === Kind.VARIABLE;
831
+ context.report({
832
+ node: currNode.name,
833
+ messageId: ALPHABETIZE,
834
+ data: isVariableNode
835
+ ? {
836
+ currName: `$${currName}`,
837
+ prevName: `$${prevName}`,
838
+ }
839
+ : { currName, prevName },
840
+ *fix(fixer) {
841
+ const prevRange = getRangeWithComments(prevNode);
842
+ const currRange = getRangeWithComments(currNode);
843
+ yield fixer.replaceTextRange(prevRange, sourceCode.getText({ range: currRange }));
844
+ yield fixer.replaceTextRange(currRange, sourceCode.getText({ range: prevRange }));
845
+ },
846
+ });
893
847
  }
894
848
  }
895
849
  const opts = context.options[0];
@@ -1086,7 +1040,7 @@ const rule$2 = {
1086
1040
  if (shouldCheckType(node.parent.parent)) {
1087
1041
  const name = node.name.value;
1088
1042
  context.report({
1089
- loc: getLocation(node.loc, name),
1043
+ node: node.name,
1090
1044
  message: `Input "${name}" should be called "input"`,
1091
1045
  });
1092
1046
  }
@@ -1108,7 +1062,7 @@ const rule$2 = {
1108
1062
  if ((options.caseSensitiveInputType && node.name.value !== mutationName) ||
1109
1063
  name.toLowerCase() !== mutationName.toLowerCase()) {
1110
1064
  context.report({
1111
- loc: getLocation(node.loc, name),
1065
+ node: node.name,
1112
1066
  message: `InputType "${name}" name should be "${mutationName}"`,
1113
1067
  });
1114
1068
  }
@@ -1122,7 +1076,7 @@ const rule$2 = {
1122
1076
  const MATCH_EXTENSION = 'MATCH_EXTENSION';
1123
1077
  const MATCH_STYLE = 'MATCH_STYLE';
1124
1078
  const ACCEPTED_EXTENSIONS = ['.gql', '.graphql'];
1125
- const CASE_STYLES = ['camelCase', 'PascalCase', 'snake_case', 'UPPER_CASE', 'kebab-case'];
1079
+ const CASE_STYLES = ['camelCase', 'PascalCase', 'snake_case', 'UPPER_CASE', 'kebab-case', 'matchDocumentStyle'];
1126
1080
  const schemaOption = {
1127
1081
  oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
1128
1082
  };
@@ -1296,7 +1250,14 @@ const rule$3 = {
1296
1250
  option = { style: option };
1297
1251
  }
1298
1252
  const expectedExtension = options.fileExtension || fileExtension;
1299
- const expectedFilename = (option.style ? convertCase(option.style, docName) : filename) + (option.suffix || '') + expectedExtension;
1253
+ let expectedFilename;
1254
+ if (option.style) {
1255
+ expectedFilename = option.style === 'matchDocumentStyle' ? docName : convertCase(option.style, docName);
1256
+ }
1257
+ else {
1258
+ expectedFilename = filename;
1259
+ }
1260
+ expectedFilename += (option.suffix || '') + expectedExtension;
1300
1261
  const filenameWithExtension = filename + expectedExtension;
1301
1262
  if (expectedFilename !== filenameWithExtension) {
1302
1263
  context.report({
@@ -1553,7 +1514,7 @@ const rule$4 = {
1553
1514
  const [trailingUnderscores] = nodeName.match(/_*$/);
1554
1515
  const suggestedName = leadingUnderscores + renameToName + trailingUnderscores;
1555
1516
  context.report({
1556
- loc: getLocation(node.loc, node.value),
1517
+ node,
1557
1518
  message: `${nodeType} "${nodeName}" should ${errorMessage}`,
1558
1519
  suggest: [
1559
1520
  {
@@ -1611,7 +1572,7 @@ const rule$4 = {
1611
1572
  const name = node.value;
1612
1573
  const renameToName = name.replace(new RegExp(isLeading ? '^_+' : '_+$'), '');
1613
1574
  context.report({
1614
- loc: getLocation(node.loc, name),
1575
+ node,
1615
1576
  message: `${isLeading ? 'Leading' : 'Trailing'} underscores are not allowed`,
1616
1577
  suggest: [
1617
1578
  {
@@ -1727,7 +1688,7 @@ const rule$6 = {
1727
1688
  for (const duplicate of duplicates) {
1728
1689
  const enumName = duplicate.name.value;
1729
1690
  context.report({
1730
- loc: getLocation(duplicate.loc, enumName),
1691
+ node: duplicate.name,
1731
1692
  message: `Case-insensitive enum values duplicates are not allowed! Found: "${enumName}"`,
1732
1693
  });
1733
1694
  }
@@ -1821,9 +1782,8 @@ const rule$7 = {
1821
1782
  const typeInfo = node.typeInfo();
1822
1783
  if (typeInfo && typeInfo.enumValue) {
1823
1784
  if (typeInfo.enumValue.deprecationReason) {
1824
- const enumValueName = node.value;
1825
1785
  context.report({
1826
- loc: getLocation(node.loc, enumValueName),
1786
+ node,
1827
1787
  messageId: NO_DEPRECATED,
1828
1788
  data: {
1829
1789
  type: 'enum value',
@@ -1838,9 +1798,8 @@ const rule$7 = {
1838
1798
  const typeInfo = node.typeInfo();
1839
1799
  if (typeInfo && typeInfo.fieldDef) {
1840
1800
  if (typeInfo.fieldDef.deprecationReason) {
1841
- const fieldName = node.name.value;
1842
1801
  context.report({
1843
- loc: getLocation(node.loc, fieldName),
1802
+ node: node.name,
1844
1803
  messageId: NO_DEPRECATED,
1845
1804
  data: {
1846
1805
  type: 'field',
@@ -1912,12 +1871,11 @@ const rule$8 = {
1912
1871
  schema: [],
1913
1872
  },
1914
1873
  create(context) {
1915
- function checkNode(usedFields, fieldName, type, node) {
1874
+ function checkNode(usedFields, type, node) {
1875
+ const fieldName = node.value;
1916
1876
  if (usedFields.has(fieldName)) {
1917
1877
  context.report({
1918
- loc: getLocation((node.kind === Kind.FIELD && node.alias ? node.alias : node).loc, fieldName, {
1919
- offsetEnd: node.kind === Kind.VARIABLE_DEFINITION ? 0 : 1,
1920
- }),
1878
+ node,
1921
1879
  messageId: NO_DUPLICATE_FIELDS,
1922
1880
  data: {
1923
1881
  type,
@@ -1933,21 +1891,20 @@ const rule$8 = {
1933
1891
  OperationDefinition(node) {
1934
1892
  const set = new Set();
1935
1893
  for (const varDef of node.variableDefinitions) {
1936
- checkNode(set, varDef.variable.name.value, 'Operation variable', varDef);
1894
+ checkNode(set, 'Operation variable', varDef.variable.name);
1937
1895
  }
1938
1896
  },
1939
1897
  Field(node) {
1940
1898
  const set = new Set();
1941
1899
  for (const arg of node.arguments) {
1942
- checkNode(set, arg.name.value, 'Field argument', arg);
1900
+ checkNode(set, 'Field argument', arg.name);
1943
1901
  }
1944
1902
  },
1945
1903
  SelectionSet(node) {
1946
- var _a;
1947
1904
  const set = new Set();
1948
1905
  for (const selection of node.selections) {
1949
1906
  if (selection.kind === Kind.FIELD) {
1950
- checkNode(set, ((_a = selection.alias) === null || _a === void 0 ? void 0 : _a.value) || selection.name.value, 'Field', selection);
1907
+ checkNode(set, 'Field', selection.alias || selection.name);
1951
1908
  }
1952
1909
  }
1953
1910
  },
@@ -2006,7 +1963,7 @@ const rule$9 = {
2006
1963
  schema: [],
2007
1964
  },
2008
1965
  create(context) {
2009
- const selector = `${Kind.DOCUMENT}[definitions.0.kind!=/^(${Kind.OPERATION_DEFINITION}|${Kind.FRAGMENT_DEFINITION})$/]`;
1966
+ const selector = 'Document[definitions.0.kind!=/^(OperationDefinition|FragmentDefinition)$/]';
2010
1967
  return {
2011
1968
  [selector](node) {
2012
1969
  const rawNode = node.rawNode();
@@ -2019,7 +1976,10 @@ const rule$9 = {
2019
1976
  if (!isEslintComment && line !== prev.line && next.kind === TokenKind.NAME && linesAfter < 2) {
2020
1977
  context.report({
2021
1978
  messageId: HASHTAG_COMMENT,
2022
- loc: getLocation({ start: { line, column } }),
1979
+ loc: {
1980
+ line,
1981
+ column: column - 1,
1982
+ },
2023
1983
  });
2024
1984
  }
2025
1985
  }
@@ -2102,7 +2062,7 @@ const rule$a = {
2102
2062
  [selector](node) {
2103
2063
  const typeName = node.value;
2104
2064
  context.report({
2105
- loc: getLocation(node.loc, typeName),
2065
+ node,
2106
2066
  message: `Root type "${typeName}" is forbidden`,
2107
2067
  });
2108
2068
  },
@@ -2155,7 +2115,7 @@ const rule$b = {
2155
2115
  const graphQLType = schema.getType(typeName);
2156
2116
  if (isScalarType(graphQLType)) {
2157
2117
  context.report({
2158
- loc: getLocation(node.loc, typeName),
2118
+ node,
2159
2119
  message: `Unexpected scalar result type "${typeName}"`,
2160
2120
  });
2161
2121
  }
@@ -2211,7 +2171,7 @@ const rule$c = {
2211
2171
  typeName,
2212
2172
  },
2213
2173
  messageId: NO_TYPENAME_PREFIX,
2214
- loc: getLocation(field.loc, lowerTypeName),
2174
+ node: field.name,
2215
2175
  });
2216
2176
  }
2217
2177
  }
@@ -2289,7 +2249,7 @@ const rule$d = {
2289
2249
  const typeName = node.name.value;
2290
2250
  if (!reachableTypes.has(typeName)) {
2291
2251
  context.report({
2292
- loc: getLocation(node.name.loc, typeName, { offsetStart: node.kind === Kind.DIRECTIVE_DEFINITION ? 2 : 1 }),
2252
+ node: node.name,
2293
2253
  messageId: UNREACHABLE_TYPE,
2294
2254
  data: { typeName },
2295
2255
  suggest: [
@@ -2378,7 +2338,7 @@ const rule$e = {
2378
2338
  return;
2379
2339
  }
2380
2340
  context.report({
2381
- loc: getLocation(node.loc, fieldName),
2341
+ node: node.name,
2382
2342
  messageId: UNUSED_FIELD,
2383
2343
  data: { fieldName },
2384
2344
  suggest: [
@@ -2431,8 +2391,51 @@ function getBaseType(type) {
2431
2391
  }
2432
2392
  return type;
2433
2393
  }
2434
- function convertRange(gqlLocation) {
2435
- return [gqlLocation.start, gqlLocation.end];
2394
+ function convertToken(token, type) {
2395
+ const { line, column, end, start, value } = token;
2396
+ return {
2397
+ type,
2398
+ value,
2399
+ /*
2400
+ * ESLint has 0-based column number
2401
+ * https://eslint.org/docs/developer-guide/working-with-rules#contextreport
2402
+ */
2403
+ loc: {
2404
+ start: {
2405
+ line,
2406
+ column: column - 1,
2407
+ },
2408
+ end: {
2409
+ line,
2410
+ column: column - 1 + (end - start),
2411
+ },
2412
+ },
2413
+ range: [start, end],
2414
+ };
2415
+ }
2416
+ function getLexer(source) {
2417
+ // GraphQL v14
2418
+ const gqlLanguage = require('graphql/language');
2419
+ if (gqlLanguage && gqlLanguage.createLexer) {
2420
+ return gqlLanguage.createLexer(source, {});
2421
+ }
2422
+ // GraphQL v15
2423
+ const { Lexer: LexerCls } = require('graphql');
2424
+ if (LexerCls && typeof LexerCls === 'function') {
2425
+ return new LexerCls(source);
2426
+ }
2427
+ throw new Error('Unsupported GraphQL version! Please make sure to use GraphQL v14 or newer!');
2428
+ }
2429
+ function extractTokens(source) {
2430
+ const lexer = getLexer(source);
2431
+ const tokens = [];
2432
+ let token = lexer.advance();
2433
+ while (token && token.kind !== TokenKind.EOF) {
2434
+ const result = convertToken(token, token.kind);
2435
+ tokens.push(result);
2436
+ token = lexer.advance();
2437
+ }
2438
+ return tokens;
2436
2439
  }
2437
2440
  function extractCommentsFromAst(loc) {
2438
2441
  if (!loc) {
@@ -2441,35 +2444,16 @@ function extractCommentsFromAst(loc) {
2441
2444
  const comments = [];
2442
2445
  let token = loc.startToken;
2443
2446
  while (token !== null) {
2444
- const { kind, value, line, column, start, end, next } = token;
2445
- if (kind === TokenKind.COMMENT) {
2446
- comments.push({
2447
- type: 'Block',
2448
- value,
2449
- loc: {
2450
- start: { line, column },
2451
- end: { line, column },
2452
- },
2453
- range: [start, end],
2454
- });
2447
+ if (token.kind === TokenKind.COMMENT) {
2448
+ const comment = convertToken(token,
2449
+ // `eslint-disable` directive works only with `Block` type comment
2450
+ token.value.trimStart().startsWith('eslint') ? 'Block' : 'Line');
2451
+ comments.push(comment);
2455
2452
  }
2456
- token = next;
2453
+ token = token.next;
2457
2454
  }
2458
2455
  return comments;
2459
2456
  }
2460
- function convertLocation(gqlLocation) {
2461
- return {
2462
- start: {
2463
- column: gqlLocation.startToken.column,
2464
- line: gqlLocation.startToken.line,
2465
- },
2466
- end: {
2467
- column: gqlLocation.endToken.column,
2468
- line: gqlLocation.endToken.line,
2469
- },
2470
- source: gqlLocation.source.body,
2471
- };
2472
- }
2473
2457
  function isNodeWithDescription(obj) {
2474
2458
  var _a;
2475
2459
  return (_a = obj) === null || _a === void 0 ? void 0 : _a.description;
@@ -2555,7 +2539,7 @@ const rule$f = {
2555
2539
  const deletionDateNode = node.arguments.find(arg => arg.name.value === argName);
2556
2540
  if (!deletionDateNode) {
2557
2541
  context.report({
2558
- loc: getLocation(node.loc, node.name.value, { offsetEnd: 0 }),
2542
+ node: node.name,
2559
2543
  messageId: MESSAGE_REQUIRE_DATE,
2560
2544
  });
2561
2545
  return;
@@ -2583,7 +2567,7 @@ const rule$f = {
2583
2567
  const canRemove = Date.now() > deletionDateInMS;
2584
2568
  if (canRemove) {
2585
2569
  context.report({
2586
- node,
2570
+ node: node.parent.name,
2587
2571
  messageId: MESSAGE_CAN_BE_REMOVED,
2588
2572
  data: {
2589
2573
  nodeName: node.parent.name.value,
@@ -2640,7 +2624,7 @@ const rule$g = {
2640
2624
  const value = reasonArg ? String(valueFromNode(reasonArg.value) || '').trim() : null;
2641
2625
  if (!value) {
2642
2626
  context.report({
2643
- loc: getLocation(node.loc, node.name.value, { offsetEnd: 0 }),
2627
+ node: node.name,
2644
2628
  message: 'Directive "@deprecated" must have a reason!',
2645
2629
  });
2646
2630
  }
@@ -2652,12 +2636,40 @@ const rule$g = {
2652
2636
  const RULE_ID$2 = 'require-description';
2653
2637
  const ALLOWED_KINDS$1 = [
2654
2638
  ...TYPES_KINDS,
2639
+ Kind.DIRECTIVE_DEFINITION,
2655
2640
  Kind.FIELD_DEFINITION,
2656
2641
  Kind.INPUT_VALUE_DEFINITION,
2657
2642
  Kind.ENUM_VALUE_DEFINITION,
2658
- Kind.DIRECTIVE_DEFINITION,
2659
2643
  Kind.OPERATION_DEFINITION,
2660
2644
  ];
2645
+ function getNodeName(node) {
2646
+ const DisplayNodeNameMap = {
2647
+ [Kind.OBJECT_TYPE_DEFINITION]: 'type',
2648
+ [Kind.INTERFACE_TYPE_DEFINITION]: 'interface',
2649
+ [Kind.ENUM_TYPE_DEFINITION]: 'enum',
2650
+ [Kind.SCALAR_TYPE_DEFINITION]: 'scalar',
2651
+ [Kind.INPUT_OBJECT_TYPE_DEFINITION]: 'input',
2652
+ [Kind.UNION_TYPE_DEFINITION]: 'union',
2653
+ [Kind.DIRECTIVE_DEFINITION]: 'directive',
2654
+ };
2655
+ switch (node.kind) {
2656
+ case Kind.OBJECT_TYPE_DEFINITION:
2657
+ case Kind.INTERFACE_TYPE_DEFINITION:
2658
+ case Kind.ENUM_TYPE_DEFINITION:
2659
+ case Kind.SCALAR_TYPE_DEFINITION:
2660
+ case Kind.INPUT_OBJECT_TYPE_DEFINITION:
2661
+ case Kind.UNION_TYPE_DEFINITION:
2662
+ return `${DisplayNodeNameMap[node.kind]} ${node.name.value}`;
2663
+ case Kind.DIRECTIVE_DEFINITION:
2664
+ return `${DisplayNodeNameMap[node.kind]} @${node.name.value}`;
2665
+ case Kind.FIELD_DEFINITION:
2666
+ case Kind.INPUT_VALUE_DEFINITION:
2667
+ case Kind.ENUM_VALUE_DEFINITION:
2668
+ return `${node.parent.name.value}.${node.name.value}`;
2669
+ case Kind.OPERATION_DEFINITION:
2670
+ return node.name ? `${node.operation} ${node.name.value}` : node.operation;
2671
+ }
2672
+ }
2661
2673
  const rule$h = {
2662
2674
  meta: {
2663
2675
  docs: {
@@ -2710,7 +2722,7 @@ const rule$h = {
2710
2722
  },
2711
2723
  type: 'suggestion',
2712
2724
  messages: {
2713
- [RULE_ID$2]: 'Description is required for nodes of type "{{ nodeType }}"',
2725
+ [RULE_ID$2]: 'Description is required for `{{ nodeName }}`.',
2714
2726
  },
2715
2727
  schema: {
2716
2728
  type: 'array',
@@ -2769,10 +2781,10 @@ const rule$h = {
2769
2781
  }
2770
2782
  if (description.length === 0) {
2771
2783
  context.report({
2772
- loc: isOperation ? getLocation(node.loc, node.operation) : getLocation(node.name.loc, node.name.value),
2784
+ loc: isOperation ? getLocation(node.loc, node.operation) : node.name.loc,
2773
2785
  messageId: RULE_ID$2,
2774
2786
  data: {
2775
- nodeType: node.kind,
2787
+ nodeName: getNodeName(node),
2776
2788
  },
2777
2789
  });
2778
2790
  }
@@ -2842,7 +2854,7 @@ const rule$i = {
2842
2854
  const hasQueryType = fields.some(field => getTypeName(field) === queryType.name);
2843
2855
  if (!hasQueryType) {
2844
2856
  context.report({
2845
- loc: getLocation(node.loc, typeName),
2857
+ node,
2846
2858
  message: `Mutation result type "${graphQLType.name}" must contain field of type "${queryType.name}"`,
2847
2859
  });
2848
2860
  }
@@ -2862,16 +2874,30 @@ function convertToESTree(node, typeInfo) {
2862
2874
  function hasTypeField(obj) {
2863
2875
  return obj && !!obj.type;
2864
2876
  }
2865
- /**
2866
- * Strips tokens information from `location` object - this is needed since it's created as linked list in GraphQL-JS,
2867
- * causing eslint to fail on circular JSON
2868
- * @param location
2869
- */
2870
- function stripTokens(location) {
2871
- return {
2872
- end: location.end,
2873
- start: location.start,
2877
+ function convertLocation(location) {
2878
+ const { startToken, endToken, source, start, end } = location;
2879
+ /*
2880
+ * ESLint has 0-based column number
2881
+ * https://eslint.org/docs/developer-guide/working-with-rules#contextreport
2882
+ */
2883
+ const loc = {
2884
+ start: {
2885
+ /*
2886
+ * Kind.Document has startToken: { line: 0, column: 0 }, we set line as 1 and column as 0
2887
+ */
2888
+ line: startToken.line === 0 ? 1 : startToken.line,
2889
+ column: startToken.column === 0 ? 0 : startToken.column - 1,
2890
+ },
2891
+ end: {
2892
+ line: endToken.line,
2893
+ column: endToken.column - 1,
2894
+ },
2895
+ source: source.body,
2874
2896
  };
2897
+ if (loc.start.column === loc.end.column) {
2898
+ loc.end.column += end - start;
2899
+ }
2900
+ return loc;
2875
2901
  }
2876
2902
  const convertNode = (typeInfo) => (node, key, parent) => {
2877
2903
  const calculatedTypeInfo = typeInfo
@@ -2891,7 +2917,7 @@ const convertNode = (typeInfo) => (node, key, parent) => {
2891
2917
  typeInfo: () => calculatedTypeInfo,
2892
2918
  leadingComments: convertDescription(node),
2893
2919
  loc: convertLocation(node.loc),
2894
- range: convertRange(node.loc),
2920
+ range: [node.loc.start, node.loc.end],
2895
2921
  };
2896
2922
  if (hasTypeField(node)) {
2897
2923
  const { type: gqlType, loc: gqlLocation, ...rest } = node;
@@ -2916,7 +2942,6 @@ const convertNode = (typeInfo) => (node, key, parent) => {
2916
2942
  }
2917
2943
  return parent[key];
2918
2944
  },
2919
- gqlLocation: stripTokens(gqlLocation),
2920
2945
  };
2921
2946
  return estreeNode;
2922
2947
  }
@@ -2940,7 +2965,6 @@ const convertNode = (typeInfo) => (node, key, parent) => {
2940
2965
  }
2941
2966
  return parent[key];
2942
2967
  },
2943
- gqlLocation: stripTokens(gqlLocation),
2944
2968
  };
2945
2969
  return estreeNode;
2946
2970
  }
@@ -3191,9 +3215,13 @@ const rule$k = {
3191
3215
  };
3192
3216
  checkFn({
3193
3217
  getDocument: () => document,
3194
- reportError: (error) => {
3218
+ reportError(error) {
3219
+ const { line, column } = error.locations[0];
3195
3220
  context.report({
3196
- loc: getLocation({ start: error.locations[0] }),
3221
+ loc: {
3222
+ line,
3223
+ column: column - 1,
3224
+ },
3197
3225
  message: error.message,
3198
3226
  });
3199
3227
  },
@@ -3368,7 +3396,7 @@ const rule$l = {
3368
3396
  // we can extend this rule later.
3369
3397
  if (validIds.length !== 1) {
3370
3398
  context.report({
3371
- loc: getLocation(node.name.loc, typeName),
3399
+ node: node.name,
3372
3400
  messageId: RULE_ID$5,
3373
3401
  data: {
3374
3402
  typeName,
@@ -3404,7 +3432,7 @@ const checkNode = (context, node, ruleName, messageId) => {
3404
3432
  .map(f => `\t${relative(process.cwd(), getOnDiskFilepath(f.filePath))}`)
3405
3433
  .join('\n'),
3406
3434
  },
3407
- loc: getLocation(node.name.loc, documentName),
3435
+ node: node.name,
3408
3436
  });
3409
3437
  }
3410
3438
  };
@@ -3958,6 +3986,15 @@ function parseForESLint(code, options = {}) {
3958
3986
  }
3959
3987
  }
3960
3988
 
3989
+ function indentCode(code, indent = 4) {
3990
+ return code.replace(/^/gm, ' '.repeat(indent));
3991
+ }
3992
+ function printCode(code) {
3993
+ return codeFrameColumns(code, { start: { line: 0, column: 0 } }, {
3994
+ linesAbove: Number.POSITIVE_INFINITY,
3995
+ linesBelow: Number.POSITIVE_INFINITY,
3996
+ });
3997
+ }
3961
3998
  class GraphQLRuleTester extends RuleTester {
3962
3999
  constructor(parserOptions = {}) {
3963
4000
  const config = {
@@ -4013,15 +4050,14 @@ class GraphQLRuleTester extends RuleTester {
4013
4050
  if (message.fatal) {
4014
4051
  throw new Error(message.message);
4015
4052
  }
4016
- messageForSnapshot.push(`Error ${index + 1}/${messages.length}`, visualizeEslintMessage(code, message));
4053
+ messageForSnapshot.push(`❌ Error ${index + 1}/${messages.length}`, visualizeEslintMessage(code, message));
4017
4054
  }
4018
4055
  if (rule.meta.fixable) {
4019
4056
  const { fixed, output } = linter.verifyAndFix(code, verifyConfig, { filename });
4020
4057
  if (fixed) {
4021
- messageForSnapshot.push('Autofix output', dedent(output));
4058
+ messageForSnapshot.push('🔧 Autofix output', indentCode(printCode(output), 2));
4022
4059
  }
4023
4060
  }
4024
- // eslint-disable-next-line no-undef
4025
4061
  expect(messageForSnapshot.join('\n\n')).toMatchSnapshot();
4026
4062
  }
4027
4063
  }
@@ -4075,4 +4111,4 @@ function visualizeEslintMessage(text, result) {
4075
4111
  });
4076
4112
  }
4077
4113
 
4078
- export { GraphQLRuleTester, configs, convertDescription, convertLocation, convertRange, convertToESTree, extractCommentsFromAst, getBaseType, isNodeWithDescription, parse, parseForESLint, processors, rules, valueFromNode };
4114
+ export { GraphQLRuleTester, configs, convertDescription, convertToESTree, convertToken, extractCommentsFromAst, extractTokens, getBaseType, isNodeWithDescription, parse, parseForESLint, processors, rules, valueFromNode };