@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.mjs CHANGED
@@ -202,43 +202,6 @@ function requireUsedFieldsFromContext(ruleName, context) {
202
202
  const siblings = requireSiblingsOperations(ruleName, context);
203
203
  return context.parserServices.usedFields(schema, siblings);
204
204
  }
205
- function getLexer(source) {
206
- // GraphQL v14
207
- const gqlLanguage = require('graphql/language');
208
- if (gqlLanguage && gqlLanguage.createLexer) {
209
- return gqlLanguage.createLexer(source, {});
210
- }
211
- // GraphQL v15
212
- const { Lexer: LexerCls } = require('graphql');
213
- if (LexerCls && typeof LexerCls === 'function') {
214
- return new LexerCls(source);
215
- }
216
- throw new Error(`Unsupported GraphQL version! Please make sure to use GraphQL v14 or newer!`);
217
- }
218
- function extractTokens(source) {
219
- const lexer = getLexer(source);
220
- const tokens = [];
221
- let token = lexer.advance();
222
- while (token && token.kind !== '<EOF>') {
223
- tokens.push({
224
- type: token.kind,
225
- loc: {
226
- start: {
227
- line: token.line,
228
- column: token.column,
229
- },
230
- end: {
231
- line: token.line,
232
- column: token.column,
233
- },
234
- },
235
- value: token.value,
236
- range: [token.start, token.end],
237
- });
238
- token = lexer.advance();
239
- }
240
- return tokens;
241
- }
242
205
  const normalizePath = (path) => (path || '').replace(/\\/g, '/');
243
206
  /**
244
207
  * https://github.com/prettier/eslint-plugin-prettier/blob/76bd45ece6d56eb52f75db6b4a1efdd2efb56392/eslint-plugin-prettier.js#L71
@@ -308,21 +271,16 @@ const convertCase = (style, str) => {
308
271
  return lowerCase(str).replace(/ /g, '-');
309
272
  }
310
273
  };
311
- function getLocation(loc, fieldName = '', offset) {
312
- const { start } = loc;
313
- /*
314
- * ESLint has 0-based column number
315
- * https://eslint.org/docs/developer-guide/working-with-rules#contextreport
316
- */
317
- const { offsetStart = 1, offsetEnd = 1 } = offset !== null && offset !== void 0 ? offset : {};
274
+ function getLocation(loc, fieldName = '') {
275
+ const { line, column } = loc.start;
318
276
  return {
319
277
  start: {
320
- line: start.line,
321
- column: start.column - offsetStart,
278
+ line,
279
+ column,
322
280
  },
323
281
  end: {
324
- line: start.line,
325
- column: start.column - offsetEnd + fieldName.length,
282
+ line,
283
+ column: column + fieldName.length,
326
284
  },
327
285
  };
328
286
  }
@@ -336,23 +294,16 @@ function validateDocument(context, schema = null, documentNode, rule) {
336
294
  ? validate(schema, documentNode, [rule])
337
295
  : validateSDL(documentNode, null, [rule]);
338
296
  for (const error of validationErrors) {
339
- /*
340
- * TODO: Fix ESTree-AST converter because currently it's incorrectly convert loc.end
341
- * Example: loc.end always equal loc.start
342
- * {
343
- * token: {
344
- * type: 'Name',
345
- * loc: { start: { line: 4, column: 13 }, end: { line: 4, column: 13 } },
346
- * value: 'veryBad',
347
- * range: [ 40, 47 ]
348
- * }
349
- * }
350
- */
351
297
  const { line, column } = error.locations[0];
352
298
  const ancestors = context.getAncestors();
353
- 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);
354
300
  context.report({
355
- 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
+ },
356
307
  message: error.message,
357
308
  });
358
309
  }
@@ -682,9 +633,10 @@ const argumentsEnum = [
682
633
  const rule = {
683
634
  meta: {
684
635
  type: 'suggestion',
636
+ fixable: 'code',
685
637
  docs: {
686
638
  category: ['Schema', 'Operations'],
687
- description: 'Enforce arrange in alphabetical order for type fields, enum values, input object fields, operation selections and more.',
639
+ description: `Enforce arrange in alphabetical order for type fields, enum values, input object fields, operation selections and more.`,
688
640
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/alphabetize.md',
689
641
  examples: [
690
642
  {
@@ -842,24 +794,56 @@ const rule = {
842
794
  },
843
795
  create(context) {
844
796
  var _a, _b, _c, _d, _e;
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]];
818
+ }
845
819
  function checkNodes(nodes) {
846
- let prevName = null;
847
- for (const node of nodes) {
848
- const currName = node.name.value;
849
- if (prevName && prevName > currName) {
850
- const isVariableNode = node.kind === Kind.VARIABLE;
851
- context.report({
852
- loc: getLocation(node.loc, node.name.value, { offsetEnd: isVariableNode ? 0 : 1 }),
853
- messageId: ALPHABETIZE,
854
- data: isVariableNode
855
- ? {
856
- currName: `$${currName}`,
857
- prevName: `$${prevName}`,
858
- }
859
- : { currName, prevName },
860
- });
820
+ // Starts from 1, ignore nodes.length <= 1
821
+ for (let i = 1; i < nodes.length; i += 1) {
822
+ const prevNode = nodes[i - 1];
823
+ const currNode = nodes[i];
824
+ const prevName = prevNode.name.value;
825
+ const currName = currNode.name.value;
826
+ // Compare with lexicographic order
827
+ if (prevName.localeCompare(currName) !== 1) {
828
+ continue;
861
829
  }
862
- prevName = currName;
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
+ });
863
847
  }
864
848
  }
865
849
  const opts = context.options[0];
@@ -1056,7 +1040,7 @@ const rule$2 = {
1056
1040
  if (shouldCheckType(node.parent.parent)) {
1057
1041
  const name = node.name.value;
1058
1042
  context.report({
1059
- loc: getLocation(node.loc, name),
1043
+ node: node.name,
1060
1044
  message: `Input "${name}" should be called "input"`,
1061
1045
  });
1062
1046
  }
@@ -1078,7 +1062,7 @@ const rule$2 = {
1078
1062
  if ((options.caseSensitiveInputType && node.name.value !== mutationName) ||
1079
1063
  name.toLowerCase() !== mutationName.toLowerCase()) {
1080
1064
  context.report({
1081
- loc: getLocation(node.loc, name),
1065
+ node: node.name,
1082
1066
  message: `InputType "${name}" name should be "${mutationName}"`,
1083
1067
  });
1084
1068
  }
@@ -1092,7 +1076,7 @@ const rule$2 = {
1092
1076
  const MATCH_EXTENSION = 'MATCH_EXTENSION';
1093
1077
  const MATCH_STYLE = 'MATCH_STYLE';
1094
1078
  const ACCEPTED_EXTENSIONS = ['.gql', '.graphql'];
1095
- const CASE_STYLES = ['camelCase', 'PascalCase', 'snake_case', 'UPPER_CASE', 'kebab-case'];
1079
+ const CASE_STYLES = ['camelCase', 'PascalCase', 'snake_case', 'UPPER_CASE', 'kebab-case', 'matchDocumentStyle'];
1096
1080
  const schemaOption = {
1097
1081
  oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
1098
1082
  };
@@ -1266,7 +1250,14 @@ const rule$3 = {
1266
1250
  option = { style: option };
1267
1251
  }
1268
1252
  const expectedExtension = options.fileExtension || fileExtension;
1269
- 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;
1270
1261
  const filenameWithExtension = filename + expectedExtension;
1271
1262
  if (expectedFilename !== filenameWithExtension) {
1272
1263
  context.report({
@@ -1523,7 +1514,7 @@ const rule$4 = {
1523
1514
  const [trailingUnderscores] = nodeName.match(/_*$/);
1524
1515
  const suggestedName = leadingUnderscores + renameToName + trailingUnderscores;
1525
1516
  context.report({
1526
- loc: getLocation(node.loc, node.value),
1517
+ node,
1527
1518
  message: `${nodeType} "${nodeName}" should ${errorMessage}`,
1528
1519
  suggest: [
1529
1520
  {
@@ -1581,7 +1572,7 @@ const rule$4 = {
1581
1572
  const name = node.value;
1582
1573
  const renameToName = name.replace(new RegExp(isLeading ? '^_+' : '_+$'), '');
1583
1574
  context.report({
1584
- loc: getLocation(node.loc, name),
1575
+ node,
1585
1576
  message: `${isLeading ? 'Leading' : 'Trailing'} underscores are not allowed`,
1586
1577
  suggest: [
1587
1578
  {
@@ -1697,7 +1688,7 @@ const rule$6 = {
1697
1688
  for (const duplicate of duplicates) {
1698
1689
  const enumName = duplicate.name.value;
1699
1690
  context.report({
1700
- loc: getLocation(duplicate.loc, enumName),
1691
+ node: duplicate.name,
1701
1692
  message: `Case-insensitive enum values duplicates are not allowed! Found: "${enumName}"`,
1702
1693
  });
1703
1694
  }
@@ -1791,9 +1782,8 @@ const rule$7 = {
1791
1782
  const typeInfo = node.typeInfo();
1792
1783
  if (typeInfo && typeInfo.enumValue) {
1793
1784
  if (typeInfo.enumValue.deprecationReason) {
1794
- const enumValueName = node.value;
1795
1785
  context.report({
1796
- loc: getLocation(node.loc, enumValueName),
1786
+ node,
1797
1787
  messageId: NO_DEPRECATED,
1798
1788
  data: {
1799
1789
  type: 'enum value',
@@ -1808,9 +1798,8 @@ const rule$7 = {
1808
1798
  const typeInfo = node.typeInfo();
1809
1799
  if (typeInfo && typeInfo.fieldDef) {
1810
1800
  if (typeInfo.fieldDef.deprecationReason) {
1811
- const fieldName = node.name.value;
1812
1801
  context.report({
1813
- loc: getLocation(node.loc, fieldName),
1802
+ node: node.name,
1814
1803
  messageId: NO_DEPRECATED,
1815
1804
  data: {
1816
1805
  type: 'field',
@@ -1882,12 +1871,11 @@ const rule$8 = {
1882
1871
  schema: [],
1883
1872
  },
1884
1873
  create(context) {
1885
- function checkNode(usedFields, fieldName, type, node) {
1874
+ function checkNode(usedFields, type, node) {
1875
+ const fieldName = node.value;
1886
1876
  if (usedFields.has(fieldName)) {
1887
1877
  context.report({
1888
- loc: getLocation((node.kind === Kind.FIELD && node.alias ? node.alias : node).loc, fieldName, {
1889
- offsetEnd: node.kind === Kind.VARIABLE_DEFINITION ? 0 : 1,
1890
- }),
1878
+ node,
1891
1879
  messageId: NO_DUPLICATE_FIELDS,
1892
1880
  data: {
1893
1881
  type,
@@ -1903,21 +1891,20 @@ const rule$8 = {
1903
1891
  OperationDefinition(node) {
1904
1892
  const set = new Set();
1905
1893
  for (const varDef of node.variableDefinitions) {
1906
- checkNode(set, varDef.variable.name.value, 'Operation variable', varDef);
1894
+ checkNode(set, 'Operation variable', varDef.variable.name);
1907
1895
  }
1908
1896
  },
1909
1897
  Field(node) {
1910
1898
  const set = new Set();
1911
1899
  for (const arg of node.arguments) {
1912
- checkNode(set, arg.name.value, 'Field argument', arg);
1900
+ checkNode(set, 'Field argument', arg.name);
1913
1901
  }
1914
1902
  },
1915
1903
  SelectionSet(node) {
1916
- var _a;
1917
1904
  const set = new Set();
1918
1905
  for (const selection of node.selections) {
1919
1906
  if (selection.kind === Kind.FIELD) {
1920
- 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);
1921
1908
  }
1922
1909
  }
1923
1910
  },
@@ -1976,7 +1963,7 @@ const rule$9 = {
1976
1963
  schema: [],
1977
1964
  },
1978
1965
  create(context) {
1979
- const selector = `${Kind.DOCUMENT}[definitions.0.kind!=/^(${Kind.OPERATION_DEFINITION}|${Kind.FRAGMENT_DEFINITION})$/]`;
1966
+ const selector = 'Document[definitions.0.kind!=/^(OperationDefinition|FragmentDefinition)$/]';
1980
1967
  return {
1981
1968
  [selector](node) {
1982
1969
  const rawNode = node.rawNode();
@@ -1989,7 +1976,10 @@ const rule$9 = {
1989
1976
  if (!isEslintComment && line !== prev.line && next.kind === TokenKind.NAME && linesAfter < 2) {
1990
1977
  context.report({
1991
1978
  messageId: HASHTAG_COMMENT,
1992
- loc: getLocation({ start: { line, column } }),
1979
+ loc: {
1980
+ line,
1981
+ column: column - 1,
1982
+ },
1993
1983
  });
1994
1984
  }
1995
1985
  }
@@ -2072,7 +2062,7 @@ const rule$a = {
2072
2062
  [selector](node) {
2073
2063
  const typeName = node.value;
2074
2064
  context.report({
2075
- loc: getLocation(node.loc, typeName),
2065
+ node,
2076
2066
  message: `Root type "${typeName}" is forbidden`,
2077
2067
  });
2078
2068
  },
@@ -2125,7 +2115,7 @@ const rule$b = {
2125
2115
  const graphQLType = schema.getType(typeName);
2126
2116
  if (isScalarType(graphQLType)) {
2127
2117
  context.report({
2128
- loc: getLocation(node.loc, typeName),
2118
+ node,
2129
2119
  message: `Unexpected scalar result type "${typeName}"`,
2130
2120
  });
2131
2121
  }
@@ -2181,7 +2171,7 @@ const rule$c = {
2181
2171
  typeName,
2182
2172
  },
2183
2173
  messageId: NO_TYPENAME_PREFIX,
2184
- loc: getLocation(field.loc, lowerTypeName),
2174
+ node: field.name,
2185
2175
  });
2186
2176
  }
2187
2177
  }
@@ -2259,7 +2249,7 @@ const rule$d = {
2259
2249
  const typeName = node.name.value;
2260
2250
  if (!reachableTypes.has(typeName)) {
2261
2251
  context.report({
2262
- loc: getLocation(node.name.loc, typeName, { offsetStart: node.kind === Kind.DIRECTIVE_DEFINITION ? 2 : 1 }),
2252
+ node: node.name,
2263
2253
  messageId: UNREACHABLE_TYPE,
2264
2254
  data: { typeName },
2265
2255
  suggest: [
@@ -2348,7 +2338,7 @@ const rule$e = {
2348
2338
  return;
2349
2339
  }
2350
2340
  context.report({
2351
- loc: getLocation(node.loc, fieldName),
2341
+ node: node.name,
2352
2342
  messageId: UNUSED_FIELD,
2353
2343
  data: { fieldName },
2354
2344
  suggest: [
@@ -2401,8 +2391,51 @@ function getBaseType(type) {
2401
2391
  }
2402
2392
  return type;
2403
2393
  }
2404
- function convertRange(gqlLocation) {
2405
- 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;
2406
2439
  }
2407
2440
  function extractCommentsFromAst(loc) {
2408
2441
  if (!loc) {
@@ -2411,35 +2444,16 @@ function extractCommentsFromAst(loc) {
2411
2444
  const comments = [];
2412
2445
  let token = loc.startToken;
2413
2446
  while (token !== null) {
2414
- const { kind, value, line, column, start, end, next } = token;
2415
- if (kind === TokenKind.COMMENT) {
2416
- comments.push({
2417
- type: 'Block',
2418
- value,
2419
- loc: {
2420
- start: { line, column },
2421
- end: { line, column },
2422
- },
2423
- range: [start, end],
2424
- });
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);
2425
2452
  }
2426
- token = next;
2453
+ token = token.next;
2427
2454
  }
2428
2455
  return comments;
2429
2456
  }
2430
- function convertLocation(gqlLocation) {
2431
- return {
2432
- start: {
2433
- column: gqlLocation.startToken.column,
2434
- line: gqlLocation.startToken.line,
2435
- },
2436
- end: {
2437
- column: gqlLocation.endToken.column,
2438
- line: gqlLocation.endToken.line,
2439
- },
2440
- source: gqlLocation.source.body,
2441
- };
2442
- }
2443
2457
  function isNodeWithDescription(obj) {
2444
2458
  var _a;
2445
2459
  return (_a = obj) === null || _a === void 0 ? void 0 : _a.description;
@@ -2525,7 +2539,7 @@ const rule$f = {
2525
2539
  const deletionDateNode = node.arguments.find(arg => arg.name.value === argName);
2526
2540
  if (!deletionDateNode) {
2527
2541
  context.report({
2528
- loc: getLocation(node.loc, node.name.value, { offsetEnd: 0 }),
2542
+ node: node.name,
2529
2543
  messageId: MESSAGE_REQUIRE_DATE,
2530
2544
  });
2531
2545
  return;
@@ -2553,7 +2567,7 @@ const rule$f = {
2553
2567
  const canRemove = Date.now() > deletionDateInMS;
2554
2568
  if (canRemove) {
2555
2569
  context.report({
2556
- node,
2570
+ node: node.parent.name,
2557
2571
  messageId: MESSAGE_CAN_BE_REMOVED,
2558
2572
  data: {
2559
2573
  nodeName: node.parent.name.value,
@@ -2610,7 +2624,7 @@ const rule$g = {
2610
2624
  const value = reasonArg ? String(valueFromNode(reasonArg.value) || '').trim() : null;
2611
2625
  if (!value) {
2612
2626
  context.report({
2613
- loc: getLocation(node.loc, node.name.value, { offsetEnd: 0 }),
2627
+ node: node.name,
2614
2628
  message: 'Directive "@deprecated" must have a reason!',
2615
2629
  });
2616
2630
  }
@@ -2619,20 +2633,49 @@ const rule$g = {
2619
2633
  },
2620
2634
  };
2621
2635
 
2622
- const REQUIRE_DESCRIPTION_ERROR = 'REQUIRE_DESCRIPTION_ERROR';
2636
+ const RULE_ID$2 = 'require-description';
2623
2637
  const ALLOWED_KINDS$1 = [
2624
2638
  ...TYPES_KINDS,
2639
+ Kind.DIRECTIVE_DEFINITION,
2625
2640
  Kind.FIELD_DEFINITION,
2626
2641
  Kind.INPUT_VALUE_DEFINITION,
2627
2642
  Kind.ENUM_VALUE_DEFINITION,
2628
- Kind.DIRECTIVE_DEFINITION,
2643
+ Kind.OPERATION_DEFINITION,
2629
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
+ }
2630
2673
  const rule$h = {
2631
2674
  meta: {
2632
2675
  docs: {
2633
2676
  category: 'Schema',
2634
- description: 'Enforce descriptions in your type definitions.',
2635
- url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/require-description.md',
2677
+ description: 'Enforce descriptions in type definitions and operations.',
2678
+ url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$2}.md`,
2636
2679
  examples: [
2637
2680
  {
2638
2681
  title: 'Incorrect',
@@ -2656,6 +2699,16 @@ const rule$h = {
2656
2699
  """
2657
2700
  name: String
2658
2701
  }
2702
+ `,
2703
+ },
2704
+ {
2705
+ title: 'Correct',
2706
+ usage: [{ OperationDefinition: true }],
2707
+ code: /* GraphQL */ `
2708
+ # Create a new user
2709
+ mutation createUser {
2710
+ # ...
2711
+ }
2659
2712
  `,
2660
2713
  },
2661
2714
  ],
@@ -2669,7 +2722,7 @@ const rule$h = {
2669
2722
  },
2670
2723
  type: 'suggestion',
2671
2724
  messages: {
2672
- [REQUIRE_DESCRIPTION_ERROR]: 'Description is required for nodes of type "{{ nodeType }}"',
2725
+ [RULE_ID$2]: 'Description is required for `{{ nodeName }}`.',
2673
2726
  },
2674
2727
  schema: {
2675
2728
  type: 'array',
@@ -2684,19 +2737,19 @@ const rule$h = {
2684
2737
  type: 'boolean',
2685
2738
  description: `Includes:\n\n${TYPES_KINDS.map(kind => `- \`${kind}\``).join('\n')}`,
2686
2739
  },
2687
- ...Object.fromEntries([...ALLOWED_KINDS$1].sort().map(kind => [
2688
- kind,
2689
- {
2690
- type: 'boolean',
2691
- description: `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`,
2692
- },
2693
- ])),
2740
+ ...Object.fromEntries([...ALLOWED_KINDS$1].sort().map(kind => {
2741
+ let description = `Read more about this kind on [spec.graphql.org](https://spec.graphql.org/October2021/#${kind}).`;
2742
+ if (kind === Kind.OPERATION_DEFINITION) {
2743
+ description += '\n\n> You must use only comment syntax `#` and not description syntax `"""` or `"`.';
2744
+ }
2745
+ return [kind, { type: 'boolean', description }];
2746
+ })),
2694
2747
  },
2695
2748
  },
2696
2749
  },
2697
2750
  },
2698
2751
  create(context) {
2699
- const { types, ...restOptions } = context.options[0];
2752
+ const { types, ...restOptions } = context.options[0] || {};
2700
2753
  const kinds = new Set(types ? TYPES_KINDS : []);
2701
2754
  for (const [kind, isEnabled] of Object.entries(restOptions)) {
2702
2755
  if (isEnabled) {
@@ -2710,13 +2763,28 @@ const rule$h = {
2710
2763
  return {
2711
2764
  [selector](node) {
2712
2765
  var _a;
2713
- const description = ((_a = node.description) === null || _a === void 0 ? void 0 : _a.value) || '';
2714
- if (description.trim().length === 0) {
2766
+ let description = '';
2767
+ const isOperation = node.kind === Kind.OPERATION_DEFINITION;
2768
+ if (isOperation) {
2769
+ const rawNode = node.rawNode();
2770
+ const { prev, line } = rawNode.loc.startToken;
2771
+ if (prev.kind === TokenKind.COMMENT) {
2772
+ const value = prev.value.trim();
2773
+ const linesBefore = line - prev.line;
2774
+ if (!value.startsWith('eslint') && linesBefore === 1) {
2775
+ description = value;
2776
+ }
2777
+ }
2778
+ }
2779
+ else {
2780
+ description = ((_a = node.description) === null || _a === void 0 ? void 0 : _a.value.trim()) || '';
2781
+ }
2782
+ if (description.length === 0) {
2715
2783
  context.report({
2716
- loc: getLocation(node.name.loc, node.name.value),
2717
- messageId: REQUIRE_DESCRIPTION_ERROR,
2784
+ loc: isOperation ? getLocation(node.loc, node.operation) : node.name.loc,
2785
+ messageId: RULE_ID$2,
2718
2786
  data: {
2719
- nodeType: node.kind,
2787
+ nodeName: getNodeName(node),
2720
2788
  },
2721
2789
  });
2722
2790
  }
@@ -2786,7 +2854,7 @@ const rule$i = {
2786
2854
  const hasQueryType = fields.some(field => getTypeName(field) === queryType.name);
2787
2855
  if (!hasQueryType) {
2788
2856
  context.report({
2789
- loc: getLocation(node.loc, typeName),
2857
+ node,
2790
2858
  message: `Mutation result type "${graphQLType.name}" must contain field of type "${queryType.name}"`,
2791
2859
  });
2792
2860
  }
@@ -2806,16 +2874,30 @@ function convertToESTree(node, typeInfo) {
2806
2874
  function hasTypeField(obj) {
2807
2875
  return obj && !!obj.type;
2808
2876
  }
2809
- /**
2810
- * Strips tokens information from `location` object - this is needed since it's created as linked list in GraphQL-JS,
2811
- * causing eslint to fail on circular JSON
2812
- * @param location
2813
- */
2814
- function stripTokens(location) {
2815
- return {
2816
- end: location.end,
2817
- 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,
2818
2896
  };
2897
+ if (loc.start.column === loc.end.column) {
2898
+ loc.end.column += end - start;
2899
+ }
2900
+ return loc;
2819
2901
  }
2820
2902
  const convertNode = (typeInfo) => (node, key, parent) => {
2821
2903
  const calculatedTypeInfo = typeInfo
@@ -2835,7 +2917,7 @@ const convertNode = (typeInfo) => (node, key, parent) => {
2835
2917
  typeInfo: () => calculatedTypeInfo,
2836
2918
  leadingComments: convertDescription(node),
2837
2919
  loc: convertLocation(node.loc),
2838
- range: convertRange(node.loc),
2920
+ range: [node.loc.start, node.loc.end],
2839
2921
  };
2840
2922
  if (hasTypeField(node)) {
2841
2923
  const { type: gqlType, loc: gqlLocation, ...rest } = node;
@@ -2860,7 +2942,6 @@ const convertNode = (typeInfo) => (node, key, parent) => {
2860
2942
  }
2861
2943
  return parent[key];
2862
2944
  },
2863
- gqlLocation: stripTokens(gqlLocation),
2864
2945
  };
2865
2946
  return estreeNode;
2866
2947
  }
@@ -2884,13 +2965,12 @@ const convertNode = (typeInfo) => (node, key, parent) => {
2884
2965
  }
2885
2966
  return parent[key];
2886
2967
  },
2887
- gqlLocation: stripTokens(gqlLocation),
2888
2968
  };
2889
2969
  return estreeNode;
2890
2970
  }
2891
2971
  };
2892
2972
 
2893
- const RULE_ID$2 = 'require-id-when-available';
2973
+ const RULE_ID$3 = 'require-id-when-available';
2894
2974
  const MESSAGE_ID = 'REQUIRE_ID_WHEN_AVAILABLE';
2895
2975
  const DEFAULT_ID_FIELD_NAME = 'id';
2896
2976
  const rule$j = {
@@ -2899,7 +2979,7 @@ const rule$j = {
2899
2979
  docs: {
2900
2980
  category: 'Operations',
2901
2981
  description: 'Enforce selecting specific fields when they are available on the GraphQL type.',
2902
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$2}.md`,
2982
+ url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$3}.md`,
2903
2983
  requiresSchema: true,
2904
2984
  requiresSiblings: true,
2905
2985
  examples: [
@@ -2973,8 +3053,8 @@ const rule$j = {
2973
3053
  },
2974
3054
  },
2975
3055
  create(context) {
2976
- requireGraphQLSchemaFromContext(RULE_ID$2, context);
2977
- const siblings = requireSiblingsOperations(RULE_ID$2, context);
3056
+ requireGraphQLSchemaFromContext(RULE_ID$3, context);
3057
+ const siblings = requireSiblingsOperations(RULE_ID$3, context);
2978
3058
  const { fieldName = DEFAULT_ID_FIELD_NAME } = context.options[0] || {};
2979
3059
  const idNames = asArray(fieldName);
2980
3060
  const isFound = (s) => s.kind === Kind.FIELD && idNames.includes(s.name.value);
@@ -3036,13 +3116,13 @@ const rule$j = {
3036
3116
  },
3037
3117
  };
3038
3118
 
3039
- const RULE_ID$3 = 'selection-set-depth';
3119
+ const RULE_ID$4 = 'selection-set-depth';
3040
3120
  const rule$k = {
3041
3121
  meta: {
3042
3122
  docs: {
3043
3123
  category: 'Operations',
3044
3124
  description: `Limit the complexity of the GraphQL operations solely by their depth. Based on [graphql-depth-limit](https://github.com/stems/graphql-depth-limit).`,
3045
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$3}.md`,
3125
+ url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$4}.md`,
3046
3126
  requiresSiblings: true,
3047
3127
  examples: [
3048
3128
  {
@@ -3116,10 +3196,10 @@ const rule$k = {
3116
3196
  create(context) {
3117
3197
  let siblings = null;
3118
3198
  try {
3119
- siblings = requireSiblingsOperations(RULE_ID$3, context);
3199
+ siblings = requireSiblingsOperations(RULE_ID$4, context);
3120
3200
  }
3121
3201
  catch (e) {
3122
- logger.warn(`Rule "${RULE_ID$3}" works best with siblings operations loaded. For more info: http://bit.ly/graphql-eslint-operations`);
3202
+ logger.warn(`Rule "${RULE_ID$4}" works best with siblings operations loaded. For more info: http://bit.ly/graphql-eslint-operations`);
3123
3203
  }
3124
3204
  const { maxDepth } = context.options[0];
3125
3205
  const ignore = context.options[0].ignore || [];
@@ -3135,23 +3215,27 @@ const rule$k = {
3135
3215
  };
3136
3216
  checkFn({
3137
3217
  getDocument: () => document,
3138
- reportError: (error) => {
3218
+ reportError(error) {
3219
+ const { line, column } = error.locations[0];
3139
3220
  context.report({
3140
- loc: getLocation({ start: error.locations[0] }),
3221
+ loc: {
3222
+ line,
3223
+ column: column - 1,
3224
+ },
3141
3225
  message: error.message,
3142
3226
  });
3143
3227
  },
3144
3228
  });
3145
3229
  }
3146
3230
  catch (e) {
3147
- logger.warn(`Rule "${RULE_ID$3}" check failed due to a missing siblings operations. For more info: http://bit.ly/graphql-eslint-operations`, e);
3231
+ logger.warn(`Rule "${RULE_ID$4}" check failed due to a missing siblings operations. For more info: http://bit.ly/graphql-eslint-operations`, e);
3148
3232
  }
3149
3233
  },
3150
3234
  };
3151
3235
  },
3152
3236
  };
3153
3237
 
3154
- const RULE_ID$4 = 'strict-id-in-types';
3238
+ const RULE_ID$5 = 'strict-id-in-types';
3155
3239
  const rule$l = {
3156
3240
  meta: {
3157
3241
  type: 'suggestion',
@@ -3159,7 +3243,7 @@ const rule$l = {
3159
3243
  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.`,
3160
3244
  category: 'Schema',
3161
3245
  recommended: true,
3162
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$4}.md`,
3246
+ url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$5}.md`,
3163
3247
  requiresSchema: true,
3164
3248
  examples: [
3165
3249
  {
@@ -3273,7 +3357,7 @@ const rule$l = {
3273
3357
  },
3274
3358
  },
3275
3359
  messages: {
3276
- [RULE_ID$4]: `{{ typeName }} must have exactly one non-nullable unique identifier. Accepted name(s): {{ acceptedNamesString }}; Accepted type(s): {{ acceptedTypesString }}.`,
3360
+ [RULE_ID$5]: `{{ typeName }} must have exactly one non-nullable unique identifier. Accepted name(s): {{ acceptedNamesString }}; Accepted type(s): {{ acceptedTypesString }}.`,
3277
3361
  },
3278
3362
  },
3279
3363
  create(context) {
@@ -3283,7 +3367,7 @@ const rule$l = {
3283
3367
  exceptions: {},
3284
3368
  ...context.options[0],
3285
3369
  };
3286
- const schema = requireGraphQLSchemaFromContext(RULE_ID$4, context);
3370
+ const schema = requireGraphQLSchemaFromContext(RULE_ID$5, context);
3287
3371
  const rootTypeNames = [schema.getQueryType(), schema.getMutationType(), schema.getSubscriptionType()]
3288
3372
  .filter(Boolean)
3289
3373
  .map(type => type.name);
@@ -3312,8 +3396,8 @@ const rule$l = {
3312
3396
  // we can extend this rule later.
3313
3397
  if (validIds.length !== 1) {
3314
3398
  context.report({
3315
- loc: getLocation(node.name.loc, typeName),
3316
- messageId: RULE_ID$4,
3399
+ node: node.name,
3400
+ messageId: RULE_ID$5,
3317
3401
  data: {
3318
3402
  typeName,
3319
3403
  acceptedNamesString: options.acceptedIdNames.join(', '),
@@ -3348,7 +3432,7 @@ const checkNode = (context, node, ruleName, messageId) => {
3348
3432
  .map(f => `\t${relative(process.cwd(), getOnDiskFilepath(f.filePath))}`)
3349
3433
  .join('\n'),
3350
3434
  },
3351
- loc: getLocation(node.name.loc, documentName),
3435
+ node: node.name,
3352
3436
  });
3353
3437
  }
3354
3438
  };
@@ -3793,7 +3877,8 @@ function getReachableTypes(schema) {
3793
3877
  visit(astNode, visitor);
3794
3878
  }
3795
3879
  }
3796
- else {
3880
+ else if (type.astNode) {
3881
+ // astNode can be undefined for ID, String, Boolean
3797
3882
  visit(type.astNode, visitor);
3798
3883
  }
3799
3884
  };
@@ -3812,7 +3897,8 @@ function getReachableTypes(schema) {
3812
3897
  schema.getMutationType(),
3813
3898
  schema.getSubscriptionType(),
3814
3899
  ]) {
3815
- if (type) {
3900
+ // if schema don't have Query type, schema.astNode will be undefined
3901
+ if (type === null || type === void 0 ? void 0 : type.astNode) {
3816
3902
  visit(type.astNode, visitor);
3817
3903
  }
3818
3904
  }
@@ -3902,6 +3988,15 @@ function parseForESLint(code, options = {}) {
3902
3988
  }
3903
3989
  }
3904
3990
 
3991
+ function indentCode(code, indent = 4) {
3992
+ return code.replace(/^/gm, ' '.repeat(indent));
3993
+ }
3994
+ function printCode(code) {
3995
+ return codeFrameColumns(code, { start: { line: 0, column: 0 } }, {
3996
+ linesAbove: Number.POSITIVE_INFINITY,
3997
+ linesBelow: Number.POSITIVE_INFINITY,
3998
+ });
3999
+ }
3905
4000
  class GraphQLRuleTester extends RuleTester {
3906
4001
  constructor(parserOptions = {}) {
3907
4002
  const config = {
@@ -3943,19 +4038,29 @@ class GraphQLRuleTester extends RuleTester {
3943
4038
  }
3944
4039
  const linter = new Linter();
3945
4040
  linter.defineRule(name, rule);
4041
+ const hasOnlyTest = tests.invalid.some(t => t.only);
3946
4042
  for (const testCase of tests.invalid) {
4043
+ const { only, code, filename } = testCase;
4044
+ if (hasOnlyTest && !only) {
4045
+ continue;
4046
+ }
3947
4047
  const verifyConfig = getVerifyConfig(name, this.config, testCase);
3948
4048
  defineParser(linter, verifyConfig.parser);
3949
- const { code, filename } = testCase;
3950
4049
  const messages = linter.verify(code, verifyConfig, { filename });
3951
- for (const message of messages) {
4050
+ const messageForSnapshot = [];
4051
+ for (const [index, message] of messages.entries()) {
3952
4052
  if (message.fatal) {
3953
4053
  throw new Error(message.message);
3954
4054
  }
3955
- const messageForSnapshot = visualizeEslintMessage(code, message);
3956
- // eslint-disable-next-line no-undef
3957
- expect(messageForSnapshot).toMatchSnapshot();
4055
+ messageForSnapshot.push(`❌ Error ${index + 1}/${messages.length}`, visualizeEslintMessage(code, message));
4056
+ }
4057
+ if (rule.meta.fixable) {
4058
+ const { fixed, output } = linter.verifyAndFix(code, verifyConfig, { filename });
4059
+ if (fixed) {
4060
+ messageForSnapshot.push('🔧 Autofix output', indentCode(printCode(output), 2));
4061
+ }
3958
4062
  }
4063
+ expect(messageForSnapshot.join('\n\n')).toMatchSnapshot();
3959
4064
  }
3960
4065
  }
3961
4066
  }
@@ -4008,4 +4113,4 @@ function visualizeEslintMessage(text, result) {
4008
4113
  });
4009
4114
  }
4010
4115
 
4011
- export { GraphQLRuleTester, configs, convertDescription, convertLocation, convertRange, convertToESTree, extractCommentsFromAst, getBaseType, isNodeWithDescription, parse, parseForESLint, processors, rules, valueFromNode };
4116
+ export { GraphQLRuleTester, configs, convertDescription, convertToESTree, convertToken, extractCommentsFromAst, extractTokens, getBaseType, isNodeWithDescription, parse, parseForESLint, processors, rules, valueFromNode };