@graphql-eslint/eslint-plugin 2.4.0-alpha-702309a.0 → 2.4.0-alpha-a3f52b6.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
@@ -9,6 +9,8 @@ import depthLimit from 'graphql-depth-limit';
9
9
  import { parseCode } from '@graphql-tools/graphql-tag-pluck';
10
10
  import { loadConfigSync, GraphQLConfig } from 'graphql-config';
11
11
  import { CodeFileLoader } from '@graphql-tools/code-file-loader';
12
+ import { RuleTester, Linter } from 'eslint';
13
+ import { codeFrameColumns } from '@babel/code-frame';
12
14
 
13
15
  /*
14
16
  * 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
@@ -236,6 +238,24 @@ const convertCase = (style, str) => {
236
238
  return lowerCase(str).replace(/ /g, '-');
237
239
  }
238
240
  };
241
+ function getLocation(loc, fieldName = '', offset) {
242
+ const { start } = loc;
243
+ /*
244
+ * ESLint has 0-based column number
245
+ * https://eslint.org/docs/developer-guide/working-with-rules#contextreport
246
+ */
247
+ const { offsetStart = 1, offsetEnd = 1 } = offset !== null && offset !== void 0 ? offset : {};
248
+ return {
249
+ start: {
250
+ line: start.line,
251
+ column: start.column - offsetStart,
252
+ },
253
+ end: {
254
+ line: start.line,
255
+ column: start.column - offsetEnd + fieldName.length,
256
+ },
257
+ };
258
+ }
239
259
 
240
260
  function extractRuleName(stack) {
241
261
  const match = (stack || '').match(/validation[/\\\\]rules[/\\\\](.*?)\.js:/) || [];
@@ -249,7 +269,7 @@ function validateDoc(sourceNode, context, schema, documentNode, rules, ruleName
249
269
  for (const error of validationErrors) {
250
270
  const validateRuleName = ruleName || `[${extractRuleName(error.stack)}]`;
251
271
  context.report({
252
- loc: error.locations[0],
272
+ loc: getLocation({ start: error.locations[0] }),
253
273
  message: ruleName ? error.message : `${validateRuleName} ${error.message}`,
254
274
  });
255
275
  }
@@ -286,6 +306,7 @@ const validationToRule = (name, ruleName, docs, getDocumentNode) => {
286
306
  meta: {
287
307
  docs: {
288
308
  ...docs,
309
+ graphQLJSRuleName: ruleName,
289
310
  category: 'Validation',
290
311
  recommended: true,
291
312
  requiresSchema,
@@ -585,7 +606,7 @@ const rule = {
585
606
  ],
586
607
  },
587
608
  messages: {
588
- [ALPHABETIZE]: '"{{ currName }}" should be before "{{ prevName }}".',
609
+ [ALPHABETIZE]: '"{{ currName }}" should be before "{{ prevName }}"',
589
610
  },
590
611
  schema: {
591
612
  type: 'array',
@@ -642,16 +663,9 @@ const rule = {
642
663
  for (const node of nodes) {
643
664
  const currName = node.name.value;
644
665
  if (prevName && prevName > currName) {
645
- const { start, end } = node.name.loc;
646
666
  const isVariableNode = node.kind === Kind.VARIABLE;
647
667
  context.report({
648
- loc: {
649
- start: {
650
- line: start.line,
651
- column: start.column - (isVariableNode ? 2 : 1),
652
- },
653
- end,
654
- },
668
+ loc: getLocation(node.loc, node.name.value, { offsetEnd: isVariableNode ? 0 : 1 }),
655
669
  messageId: ALPHABETIZE,
656
670
  data: isVariableNode
657
671
  ? {
@@ -717,35 +731,22 @@ const rule = {
717
731
  };
718
732
 
719
733
  const AVOID_DUPLICATE_FIELDS = 'AVOID_DUPLICATE_FIELDS';
720
- const ensureUnique = () => {
721
- const set = new Set();
722
- return {
723
- add: (item, onError) => {
724
- if (set.has(item)) {
725
- onError();
726
- }
727
- else {
728
- set.add(item);
729
- }
730
- },
731
- };
732
- };
733
734
  const rule$1 = {
734
735
  meta: {
735
736
  type: 'suggestion',
736
737
  docs: {
737
- description: 'Checks for duplicate fields in selection set, variables in operation definition, or in arguments set of a field.',
738
+ description: `Checks for duplicate fields in selection set, variables in operation definition, or in arguments set of a field.`,
738
739
  category: 'Stylistic Issues',
739
740
  url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/avoid-duplicate-fields.md',
740
741
  examples: [
741
742
  {
742
743
  title: 'Incorrect',
743
744
  code: /* GraphQL */ `
744
- query getUserDetails {
745
+ query {
745
746
  user {
746
- name # first
747
+ name
747
748
  email
748
- name # second
749
+ name # duplicate field
749
750
  }
750
751
  }
751
752
  `,
@@ -753,7 +754,7 @@ const rule$1 = {
753
754
  {
754
755
  title: 'Incorrect',
755
756
  code: /* GraphQL */ `
756
- query getUsers {
757
+ query {
757
758
  users(
758
759
  first: 100
759
760
  skip: 50
@@ -768,9 +769,11 @@ const rule$1 = {
768
769
  {
769
770
  title: 'Incorrect',
770
771
  code: /* GraphQL */ `
771
- query getUsers($first: Int!, $first: Int!) {
772
- # Duplicate variable
773
- users(first: 100, skip: 50, after: "cji629tngfgou0b73kt7vi5jo") {
772
+ query (
773
+ $first: Int!
774
+ $first: Int! # duplicate variable
775
+ ) {
776
+ users(first: $first, skip: 50) {
774
777
  id
775
778
  }
776
779
  }
@@ -779,58 +782,47 @@ const rule$1 = {
779
782
  ],
780
783
  },
781
784
  messages: {
782
- [AVOID_DUPLICATE_FIELDS]: `{{ type }} "{{ fieldName }}" defined multiple times.`,
785
+ [AVOID_DUPLICATE_FIELDS]: `{{ type }} "{{ fieldName }}" defined multiple times`,
783
786
  },
784
787
  schema: [],
785
788
  },
786
789
  create(context) {
790
+ function checkNode(usedFields, fieldName, type, node) {
791
+ if (usedFields.has(fieldName)) {
792
+ context.report({
793
+ loc: getLocation((node.kind === Kind.FIELD && node.alias ? node.alias : node).loc, fieldName, {
794
+ offsetEnd: node.kind === Kind.VARIABLE_DEFINITION ? 0 : 1,
795
+ }),
796
+ messageId: AVOID_DUPLICATE_FIELDS,
797
+ data: {
798
+ type,
799
+ fieldName,
800
+ },
801
+ });
802
+ }
803
+ else {
804
+ usedFields.add(fieldName);
805
+ }
806
+ }
787
807
  return {
788
808
  OperationDefinition(node) {
789
- const uniqueCheck = ensureUnique();
790
- for (const arg of node.variableDefinitions || []) {
791
- uniqueCheck.add(arg.variable.name.value, () => {
792
- context.report({
793
- messageId: AVOID_DUPLICATE_FIELDS,
794
- data: {
795
- type: 'Operation variable',
796
- fieldName: arg.variable.name.value,
797
- },
798
- node: arg,
799
- });
800
- });
809
+ const set = new Set();
810
+ for (const varDef of node.variableDefinitions) {
811
+ checkNode(set, varDef.variable.name.value, 'Operation variable', varDef);
801
812
  }
802
813
  },
803
814
  Field(node) {
804
- const uniqueCheck = ensureUnique();
805
- for (const arg of node.arguments || []) {
806
- uniqueCheck.add(arg.name.value, () => {
807
- context.report({
808
- messageId: AVOID_DUPLICATE_FIELDS,
809
- data: {
810
- type: 'Field argument',
811
- fieldName: arg.name.value,
812
- },
813
- node: arg,
814
- });
815
- });
815
+ const set = new Set();
816
+ for (const arg of node.arguments) {
817
+ checkNode(set, arg.name.value, 'Field argument', arg);
816
818
  }
817
819
  },
818
820
  SelectionSet(node) {
819
821
  var _a;
820
- const uniqueCheck = ensureUnique();
821
- for (const selection of node.selections || []) {
822
+ const set = new Set();
823
+ for (const selection of node.selections) {
822
824
  if (selection.kind === Kind.FIELD) {
823
- const nameToCheck = ((_a = selection.alias) === null || _a === void 0 ? void 0 : _a.value) || selection.name.value;
824
- uniqueCheck.add(nameToCheck, () => {
825
- context.report({
826
- messageId: AVOID_DUPLICATE_FIELDS,
827
- data: {
828
- type: 'Field',
829
- fieldName: nameToCheck,
830
- },
831
- node: selection,
832
- });
833
- });
825
+ checkNode(set, ((_a = selection.alias) === null || _a === void 0 ? void 0 : _a.value) || selection.name.value, 'Field', selection);
834
826
  }
835
827
  }
836
828
  },
@@ -961,16 +953,20 @@ const rule$3 = {
961
953
  if (!mutationType) {
962
954
  return {};
963
955
  }
964
- const selector = `:matches(${Kind.OBJECT_TYPE_DEFINITION}, ${Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}] > ${Kind.FIELD_DEFINITION}`;
956
+ const selector = [
957
+ `:matches(${Kind.OBJECT_TYPE_DEFINITION}, ${Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}]`,
958
+ '>',
959
+ Kind.FIELD_DEFINITION,
960
+ Kind.NAMED_TYPE,
961
+ ].join(' ');
965
962
  return {
966
963
  [selector](node) {
967
- const rawNode = node.rawNode();
968
- const typeName = getTypeName(rawNode);
964
+ const typeName = node.name.value;
969
965
  const graphQLType = schema.getType(typeName);
970
966
  if (isScalarType(graphQLType)) {
971
967
  context.report({
972
- node,
973
- message: `Unexpected scalar result type "${typeName}".`,
968
+ loc: getLocation(node.loc, typeName),
969
+ message: `Unexpected scalar result type "${typeName}"`,
974
970
  });
975
971
  }
976
972
  },
@@ -1019,23 +1015,13 @@ const rule$4 = {
1019
1015
  for (const field of node.fields) {
1020
1016
  const fieldName = field.name.value;
1021
1017
  if (fieldName.toLowerCase().startsWith(lowerTypeName)) {
1022
- const { start } = field.loc;
1023
1018
  context.report({
1024
1019
  data: {
1025
1020
  fieldName,
1026
1021
  typeName,
1027
1022
  },
1028
1023
  messageId: AVOID_TYPENAME_PREFIX,
1029
- loc: {
1030
- start: {
1031
- line: start.line,
1032
- column: start.column - 1,
1033
- },
1034
- end: {
1035
- line: start.line,
1036
- column: start.column - 1 + lowerTypeName.length,
1037
- },
1038
- },
1024
+ loc: getLocation(field.loc, lowerTypeName),
1039
1025
  });
1040
1026
  }
1041
1027
  }
@@ -1095,7 +1081,7 @@ const rule$5 = {
1095
1081
  '[description.type="StringValue"]': node => {
1096
1082
  if (node.description.block !== (style === 'block')) {
1097
1083
  context.report({
1098
- node: node.description,
1084
+ loc: getLocation(node.description.loc),
1099
1085
  message: `Unexpected ${wrongDescriptionType} description`,
1100
1086
  });
1101
1087
  }
@@ -1182,10 +1168,11 @@ const rule$6 = {
1182
1168
  const shouldCheckType = node => (options.checkMutations && isMutationType(node)) || (options.checkQueries && isQueryType(node));
1183
1169
  const listeners = {
1184
1170
  'FieldDefinition > InputValueDefinition': node => {
1185
- if (node.name.value !== 'input' && shouldCheckType(node.parent.parent)) {
1171
+ const name = node.name.value;
1172
+ if (name !== 'input' && shouldCheckType(node.parent.parent)) {
1186
1173
  context.report({
1187
- node: node.name,
1188
- message: `Input "${node.name.value}" should be called "input"`,
1174
+ loc: getLocation(node.loc, name),
1175
+ message: `Input "${name}" should be called "input"`,
1189
1176
  });
1190
1177
  }
1191
1178
  },
@@ -1202,11 +1189,12 @@ const rule$6 = {
1202
1189
  const inputValueNode = findInputType(node);
1203
1190
  if (shouldCheckType(inputValueNode.parent.parent)) {
1204
1191
  const mutationName = `${inputValueNode.parent.name.value}Input`;
1192
+ const name = node.name.value;
1205
1193
  if ((options.caseSensitiveInputType && node.name.value !== mutationName) ||
1206
- node.name.value.toLowerCase() !== mutationName.toLowerCase()) {
1194
+ name.toLowerCase() !== mutationName.toLowerCase()) {
1207
1195
  context.report({
1208
- node,
1209
- message: `InputType "${node.name.value}" name should be "${mutationName}"`,
1196
+ loc: getLocation(node.loc, name),
1197
+ message: `InputType "${name}" name should be "${mutationName}"`,
1210
1198
  });
1211
1199
  }
1212
1200
  }
@@ -1363,7 +1351,8 @@ const rule$7 = {
1363
1351
  var _a;
1364
1352
  if (options.fileExtension && options.fileExtension !== fileExtension) {
1365
1353
  context.report({
1366
- node: documentNode,
1354
+ // Report on first character
1355
+ loc: { column: 0, line: 1 },
1367
1356
  messageId: MATCH_EXTENSION,
1368
1357
  data: {
1369
1358
  fileExtension,
@@ -1395,7 +1384,8 @@ const rule$7 = {
1395
1384
  const filenameWithExtension = filename + expectedExtension;
1396
1385
  if (expectedFilename !== filenameWithExtension) {
1397
1386
  context.report({
1398
- node: documentNode,
1387
+ // Report on first character
1388
+ loc: { column: 0, line: 1 },
1399
1389
  messageId: MATCH_STYLE,
1400
1390
  data: {
1401
1391
  expectedFilename,
@@ -1593,7 +1583,7 @@ const rule$8 = {
1593
1583
  });
1594
1584
  if (result.ok === false) {
1595
1585
  context.report({
1596
- node,
1586
+ loc: getLocation(node.loc, node.value),
1597
1587
  message: result.errorMessage,
1598
1588
  data: {
1599
1589
  prefix,
@@ -1620,10 +1610,16 @@ const rule$8 = {
1620
1610
  return {
1621
1611
  Name: node => {
1622
1612
  if (node.value.startsWith('_') && options.leadingUnderscore === 'forbid') {
1623
- context.report({ node, message: 'Leading underscores are not allowed' });
1613
+ context.report({
1614
+ loc: getLocation(node.loc, node.value),
1615
+ message: 'Leading underscores are not allowed',
1616
+ });
1624
1617
  }
1625
1618
  if (node.value.endsWith('_') && options.trailingUnderscore === 'forbid') {
1626
- context.report({ node, message: 'Trailing underscores are not allowed' });
1619
+ context.report({
1620
+ loc: getLocation(node.loc, node.value),
1621
+ message: 'Trailing underscores are not allowed',
1622
+ });
1627
1623
  }
1628
1624
  },
1629
1625
  ObjectTypeDefinition: node => {
@@ -1737,28 +1733,14 @@ const rule$9 = {
1737
1733
  },
1738
1734
  create(context) {
1739
1735
  return {
1740
- OperationDefinition(node) {
1741
- var _a;
1742
- const isAnonymous = (((_a = node.name) === null || _a === void 0 ? void 0 : _a.value) || '').length === 0;
1743
- if (isAnonymous) {
1744
- const { start } = node.loc;
1745
- context.report({
1746
- loc: {
1747
- start: {
1748
- column: start.column - 1,
1749
- line: start.line,
1750
- },
1751
- end: {
1752
- column: start.column - 1 + node.operation.length,
1753
- line: start.line,
1754
- },
1755
- },
1756
- data: {
1757
- operation: node.operation,
1758
- },
1759
- messageId: NO_ANONYMOUS_OPERATIONS,
1760
- });
1761
- }
1736
+ 'OperationDefinition[name=undefined]'(node) {
1737
+ context.report({
1738
+ loc: getLocation(node.loc, node.operation),
1739
+ data: {
1740
+ operation: node.operation,
1741
+ },
1742
+ messageId: NO_ANONYMOUS_OPERATIONS,
1743
+ });
1762
1744
  },
1763
1745
  };
1764
1746
  },
@@ -1865,8 +1847,8 @@ const rule$b = {
1865
1847
  mutation {
1866
1848
  changeSomething(
1867
1849
  type: OLD # This is deprecated, so you'll get an error
1868
- ) {
1869
- ...
1850
+ ) {
1851
+ ...
1870
1852
  }
1871
1853
  }
1872
1854
  `,
@@ -1904,8 +1886,9 @@ const rule$b = {
1904
1886
  const typeInfo = node.typeInfo();
1905
1887
  if (typeInfo && typeInfo.enumValue) {
1906
1888
  if (typeInfo.enumValue.deprecationReason) {
1889
+ const enumValueName = node.value;
1907
1890
  context.report({
1908
- loc: node.loc,
1891
+ loc: getLocation(node.loc, enumValueName),
1909
1892
  messageId: NO_DEPRECATED,
1910
1893
  data: {
1911
1894
  type: 'enum value',
@@ -1920,8 +1903,9 @@ const rule$b = {
1920
1903
  const typeInfo = node.typeInfo();
1921
1904
  if (typeInfo && typeInfo.fieldDef) {
1922
1905
  if (typeInfo.fieldDef.deprecationReason) {
1906
+ const fieldName = node.name.value;
1923
1907
  context.report({
1924
- loc: node.loc,
1908
+ loc: getLocation(node.loc, fieldName),
1925
1909
  messageId: NO_DEPRECATED,
1926
1910
  data: {
1927
1911
  type: 'field',
@@ -1997,10 +1981,7 @@ const rule$c = {
1997
1981
  if (!isEslintComment && line !== prev.line && next.kind === TokenKind.NAME && linesAfter < 2) {
1998
1982
  context.report({
1999
1983
  messageId: HASHTAG_COMMENT,
2000
- loc: {
2001
- start: { line, column },
2002
- end: { line, column },
2003
- },
1984
+ loc: getLocation({ start: { line, column } }),
2004
1985
  });
2005
1986
  }
2006
1987
  }
@@ -2129,7 +2110,7 @@ const rule$e = {
2129
2110
  const typeName = node.name.value;
2130
2111
  if (!reachableTypes.has(typeName)) {
2131
2112
  context.report({
2132
- node,
2113
+ loc: getLocation(node.name.loc, typeName, { offsetStart: node.kind === Kind.DIRECTIVE_DEFINITION ? 2 : 1 }),
2133
2114
  messageId: UNREACHABLE_TYPE,
2134
2115
  data: { typeName },
2135
2116
  fix: fixer => fixer.remove(node),
@@ -2227,7 +2208,7 @@ const rule$f = {
2227
2208
  return;
2228
2209
  }
2229
2210
  context.report({
2230
- node,
2211
+ loc: getLocation(node.loc, fieldName),
2231
2212
  messageId: UNUSED_FIELD,
2232
2213
  data: { fieldName },
2233
2214
  fix(fixer) {
@@ -2382,10 +2363,10 @@ const rule$g = {
2382
2363
  ],
2383
2364
  },
2384
2365
  messages: {
2385
- [MESSAGE_REQUIRE_DATE]: 'Directive "@deprecated" must have a deletion date.',
2386
- [MESSAGE_INVALID_FORMAT]: 'Deletion date must be in format "DD/MM/YYYY".',
2387
- [MESSAGE_INVALID_DATE]: 'Invalid "{{ deletionDate }}" deletion date.',
2388
- [MESSAGE_CAN_BE_REMOVED]: '"{{ nodeName }}" сan be removed.',
2366
+ [MESSAGE_REQUIRE_DATE]: 'Directive "@deprecated" must have a deletion date',
2367
+ [MESSAGE_INVALID_FORMAT]: 'Deletion date must be in format "DD/MM/YYYY"',
2368
+ [MESSAGE_INVALID_DATE]: 'Invalid "{{ deletionDate }}" deletion date',
2369
+ [MESSAGE_CAN_BE_REMOVED]: '"{{ nodeName }}" сan be removed',
2389
2370
  },
2390
2371
  schema: [
2391
2372
  {
@@ -2406,13 +2387,16 @@ const rule$g = {
2406
2387
  const argName = ((_a = context.options[0]) === null || _a === void 0 ? void 0 : _a.argumentName) || 'deletionDate';
2407
2388
  const deletionDateNode = node.arguments.find(arg => arg.name.value === argName);
2408
2389
  if (!deletionDateNode) {
2409
- context.report({ node: node.name, messageId: MESSAGE_REQUIRE_DATE });
2390
+ context.report({
2391
+ loc: getLocation(node.loc, node.name.value, { offsetEnd: 0 }),
2392
+ messageId: MESSAGE_REQUIRE_DATE,
2393
+ });
2410
2394
  return;
2411
2395
  }
2412
2396
  const deletionDate = valueFromNode(deletionDateNode.value);
2413
2397
  const isValidDate = DATE_REGEX.test(deletionDate);
2414
2398
  if (!isValidDate) {
2415
- context.report({ node: node.name, messageId: MESSAGE_INVALID_FORMAT });
2399
+ context.report({ node: deletionDateNode.value, messageId: MESSAGE_INVALID_FORMAT });
2416
2400
  return;
2417
2401
  }
2418
2402
  let [day, month, year] = deletionDate.split('/');
@@ -2421,7 +2405,7 @@ const rule$g = {
2421
2405
  const deletionDateInMS = Date.parse(`${year}-${month}-${day}`);
2422
2406
  if (Number.isNaN(deletionDateInMS)) {
2423
2407
  context.report({
2424
- node: node.name,
2408
+ node: deletionDateNode.value,
2425
2409
  messageId: MESSAGE_INVALID_DATE,
2426
2410
  data: {
2427
2411
  deletionDate,
@@ -2432,7 +2416,7 @@ const rule$g = {
2432
2416
  const canRemove = Date.now() > deletionDateInMS;
2433
2417
  if (canRemove) {
2434
2418
  context.report({
2435
- node: node.name,
2419
+ node,
2436
2420
  messageId: MESSAGE_CAN_BE_REMOVED,
2437
2421
  data: {
2438
2422
  nodeName: node.parent.name.value,
@@ -2483,17 +2467,15 @@ const rule$h = {
2483
2467
  },
2484
2468
  create(context) {
2485
2469
  return {
2486
- Directive(node) {
2487
- if (node && node.name && node.name.value === 'deprecated') {
2488
- const args = node.arguments || [];
2489
- const reasonArg = args.find(arg => arg.name && arg.name.value === 'reason');
2490
- const value = reasonArg ? String(valueFromNode(reasonArg.value) || '').trim() : null;
2491
- if (!value) {
2492
- context.report({
2493
- node: node.name,
2494
- message: 'Directive "@deprecated" must have a reason!',
2495
- });
2496
- }
2470
+ 'Directive[name.value=deprecated]'(node) {
2471
+ const args = node.arguments || [];
2472
+ const reasonArg = args.find(arg => arg.name && arg.name.value === 'reason');
2473
+ const value = reasonArg ? String(valueFromNode(reasonArg.value) || '').trim() : null;
2474
+ if (!value) {
2475
+ context.report({
2476
+ loc: getLocation(node.loc, node.name.value, { offsetEnd: 0 }),
2477
+ message: 'Directive "@deprecated" must have a reason!',
2478
+ });
2497
2479
  }
2498
2480
  },
2499
2481
  };
@@ -2516,20 +2498,8 @@ const DESCRIBABLE_NODES = [
2516
2498
  function verifyRule(context, node) {
2517
2499
  if (node) {
2518
2500
  if (!node.description || !node.description.value || node.description.value.trim().length === 0) {
2519
- const { start, end } = ('name' in node ? node.name : node).loc;
2520
2501
  context.report({
2521
- loc: {
2522
- start: {
2523
- line: start.line,
2524
- column: start.column - 1,
2525
- },
2526
- end: {
2527
- line: end.line,
2528
- column:
2529
- // node.name don't exist on SchemaDefinition
2530
- 'name' in node ? end.column - 1 + node.name.value.length : end.column,
2531
- },
2532
- },
2502
+ loc: getLocation(('name' in node ? node.name : node).loc, 'name' in node ? node.name.value : 'schema'),
2533
2503
  messageId: REQUIRE_DESCRIPTION_ERROR,
2534
2504
  data: {
2535
2505
  nodeType: node.kind,
@@ -2650,18 +2620,22 @@ const rule$j = {
2650
2620
  if (!mutationType || !queryType) {
2651
2621
  return {};
2652
2622
  }
2653
- const selector = `:matches(${Kind.OBJECT_TYPE_DEFINITION}, ${Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}] > ${Kind.FIELD_DEFINITION}`;
2623
+ const selector = [
2624
+ `:matches(${Kind.OBJECT_TYPE_DEFINITION}, ${Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}]`,
2625
+ '>',
2626
+ Kind.FIELD_DEFINITION,
2627
+ Kind.NAMED_TYPE,
2628
+ ].join(' ');
2654
2629
  return {
2655
2630
  [selector](node) {
2656
- const rawNode = node.rawNode();
2657
- const typeName = getTypeName(rawNode);
2631
+ const typeName = node.name.value;
2658
2632
  const graphQLType = schema.getType(typeName);
2659
2633
  if (isObjectType$1(graphQLType)) {
2660
2634
  const { fields } = graphQLType.astNode;
2661
2635
  const hasQueryType = fields.some(field => getTypeName(field) === queryType.name);
2662
2636
  if (!hasQueryType) {
2663
2637
  context.report({
2664
- node,
2638
+ loc: getLocation(node.loc, typeName),
2665
2639
  message: `Mutation result type "${graphQLType.name}" must contain field of type "${queryType.name}".`,
2666
2640
  });
2667
2641
  }
@@ -3000,7 +2974,7 @@ const rule$l = {
3000
2974
  getDocument: () => document,
3001
2975
  reportError: (error) => {
3002
2976
  context.report({
3003
- loc: error.locations[0],
2977
+ loc: getLocation({ start: error.locations[0] }),
3004
2978
  message: error.message,
3005
2979
  });
3006
2980
  },
@@ -3156,15 +3130,16 @@ const rule$m = {
3156
3130
  }
3157
3131
  return isValidIdName && isValidIdType;
3158
3132
  });
3133
+ const typeName = node.name.value;
3159
3134
  // Usually, there should be only one unique identifier field per type.
3160
3135
  // Some clients allow multiple fields to be used. If more people need this,
3161
3136
  // we can extend this rule later.
3162
3137
  if (validIds.length !== 1) {
3163
3138
  context.report({
3164
- node,
3165
- message: '{{nodeName}} must have exactly one non-nullable unique identifier. Accepted name(s): {{acceptedNamesString}} ; Accepted type(s): {{acceptedTypesString}}',
3139
+ loc: getLocation(node.name.loc, typeName),
3140
+ message: `{{ typeName }} must have exactly one non-nullable unique identifier. Accepted name(s): {{ acceptedNamesString }} ; Accepted type(s): {{ acceptedTypesString }}`,
3166
3141
  data: {
3167
- nodeName: node.name.value,
3142
+ typeName,
3168
3143
  acceptedNamesString: options.acceptedIdNames.join(','),
3169
3144
  acceptedTypesString: options.acceptedIdTypes.join(','),
3170
3145
  },
@@ -3178,11 +3153,7 @@ const rule$m = {
3178
3153
  const RULE_NAME$3 = 'unique-fragment-name';
3179
3154
  const UNIQUE_FRAGMENT_NAME = 'UNIQUE_FRAGMENT_NAME';
3180
3155
  const checkNode = (context, node, ruleName, messageId) => {
3181
- var _a;
3182
- const documentName = (_a = node.name) === null || _a === void 0 ? void 0 : _a.value;
3183
- if (!documentName) {
3184
- return;
3185
- }
3156
+ const documentName = node.name.value;
3186
3157
  const siblings = requireSiblingsOperations(ruleName, context);
3187
3158
  const siblingDocuments = node.kind === Kind.FRAGMENT_DEFINITION ? siblings.getFragment(documentName) : siblings.getOperation(documentName);
3188
3159
  const filepath = context.getFilename();
@@ -3193,7 +3164,6 @@ const checkNode = (context, node, ruleName, messageId) => {
3193
3164
  return isSameName && !isSamePath;
3194
3165
  });
3195
3166
  if (conflictingDocuments.length > 0) {
3196
- const { start, end } = node.name.loc;
3197
3167
  context.report({
3198
3168
  messageId,
3199
3169
  data: {
@@ -3202,16 +3172,7 @@ const checkNode = (context, node, ruleName, messageId) => {
3202
3172
  .map(f => `\t${relative(process.cwd(), getOnDiskFilepath(f.filePath))}`)
3203
3173
  .join('\n'),
3204
3174
  },
3205
- loc: {
3206
- start: {
3207
- line: start.line,
3208
- column: start.column - 1,
3209
- },
3210
- end: {
3211
- line: end.line,
3212
- column: end.column - 1,
3213
- },
3214
- },
3175
+ loc: getLocation(node.name.loc, documentName),
3215
3176
  });
3216
3177
  }
3217
3178
  };
@@ -3328,7 +3289,7 @@ const rule$o = {
3328
3289
  },
3329
3290
  create(context) {
3330
3291
  return {
3331
- OperationDefinition(node) {
3292
+ 'OperationDefinition[name!=undefined]'(node) {
3332
3293
  checkNode(context, node, RULE_NAME$4, UNIQUE_OPERATION_NAME);
3333
3294
  },
3334
3295
  };
@@ -3758,22 +3719,110 @@ function parseForESLint(code, options = {}) {
3758
3719
  }
3759
3720
  }
3760
3721
 
3761
- class GraphQLRuleTester extends require('eslint').RuleTester {
3722
+ class GraphQLRuleTester extends RuleTester {
3762
3723
  constructor(parserOptions = {}) {
3763
- super({
3724
+ const config = {
3764
3725
  parser: require.resolve('@graphql-eslint/eslint-plugin'),
3765
3726
  parserOptions: {
3766
3727
  ...parserOptions,
3767
3728
  skipGraphQLConfig: true,
3768
3729
  },
3769
- });
3730
+ };
3731
+ super(config);
3732
+ this.config = config;
3770
3733
  }
3771
3734
  fromMockFile(path) {
3772
3735
  return readFileSync(resolve(__dirname, `../tests/mocks/${path}`), 'utf-8');
3773
3736
  }
3774
3737
  runGraphQLTests(name, rule, tests) {
3775
- super.run(name, rule, tests);
3738
+ const ruleTests = Linter.version.startsWith('8')
3739
+ ? tests
3740
+ : {
3741
+ valid: tests.valid.map(test => {
3742
+ if (typeof test === 'string') {
3743
+ return test;
3744
+ }
3745
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
3746
+ const { name, ...testCaseOptions } = test;
3747
+ return testCaseOptions;
3748
+ }),
3749
+ invalid: tests.invalid.map(test => {
3750
+ // ESLint 7 throws an error on CI - Unexpected top-level property "name"
3751
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
3752
+ const { name, ...testCaseOptions } = test;
3753
+ return testCaseOptions;
3754
+ }),
3755
+ };
3756
+ super.run(name, rule, ruleTests);
3757
+ // Skip snapshot testing if `expect` variable is not defined
3758
+ if (typeof expect === 'undefined') {
3759
+ return;
3760
+ }
3761
+ const linter = new Linter();
3762
+ linter.defineRule(name, rule);
3763
+ for (const testCase of tests.invalid) {
3764
+ const verifyConfig = getVerifyConfig(name, this.config, testCase);
3765
+ defineParser(linter, verifyConfig.parser);
3766
+ const { code, filename } = testCase;
3767
+ const messages = linter.verify(code, verifyConfig, { filename });
3768
+ for (const message of messages) {
3769
+ if (message.fatal) {
3770
+ throw new Error(message.message);
3771
+ }
3772
+ const messageForSnapshot = visualizeEslintMessage(code, message);
3773
+ // eslint-disable-next-line no-undef
3774
+ expect(messageForSnapshot).toMatchSnapshot();
3775
+ }
3776
+ }
3777
+ }
3778
+ }
3779
+ function getVerifyConfig(ruleId, testerConfig, testCase) {
3780
+ const { options, parserOptions, parser = testerConfig.parser } = testCase;
3781
+ return {
3782
+ ...testerConfig,
3783
+ parser,
3784
+ parserOptions: {
3785
+ ...testerConfig.parserOptions,
3786
+ ...parserOptions,
3787
+ },
3788
+ rules: {
3789
+ [ruleId]: ['error', ...(Array.isArray(options) ? options : [])],
3790
+ },
3791
+ };
3792
+ }
3793
+ const parsers = new WeakMap();
3794
+ function defineParser(linter, parser) {
3795
+ if (!parser) {
3796
+ return;
3797
+ }
3798
+ if (!parsers.has(linter)) {
3799
+ parsers.set(linter, new Set());
3776
3800
  }
3801
+ const defined = parsers.get(linter);
3802
+ if (!defined.has(parser)) {
3803
+ defined.add(parser);
3804
+ linter.defineParser(parser, require(parser));
3805
+ }
3806
+ }
3807
+ function visualizeEslintMessage(text, result) {
3808
+ const { line, column, endLine, endColumn, message } = result;
3809
+ const location = {
3810
+ start: {
3811
+ line,
3812
+ column,
3813
+ },
3814
+ };
3815
+ if (typeof endLine === 'number' && typeof endColumn === 'number') {
3816
+ location.end = {
3817
+ line: endLine,
3818
+ column: endColumn,
3819
+ };
3820
+ }
3821
+ return codeFrameColumns(text, location, {
3822
+ linesAbove: Number.POSITIVE_INFINITY,
3823
+ linesBelow: Number.POSITIVE_INFINITY,
3824
+ message,
3825
+ });
3777
3826
  }
3778
3827
 
3779
3828
  export { GraphQLRuleTester, configs, convertDescription, convertLocation, convertRange, convertToESTree, extractCommentsFromAst, getBaseType, isNodeWithDescription, parse, parseForESLint, processors, rules, valueFromNode };