@graphql-eslint/eslint-plugin 3.7.0 → 3.7.1-alpha-d9a3c78.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
  }
@@ -1098,7 +1082,7 @@ const rule$2 = {
1098
1082
  const MATCH_EXTENSION = 'MATCH_EXTENSION';
1099
1083
  const MATCH_STYLE = 'MATCH_STYLE';
1100
1084
  const ACCEPTED_EXTENSIONS = ['.gql', '.graphql'];
1101
- const CASE_STYLES = ['camelCase', 'PascalCase', 'snake_case', 'UPPER_CASE', 'kebab-case'];
1085
+ const CASE_STYLES = ['camelCase', 'PascalCase', 'snake_case', 'UPPER_CASE', 'kebab-case', 'matchDocumentStyle'];
1102
1086
  const schemaOption = {
1103
1087
  oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
1104
1088
  };
@@ -1272,7 +1256,14 @@ const rule$3 = {
1272
1256
  option = { style: option };
1273
1257
  }
1274
1258
  const expectedExtension = options.fileExtension || fileExtension;
1275
- 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;
1276
1267
  const filenameWithExtension = filename + expectedExtension;
1277
1268
  if (expectedFilename !== filenameWithExtension) {
1278
1269
  context.report({
@@ -1529,7 +1520,7 @@ const rule$4 = {
1529
1520
  const [trailingUnderscores] = nodeName.match(/_*$/);
1530
1521
  const suggestedName = leadingUnderscores + renameToName + trailingUnderscores;
1531
1522
  context.report({
1532
- loc: getLocation(node.loc, node.value),
1523
+ node,
1533
1524
  message: `${nodeType} "${nodeName}" should ${errorMessage}`,
1534
1525
  suggest: [
1535
1526
  {
@@ -1587,7 +1578,7 @@ const rule$4 = {
1587
1578
  const name = node.value;
1588
1579
  const renameToName = name.replace(new RegExp(isLeading ? '^_+' : '_+$'), '');
1589
1580
  context.report({
1590
- loc: getLocation(node.loc, name),
1581
+ node,
1591
1582
  message: `${isLeading ? 'Leading' : 'Trailing'} underscores are not allowed`,
1592
1583
  suggest: [
1593
1584
  {
@@ -1703,7 +1694,7 @@ const rule$6 = {
1703
1694
  for (const duplicate of duplicates) {
1704
1695
  const enumName = duplicate.name.value;
1705
1696
  context.report({
1706
- loc: getLocation(duplicate.loc, enumName),
1697
+ node: duplicate.name,
1707
1698
  message: `Case-insensitive enum values duplicates are not allowed! Found: "${enumName}"`,
1708
1699
  });
1709
1700
  }
@@ -1797,9 +1788,8 @@ const rule$7 = {
1797
1788
  const typeInfo = node.typeInfo();
1798
1789
  if (typeInfo && typeInfo.enumValue) {
1799
1790
  if (typeInfo.enumValue.deprecationReason) {
1800
- const enumValueName = node.value;
1801
1791
  context.report({
1802
- loc: getLocation(node.loc, enumValueName),
1792
+ node,
1803
1793
  messageId: NO_DEPRECATED,
1804
1794
  data: {
1805
1795
  type: 'enum value',
@@ -1814,9 +1804,8 @@ const rule$7 = {
1814
1804
  const typeInfo = node.typeInfo();
1815
1805
  if (typeInfo && typeInfo.fieldDef) {
1816
1806
  if (typeInfo.fieldDef.deprecationReason) {
1817
- const fieldName = node.name.value;
1818
1807
  context.report({
1819
- loc: getLocation(node.loc, fieldName),
1808
+ node: node.name,
1820
1809
  messageId: NO_DEPRECATED,
1821
1810
  data: {
1822
1811
  type: 'field',
@@ -1888,12 +1877,11 @@ const rule$8 = {
1888
1877
  schema: [],
1889
1878
  },
1890
1879
  create(context) {
1891
- function checkNode(usedFields, fieldName, type, node) {
1880
+ function checkNode(usedFields, type, node) {
1881
+ const fieldName = node.value;
1892
1882
  if (usedFields.has(fieldName)) {
1893
1883
  context.report({
1894
- loc: getLocation((node.kind === graphql.Kind.FIELD && node.alias ? node.alias : node).loc, fieldName, {
1895
- offsetEnd: node.kind === graphql.Kind.VARIABLE_DEFINITION ? 0 : 1,
1896
- }),
1884
+ node,
1897
1885
  messageId: NO_DUPLICATE_FIELDS,
1898
1886
  data: {
1899
1887
  type,
@@ -1909,21 +1897,20 @@ const rule$8 = {
1909
1897
  OperationDefinition(node) {
1910
1898
  const set = new Set();
1911
1899
  for (const varDef of node.variableDefinitions) {
1912
- checkNode(set, varDef.variable.name.value, 'Operation variable', varDef);
1900
+ checkNode(set, 'Operation variable', varDef.variable.name);
1913
1901
  }
1914
1902
  },
1915
1903
  Field(node) {
1916
1904
  const set = new Set();
1917
1905
  for (const arg of node.arguments) {
1918
- checkNode(set, arg.name.value, 'Field argument', arg);
1906
+ checkNode(set, 'Field argument', arg.name);
1919
1907
  }
1920
1908
  },
1921
1909
  SelectionSet(node) {
1922
- var _a;
1923
1910
  const set = new Set();
1924
1911
  for (const selection of node.selections) {
1925
1912
  if (selection.kind === graphql.Kind.FIELD) {
1926
- 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);
1927
1914
  }
1928
1915
  }
1929
1916
  },
@@ -1982,7 +1969,7 @@ const rule$9 = {
1982
1969
  schema: [],
1983
1970
  },
1984
1971
  create(context) {
1985
- 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)$/]';
1986
1973
  return {
1987
1974
  [selector](node) {
1988
1975
  const rawNode = node.rawNode();
@@ -1995,7 +1982,10 @@ const rule$9 = {
1995
1982
  if (!isEslintComment && line !== prev.line && next.kind === graphql.TokenKind.NAME && linesAfter < 2) {
1996
1983
  context.report({
1997
1984
  messageId: HASHTAG_COMMENT,
1998
- loc: getLocation({ start: { line, column } }),
1985
+ loc: {
1986
+ line,
1987
+ column: column - 1,
1988
+ },
1999
1989
  });
2000
1990
  }
2001
1991
  }
@@ -2078,7 +2068,7 @@ const rule$a = {
2078
2068
  [selector](node) {
2079
2069
  const typeName = node.value;
2080
2070
  context.report({
2081
- loc: getLocation(node.loc, typeName),
2071
+ node,
2082
2072
  message: `Root type "${typeName}" is forbidden`,
2083
2073
  });
2084
2074
  },
@@ -2131,7 +2121,7 @@ const rule$b = {
2131
2121
  const graphQLType = schema.getType(typeName);
2132
2122
  if (graphql.isScalarType(graphQLType)) {
2133
2123
  context.report({
2134
- loc: getLocation(node.loc, typeName),
2124
+ node,
2135
2125
  message: `Unexpected scalar result type "${typeName}"`,
2136
2126
  });
2137
2127
  }
@@ -2187,7 +2177,7 @@ const rule$c = {
2187
2177
  typeName,
2188
2178
  },
2189
2179
  messageId: NO_TYPENAME_PREFIX,
2190
- loc: getLocation(field.loc, lowerTypeName),
2180
+ node: field.name,
2191
2181
  });
2192
2182
  }
2193
2183
  }
@@ -2265,7 +2255,7 @@ const rule$d = {
2265
2255
  const typeName = node.name.value;
2266
2256
  if (!reachableTypes.has(typeName)) {
2267
2257
  context.report({
2268
- loc: getLocation(node.name.loc, typeName, { offsetStart: node.kind === graphql.Kind.DIRECTIVE_DEFINITION ? 2 : 1 }),
2258
+ node: node.name,
2269
2259
  messageId: UNREACHABLE_TYPE,
2270
2260
  data: { typeName },
2271
2261
  suggest: [
@@ -2354,7 +2344,7 @@ const rule$e = {
2354
2344
  return;
2355
2345
  }
2356
2346
  context.report({
2357
- loc: getLocation(node.loc, fieldName),
2347
+ node: node.name,
2358
2348
  messageId: UNUSED_FIELD,
2359
2349
  data: { fieldName },
2360
2350
  suggest: [
@@ -2407,8 +2397,51 @@ function getBaseType(type) {
2407
2397
  }
2408
2398
  return type;
2409
2399
  }
2410
- function convertRange(gqlLocation) {
2411
- 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;
2412
2445
  }
2413
2446
  function extractCommentsFromAst(loc) {
2414
2447
  if (!loc) {
@@ -2417,35 +2450,16 @@ function extractCommentsFromAst(loc) {
2417
2450
  const comments = [];
2418
2451
  let token = loc.startToken;
2419
2452
  while (token !== null) {
2420
- const { kind, value, line, column, start, end, next } = token;
2421
- if (kind === graphql.TokenKind.COMMENT) {
2422
- comments.push({
2423
- type: 'Block',
2424
- value,
2425
- loc: {
2426
- start: { line, column },
2427
- end: { line, column },
2428
- },
2429
- range: [start, end],
2430
- });
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);
2431
2458
  }
2432
- token = next;
2459
+ token = token.next;
2433
2460
  }
2434
2461
  return comments;
2435
2462
  }
2436
- function convertLocation(gqlLocation) {
2437
- return {
2438
- start: {
2439
- column: gqlLocation.startToken.column,
2440
- line: gqlLocation.startToken.line,
2441
- },
2442
- end: {
2443
- column: gqlLocation.endToken.column,
2444
- line: gqlLocation.endToken.line,
2445
- },
2446
- source: gqlLocation.source.body,
2447
- };
2448
- }
2449
2463
  function isNodeWithDescription(obj) {
2450
2464
  var _a;
2451
2465
  return (_a = obj) === null || _a === void 0 ? void 0 : _a.description;
@@ -2531,7 +2545,7 @@ const rule$f = {
2531
2545
  const deletionDateNode = node.arguments.find(arg => arg.name.value === argName);
2532
2546
  if (!deletionDateNode) {
2533
2547
  context.report({
2534
- loc: getLocation(node.loc, node.name.value, { offsetEnd: 0 }),
2548
+ node: node.name,
2535
2549
  messageId: MESSAGE_REQUIRE_DATE,
2536
2550
  });
2537
2551
  return;
@@ -2559,7 +2573,7 @@ const rule$f = {
2559
2573
  const canRemove = Date.now() > deletionDateInMS;
2560
2574
  if (canRemove) {
2561
2575
  context.report({
2562
- node,
2576
+ node: node.parent.name,
2563
2577
  messageId: MESSAGE_CAN_BE_REMOVED,
2564
2578
  data: {
2565
2579
  nodeName: node.parent.name.value,
@@ -2616,7 +2630,7 @@ const rule$g = {
2616
2630
  const value = reasonArg ? String(valueFromNode(reasonArg.value) || '').trim() : null;
2617
2631
  if (!value) {
2618
2632
  context.report({
2619
- loc: getLocation(node.loc, node.name.value, { offsetEnd: 0 }),
2633
+ node: node.name,
2620
2634
  message: 'Directive "@deprecated" must have a reason!',
2621
2635
  });
2622
2636
  }
@@ -2625,20 +2639,49 @@ const rule$g = {
2625
2639
  },
2626
2640
  };
2627
2641
 
2628
- const REQUIRE_DESCRIPTION_ERROR = 'REQUIRE_DESCRIPTION_ERROR';
2642
+ const RULE_ID$2 = 'require-description';
2629
2643
  const ALLOWED_KINDS$1 = [
2630
2644
  ...TYPES_KINDS,
2645
+ graphql.Kind.DIRECTIVE_DEFINITION,
2631
2646
  graphql.Kind.FIELD_DEFINITION,
2632
2647
  graphql.Kind.INPUT_VALUE_DEFINITION,
2633
2648
  graphql.Kind.ENUM_VALUE_DEFINITION,
2634
- graphql.Kind.DIRECTIVE_DEFINITION,
2649
+ graphql.Kind.OPERATION_DEFINITION,
2635
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
+ }
2636
2679
  const rule$h = {
2637
2680
  meta: {
2638
2681
  docs: {
2639
2682
  category: 'Schema',
2640
- description: 'Enforce descriptions in your type definitions.',
2641
- url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-description.md',
2683
+ description: 'Enforce descriptions in type definitions and operations.',
2684
+ url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$2}.md`,
2642
2685
  examples: [
2643
2686
  {
2644
2687
  title: 'Incorrect',
@@ -2662,6 +2705,16 @@ const rule$h = {
2662
2705
  """
2663
2706
  name: String
2664
2707
  }
2708
+ `,
2709
+ },
2710
+ {
2711
+ title: 'Correct',
2712
+ usage: [{ OperationDefinition: true }],
2713
+ code: /* GraphQL */ `
2714
+ # Create a new user
2715
+ mutation createUser {
2716
+ # ...
2717
+ }
2665
2718
  `,
2666
2719
  },
2667
2720
  ],
@@ -2675,7 +2728,7 @@ const rule$h = {
2675
2728
  },
2676
2729
  type: 'suggestion',
2677
2730
  messages: {
2678
- [REQUIRE_DESCRIPTION_ERROR]: 'Description is required for nodes of type "{{ nodeType }}"',
2731
+ [RULE_ID$2]: 'Description is required for `{{ nodeName }}`.',
2679
2732
  },
2680
2733
  schema: {
2681
2734
  type: 'array',
@@ -2690,19 +2743,19 @@ const rule$h = {
2690
2743
  type: 'boolean',
2691
2744
  description: `Includes:\n\n${TYPES_KINDS.map(kind => `- \`${kind}\``).join('\n')}`,
2692
2745
  },
2693
- ...Object.fromEntries([...ALLOWED_KINDS$1].sort().map(kind => [
2694
- kind,
2695
- {
2696
- type: 'boolean',
2697
- description: `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`,
2698
- },
2699
- ])),
2746
+ ...Object.fromEntries([...ALLOWED_KINDS$1].sort().map(kind => {
2747
+ let description = `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`;
2748
+ if (kind === graphql.Kind.OPERATION_DEFINITION) {
2749
+ description += '\n\n> You must use only comment syntax `#` and not description syntax `"""` or `"`.';
2750
+ }
2751
+ return [kind, { type: 'boolean', description }];
2752
+ })),
2700
2753
  },
2701
2754
  },
2702
2755
  },
2703
2756
  },
2704
2757
  create(context) {
2705
- const { types, ...restOptions } = context.options[0];
2758
+ const { types, ...restOptions } = context.options[0] || {};
2706
2759
  const kinds = new Set(types ? TYPES_KINDS : []);
2707
2760
  for (const [kind, isEnabled] of Object.entries(restOptions)) {
2708
2761
  if (isEnabled) {
@@ -2716,13 +2769,28 @@ const rule$h = {
2716
2769
  return {
2717
2770
  [selector](node) {
2718
2771
  var _a;
2719
- const description = ((_a = node.description) === null || _a === void 0 ? void 0 : _a.value) || '';
2720
- if (description.trim().length === 0) {
2772
+ let description = '';
2773
+ const isOperation = node.kind === graphql.Kind.OPERATION_DEFINITION;
2774
+ if (isOperation) {
2775
+ const rawNode = node.rawNode();
2776
+ const { prev, line } = rawNode.loc.startToken;
2777
+ if (prev.kind === graphql.TokenKind.COMMENT) {
2778
+ const value = prev.value.trim();
2779
+ const linesBefore = line - prev.line;
2780
+ if (!value.startsWith('eslint') && linesBefore === 1) {
2781
+ description = value;
2782
+ }
2783
+ }
2784
+ }
2785
+ else {
2786
+ description = ((_a = node.description) === null || _a === void 0 ? void 0 : _a.value.trim()) || '';
2787
+ }
2788
+ if (description.length === 0) {
2721
2789
  context.report({
2722
- loc: getLocation(node.name.loc, node.name.value),
2723
- messageId: REQUIRE_DESCRIPTION_ERROR,
2790
+ loc: isOperation ? getLocation(node.loc, node.operation) : node.name.loc,
2791
+ messageId: RULE_ID$2,
2724
2792
  data: {
2725
- nodeType: node.kind,
2793
+ nodeName: getNodeName(node),
2726
2794
  },
2727
2795
  });
2728
2796
  }
@@ -2792,7 +2860,7 @@ const rule$i = {
2792
2860
  const hasQueryType = fields.some(field => getTypeName(field) === queryType.name);
2793
2861
  if (!hasQueryType) {
2794
2862
  context.report({
2795
- loc: getLocation(node.loc, typeName),
2863
+ node,
2796
2864
  message: `Mutation result type "${graphQLType.name}" must contain field of type "${queryType.name}"`,
2797
2865
  });
2798
2866
  }
@@ -2812,16 +2880,30 @@ function convertToESTree(node, typeInfo) {
2812
2880
  function hasTypeField(obj) {
2813
2881
  return obj && !!obj.type;
2814
2882
  }
2815
- /**
2816
- * Strips tokens information from `location` object - this is needed since it's created as linked list in GraphQL-JS,
2817
- * causing eslint to fail on circular JSON
2818
- * @param location
2819
- */
2820
- function stripTokens(location) {
2821
- return {
2822
- end: location.end,
2823
- 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,
2824
2902
  };
2903
+ if (loc.start.column === loc.end.column) {
2904
+ loc.end.column += end - start;
2905
+ }
2906
+ return loc;
2825
2907
  }
2826
2908
  const convertNode = (typeInfo) => (node, key, parent) => {
2827
2909
  const calculatedTypeInfo = typeInfo
@@ -2841,7 +2923,7 @@ const convertNode = (typeInfo) => (node, key, parent) => {
2841
2923
  typeInfo: () => calculatedTypeInfo,
2842
2924
  leadingComments: convertDescription(node),
2843
2925
  loc: convertLocation(node.loc),
2844
- range: convertRange(node.loc),
2926
+ range: [node.loc.start, node.loc.end],
2845
2927
  };
2846
2928
  if (hasTypeField(node)) {
2847
2929
  const { type: gqlType, loc: gqlLocation, ...rest } = node;
@@ -2866,7 +2948,6 @@ const convertNode = (typeInfo) => (node, key, parent) => {
2866
2948
  }
2867
2949
  return parent[key];
2868
2950
  },
2869
- gqlLocation: stripTokens(gqlLocation),
2870
2951
  };
2871
2952
  return estreeNode;
2872
2953
  }
@@ -2890,13 +2971,12 @@ const convertNode = (typeInfo) => (node, key, parent) => {
2890
2971
  }
2891
2972
  return parent[key];
2892
2973
  },
2893
- gqlLocation: stripTokens(gqlLocation),
2894
2974
  };
2895
2975
  return estreeNode;
2896
2976
  }
2897
2977
  };
2898
2978
 
2899
- const RULE_ID$2 = 'require-id-when-available';
2979
+ const RULE_ID$3 = 'require-id-when-available';
2900
2980
  const MESSAGE_ID = 'REQUIRE_ID_WHEN_AVAILABLE';
2901
2981
  const DEFAULT_ID_FIELD_NAME = 'id';
2902
2982
  const rule$j = {
@@ -2905,7 +2985,7 @@ const rule$j = {
2905
2985
  docs: {
2906
2986
  category: 'Operations',
2907
2987
  description: 'Enforce selecting specific fields when they are available on the GraphQL type.',
2908
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$2}.md`,
2988
+ url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$3}.md`,
2909
2989
  requiresSchema: true,
2910
2990
  requiresSiblings: true,
2911
2991
  examples: [
@@ -2979,8 +3059,8 @@ const rule$j = {
2979
3059
  },
2980
3060
  },
2981
3061
  create(context) {
2982
- requireGraphQLSchemaFromContext(RULE_ID$2, context);
2983
- const siblings = requireSiblingsOperations(RULE_ID$2, context);
3062
+ requireGraphQLSchemaFromContext(RULE_ID$3, context);
3063
+ const siblings = requireSiblingsOperations(RULE_ID$3, context);
2984
3064
  const { fieldName = DEFAULT_ID_FIELD_NAME } = context.options[0] || {};
2985
3065
  const idNames = utils.asArray(fieldName);
2986
3066
  const isFound = (s) => s.kind === graphql.Kind.FIELD && idNames.includes(s.name.value);
@@ -3042,13 +3122,13 @@ const rule$j = {
3042
3122
  },
3043
3123
  };
3044
3124
 
3045
- const RULE_ID$3 = 'selection-set-depth';
3125
+ const RULE_ID$4 = 'selection-set-depth';
3046
3126
  const rule$k = {
3047
3127
  meta: {
3048
3128
  docs: {
3049
3129
  category: 'Operations',
3050
3130
  description: `Limit the complexity of the GraphQL operations solely by their depth. Based on [graphql-depth-limit](https://github.com/stems/graphql-depth-limit).`,
3051
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$3}.md`,
3131
+ url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$4}.md`,
3052
3132
  requiresSiblings: true,
3053
3133
  examples: [
3054
3134
  {
@@ -3122,10 +3202,10 @@ const rule$k = {
3122
3202
  create(context) {
3123
3203
  let siblings = null;
3124
3204
  try {
3125
- siblings = requireSiblingsOperations(RULE_ID$3, context);
3205
+ siblings = requireSiblingsOperations(RULE_ID$4, context);
3126
3206
  }
3127
3207
  catch (e) {
3128
- logger.warn(`Rule "${RULE_ID$3}" works best with siblings operations loaded. For more info: http://bit.ly/graphql-eslint-operations`);
3208
+ logger.warn(`Rule "${RULE_ID$4}" works best with siblings operations loaded. For more info: http://bit.ly/graphql-eslint-operations`);
3129
3209
  }
3130
3210
  const { maxDepth } = context.options[0];
3131
3211
  const ignore = context.options[0].ignore || [];
@@ -3141,23 +3221,27 @@ const rule$k = {
3141
3221
  };
3142
3222
  checkFn({
3143
3223
  getDocument: () => document,
3144
- reportError: (error) => {
3224
+ reportError(error) {
3225
+ const { line, column } = error.locations[0];
3145
3226
  context.report({
3146
- loc: getLocation({ start: error.locations[0] }),
3227
+ loc: {
3228
+ line,
3229
+ column: column - 1,
3230
+ },
3147
3231
  message: error.message,
3148
3232
  });
3149
3233
  },
3150
3234
  });
3151
3235
  }
3152
3236
  catch (e) {
3153
- logger.warn(`Rule "${RULE_ID$3}" check failed due to a missing siblings operations. For more info: http://bit.ly/graphql-eslint-operations`, e);
3237
+ logger.warn(`Rule "${RULE_ID$4}" check failed due to a missing siblings operations. For more info: http://bit.ly/graphql-eslint-operations`, e);
3154
3238
  }
3155
3239
  },
3156
3240
  };
3157
3241
  },
3158
3242
  };
3159
3243
 
3160
- const RULE_ID$4 = 'strict-id-in-types';
3244
+ const RULE_ID$5 = 'strict-id-in-types';
3161
3245
  const rule$l = {
3162
3246
  meta: {
3163
3247
  type: 'suggestion',
@@ -3165,7 +3249,7 @@ const rule$l = {
3165
3249
  description: `Requires output types to have one unique identifier unless they do not have a logical one. Exceptions can be used to ignore output types that do not have unique identifiers.`,
3166
3250
  category: 'Schema',
3167
3251
  recommended: true,
3168
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$4}.md`,
3252
+ url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$5}.md`,
3169
3253
  requiresSchema: true,
3170
3254
  examples: [
3171
3255
  {
@@ -3279,7 +3363,7 @@ const rule$l = {
3279
3363
  },
3280
3364
  },
3281
3365
  messages: {
3282
- [RULE_ID$4]: `{{ typeName }} must have exactly one non-nullable unique identifier. Accepted name(s): {{ acceptedNamesString }}; Accepted type(s): {{ acceptedTypesString }}.`,
3366
+ [RULE_ID$5]: `{{ typeName }} must have exactly one non-nullable unique identifier. Accepted name(s): {{ acceptedNamesString }}; Accepted type(s): {{ acceptedTypesString }}.`,
3283
3367
  },
3284
3368
  },
3285
3369
  create(context) {
@@ -3289,7 +3373,7 @@ const rule$l = {
3289
3373
  exceptions: {},
3290
3374
  ...context.options[0],
3291
3375
  };
3292
- const schema = requireGraphQLSchemaFromContext(RULE_ID$4, context);
3376
+ const schema = requireGraphQLSchemaFromContext(RULE_ID$5, context);
3293
3377
  const rootTypeNames = [schema.getQueryType(), schema.getMutationType(), schema.getSubscriptionType()]
3294
3378
  .filter(Boolean)
3295
3379
  .map(type => type.name);
@@ -3318,8 +3402,8 @@ const rule$l = {
3318
3402
  // we can extend this rule later.
3319
3403
  if (validIds.length !== 1) {
3320
3404
  context.report({
3321
- loc: getLocation(node.name.loc, typeName),
3322
- messageId: RULE_ID$4,
3405
+ node: node.name,
3406
+ messageId: RULE_ID$5,
3323
3407
  data: {
3324
3408
  typeName,
3325
3409
  acceptedNamesString: options.acceptedIdNames.join(', '),
@@ -3354,7 +3438,7 @@ const checkNode = (context, node, ruleName, messageId) => {
3354
3438
  .map(f => `\t${path.relative(process.cwd(), getOnDiskFilepath(f.filePath))}`)
3355
3439
  .join('\n'),
3356
3440
  },
3357
- loc: getLocation(node.name.loc, documentName),
3441
+ node: node.name,
3358
3442
  });
3359
3443
  }
3360
3444
  };
@@ -3799,7 +3883,8 @@ function getReachableTypes(schema) {
3799
3883
  graphql.visit(astNode, visitor);
3800
3884
  }
3801
3885
  }
3802
- else {
3886
+ else if (type.astNode) {
3887
+ // astNode can be undefined for ID, String, Boolean
3803
3888
  graphql.visit(type.astNode, visitor);
3804
3889
  }
3805
3890
  };
@@ -3818,7 +3903,8 @@ function getReachableTypes(schema) {
3818
3903
  schema.getMutationType(),
3819
3904
  schema.getSubscriptionType(),
3820
3905
  ]) {
3821
- if (type) {
3906
+ // if schema don't have Query type, schema.astNode will be undefined
3907
+ if (type === null || type === void 0 ? void 0 : type.astNode) {
3822
3908
  graphql.visit(type.astNode, visitor);
3823
3909
  }
3824
3910
  }
@@ -3908,6 +3994,15 @@ function parseForESLint(code, options = {}) {
3908
3994
  }
3909
3995
  }
3910
3996
 
3997
+ function indentCode(code, indent = 4) {
3998
+ return code.replace(/^/gm, ' '.repeat(indent));
3999
+ }
4000
+ function printCode(code) {
4001
+ return codeFrame.codeFrameColumns(code, { start: { line: 0, column: 0 } }, {
4002
+ linesAbove: Number.POSITIVE_INFINITY,
4003
+ linesBelow: Number.POSITIVE_INFINITY,
4004
+ });
4005
+ }
3911
4006
  class GraphQLRuleTester extends eslint.RuleTester {
3912
4007
  constructor(parserOptions = {}) {
3913
4008
  const config = {
@@ -3949,19 +4044,29 @@ class GraphQLRuleTester extends eslint.RuleTester {
3949
4044
  }
3950
4045
  const linter = new eslint.Linter();
3951
4046
  linter.defineRule(name, rule);
4047
+ const hasOnlyTest = tests.invalid.some(t => t.only);
3952
4048
  for (const testCase of tests.invalid) {
4049
+ const { only, code, filename } = testCase;
4050
+ if (hasOnlyTest && !only) {
4051
+ continue;
4052
+ }
3953
4053
  const verifyConfig = getVerifyConfig(name, this.config, testCase);
3954
4054
  defineParser(linter, verifyConfig.parser);
3955
- const { code, filename } = testCase;
3956
4055
  const messages = linter.verify(code, verifyConfig, { filename });
3957
- for (const message of messages) {
4056
+ const messageForSnapshot = [];
4057
+ for (const [index, message] of messages.entries()) {
3958
4058
  if (message.fatal) {
3959
4059
  throw new Error(message.message);
3960
4060
  }
3961
- const messageForSnapshot = visualizeEslintMessage(code, message);
3962
- // eslint-disable-next-line no-undef
3963
- expect(messageForSnapshot).toMatchSnapshot();
4061
+ messageForSnapshot.push(`❌ Error ${index + 1}/${messages.length}`, visualizeEslintMessage(code, message));
4062
+ }
4063
+ if (rule.meta.fixable) {
4064
+ const { fixed, output } = linter.verifyAndFix(code, verifyConfig, { filename });
4065
+ if (fixed) {
4066
+ messageForSnapshot.push('🔧 Autofix output', indentCode(printCode(output), 2));
4067
+ }
3964
4068
  }
4069
+ expect(messageForSnapshot.join('\n\n')).toMatchSnapshot();
3965
4070
  }
3966
4071
  }
3967
4072
  }
@@ -4017,10 +4122,10 @@ function visualizeEslintMessage(text, result) {
4017
4122
  exports.GraphQLRuleTester = GraphQLRuleTester;
4018
4123
  exports.configs = configs;
4019
4124
  exports.convertDescription = convertDescription;
4020
- exports.convertLocation = convertLocation;
4021
- exports.convertRange = convertRange;
4022
4125
  exports.convertToESTree = convertToESTree;
4126
+ exports.convertToken = convertToken;
4023
4127
  exports.extractCommentsFromAst = extractCommentsFromAst;
4128
+ exports.extractTokens = extractTokens;
4024
4129
  exports.getBaseType = getBaseType;
4025
4130
  exports.isNodeWithDescription = isNodeWithDescription;
4026
4131
  exports.parse = parse;