@graphql-eslint/eslint-plugin 2.3.2-alpha-5497183.0 → 2.3.2

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.
Files changed (4) hide show
  1. package/index.js +91 -100
  2. package/index.mjs +91 -100
  3. package/package.json +1 -1
  4. package/testkit.d.ts +1 -0
package/index.js CHANGED
@@ -275,7 +275,7 @@ function validateDoc(sourceNode, context, schema, documentNode, rules, ruleName
275
275
  for (const error of validationErrors) {
276
276
  const validateRuleName = ruleName || `[${extractRuleName(error.stack)}]`;
277
277
  context.report({
278
- loc: error.locations[0],
278
+ loc: getLocation({ start: error.locations[0] }),
279
279
  message: ruleName ? error.message : `${validateRuleName} ${error.message}`,
280
280
  });
281
281
  }
@@ -612,7 +612,7 @@ const rule = {
612
612
  ],
613
613
  },
614
614
  messages: {
615
- [ALPHABETIZE]: '"{{ currName }}" should be before "{{ prevName }}".',
615
+ [ALPHABETIZE]: '"{{ currName }}" should be before "{{ prevName }}"',
616
616
  },
617
617
  schema: {
618
618
  type: 'array',
@@ -669,16 +669,9 @@ const rule = {
669
669
  for (const node of nodes) {
670
670
  const currName = node.name.value;
671
671
  if (prevName && prevName > currName) {
672
- const { start, end } = node.name.loc;
673
672
  const isVariableNode = node.kind === graphql.Kind.VARIABLE;
674
673
  context.report({
675
- loc: {
676
- start: {
677
- line: start.line,
678
- column: start.column - (isVariableNode ? 2 : 1),
679
- },
680
- end,
681
- },
674
+ loc: getLocation(node.loc, node.name.value, { offsetEnd: isVariableNode ? 0 : 1 }),
682
675
  messageId: ALPHABETIZE,
683
676
  data: isVariableNode
684
677
  ? {
@@ -1028,23 +1021,13 @@ const rule$4 = {
1028
1021
  for (const field of node.fields) {
1029
1022
  const fieldName = field.name.value;
1030
1023
  if (fieldName.toLowerCase().startsWith(lowerTypeName)) {
1031
- const { start } = field.loc;
1032
1024
  context.report({
1033
1025
  data: {
1034
1026
  fieldName,
1035
1027
  typeName,
1036
1028
  },
1037
1029
  messageId: AVOID_TYPENAME_PREFIX,
1038
- loc: {
1039
- start: {
1040
- line: start.line,
1041
- column: start.column - 1,
1042
- },
1043
- end: {
1044
- line: start.line,
1045
- column: start.column - 1 + lowerTypeName.length,
1046
- },
1047
- },
1030
+ loc: getLocation(field.loc, lowerTypeName),
1048
1031
  });
1049
1032
  }
1050
1033
  }
@@ -1104,7 +1087,7 @@ const rule$5 = {
1104
1087
  '[description.type="StringValue"]': node => {
1105
1088
  if (node.description.block !== (style === 'block')) {
1106
1089
  context.report({
1107
- node: node.description,
1090
+ loc: getLocation(node.description.loc),
1108
1091
  message: `Unexpected ${wrongDescriptionType} description`,
1109
1092
  });
1110
1093
  }
@@ -1191,10 +1174,11 @@ const rule$6 = {
1191
1174
  const shouldCheckType = node => (options.checkMutations && isMutationType(node)) || (options.checkQueries && isQueryType(node));
1192
1175
  const listeners = {
1193
1176
  'FieldDefinition > InputValueDefinition': node => {
1194
- if (node.name.value !== 'input' && shouldCheckType(node.parent.parent)) {
1177
+ const name = node.name.value;
1178
+ if (name !== 'input' && shouldCheckType(node.parent.parent)) {
1195
1179
  context.report({
1196
- node: node.name,
1197
- message: `Input "${node.name.value}" should be called "input"`,
1180
+ loc: getLocation(node.loc, name),
1181
+ message: `Input "${name}" should be called "input"`,
1198
1182
  });
1199
1183
  }
1200
1184
  },
@@ -1211,11 +1195,12 @@ const rule$6 = {
1211
1195
  const inputValueNode = findInputType(node);
1212
1196
  if (shouldCheckType(inputValueNode.parent.parent)) {
1213
1197
  const mutationName = `${inputValueNode.parent.name.value}Input`;
1198
+ const name = node.name.value;
1214
1199
  if ((options.caseSensitiveInputType && node.name.value !== mutationName) ||
1215
- node.name.value.toLowerCase() !== mutationName.toLowerCase()) {
1200
+ name.toLowerCase() !== mutationName.toLowerCase()) {
1216
1201
  context.report({
1217
- node,
1218
- message: `InputType "${node.name.value}" name should be "${mutationName}"`,
1202
+ loc: getLocation(node.loc, name),
1203
+ message: `InputType "${name}" name should be "${mutationName}"`,
1219
1204
  });
1220
1205
  }
1221
1206
  }
@@ -1604,7 +1589,7 @@ const rule$8 = {
1604
1589
  });
1605
1590
  if (result.ok === false) {
1606
1591
  context.report({
1607
- node,
1592
+ loc: getLocation(node.loc, node.value),
1608
1593
  message: result.errorMessage,
1609
1594
  data: {
1610
1595
  prefix,
@@ -1631,10 +1616,16 @@ const rule$8 = {
1631
1616
  return {
1632
1617
  Name: node => {
1633
1618
  if (node.value.startsWith('_') && options.leadingUnderscore === 'forbid') {
1634
- context.report({ node, message: 'Leading underscores are not allowed' });
1619
+ context.report({
1620
+ loc: getLocation(node.loc, node.value),
1621
+ message: 'Leading underscores are not allowed',
1622
+ });
1635
1623
  }
1636
1624
  if (node.value.endsWith('_') && options.trailingUnderscore === 'forbid') {
1637
- context.report({ node, message: 'Trailing underscores are not allowed' });
1625
+ context.report({
1626
+ loc: getLocation(node.loc, node.value),
1627
+ message: 'Trailing underscores are not allowed',
1628
+ });
1638
1629
  }
1639
1630
  },
1640
1631
  ObjectTypeDefinition: node => {
@@ -1748,28 +1739,14 @@ const rule$9 = {
1748
1739
  },
1749
1740
  create(context) {
1750
1741
  return {
1751
- OperationDefinition(node) {
1752
- var _a;
1753
- const isAnonymous = (((_a = node.name) === null || _a === void 0 ? void 0 : _a.value) || '').length === 0;
1754
- if (isAnonymous) {
1755
- const { start } = node.loc;
1756
- context.report({
1757
- loc: {
1758
- start: {
1759
- column: start.column - 1,
1760
- line: start.line,
1761
- },
1762
- end: {
1763
- column: start.column - 1 + node.operation.length,
1764
- line: start.line,
1765
- },
1766
- },
1767
- data: {
1768
- operation: node.operation,
1769
- },
1770
- messageId: NO_ANONYMOUS_OPERATIONS,
1771
- });
1772
- }
1742
+ 'OperationDefinition[name=undefined]'(node) {
1743
+ context.report({
1744
+ loc: getLocation(node.loc, node.operation),
1745
+ data: {
1746
+ operation: node.operation,
1747
+ },
1748
+ messageId: NO_ANONYMOUS_OPERATIONS,
1749
+ });
1773
1750
  },
1774
1751
  };
1775
1752
  },
@@ -1876,8 +1853,8 @@ const rule$b = {
1876
1853
  mutation {
1877
1854
  changeSomething(
1878
1855
  type: OLD # This is deprecated, so you'll get an error
1879
- ) {
1880
- ...
1856
+ ) {
1857
+ ...
1881
1858
  }
1882
1859
  }
1883
1860
  `,
@@ -1915,8 +1892,9 @@ const rule$b = {
1915
1892
  const typeInfo = node.typeInfo();
1916
1893
  if (typeInfo && typeInfo.enumValue) {
1917
1894
  if (typeInfo.enumValue.isDeprecated) {
1895
+ const enumValueName = node.value;
1918
1896
  context.report({
1919
- loc: node.loc,
1897
+ loc: getLocation(node.loc, enumValueName),
1920
1898
  messageId: NO_DEPRECATED,
1921
1899
  data: {
1922
1900
  type: 'enum value',
@@ -1931,8 +1909,9 @@ const rule$b = {
1931
1909
  const typeInfo = node.typeInfo();
1932
1910
  if (typeInfo && typeInfo.fieldDef) {
1933
1911
  if (typeInfo.fieldDef.isDeprecated) {
1912
+ const fieldName = node.name.value;
1934
1913
  context.report({
1935
- loc: node.loc,
1914
+ loc: getLocation(node.loc, fieldName),
1936
1915
  messageId: NO_DEPRECATED,
1937
1916
  data: {
1938
1917
  type: 'field',
@@ -2137,7 +2116,7 @@ const rule$e = {
2137
2116
  const typeName = node.name.value;
2138
2117
  if (!reachableTypes.has(typeName)) {
2139
2118
  context.report({
2140
- node,
2119
+ loc: getLocation(node.name.loc, typeName, { offsetStart: node.kind === graphql.Kind.DIRECTIVE_DEFINITION ? 2 : 1 }),
2141
2120
  messageId: UNREACHABLE_TYPE,
2142
2121
  data: { typeName },
2143
2122
  fix: fixer => fixer.remove(node),
@@ -2235,7 +2214,7 @@ const rule$f = {
2235
2214
  return;
2236
2215
  }
2237
2216
  context.report({
2238
- node,
2217
+ loc: getLocation(node.loc, fieldName),
2239
2218
  messageId: UNUSED_FIELD,
2240
2219
  data: { fieldName },
2241
2220
  fix(fixer) {
@@ -2390,10 +2369,10 @@ const rule$g = {
2390
2369
  ],
2391
2370
  },
2392
2371
  messages: {
2393
- [MESSAGE_REQUIRE_DATE]: 'Directive "@deprecated" must have a deletion date.',
2394
- [MESSAGE_INVALID_FORMAT]: 'Deletion date must be in format "DD/MM/YYYY".',
2395
- [MESSAGE_INVALID_DATE]: 'Invalid "{{ deletionDate }}" deletion date.',
2396
- [MESSAGE_CAN_BE_REMOVED]: '"{{ nodeName }}" сan be removed.',
2372
+ [MESSAGE_REQUIRE_DATE]: 'Directive "@deprecated" must have a deletion date',
2373
+ [MESSAGE_INVALID_FORMAT]: 'Deletion date must be in format "DD/MM/YYYY"',
2374
+ [MESSAGE_INVALID_DATE]: 'Invalid "{{ deletionDate }}" deletion date',
2375
+ [MESSAGE_CAN_BE_REMOVED]: '"{{ nodeName }}" сan be removed',
2397
2376
  },
2398
2377
  schema: [
2399
2378
  {
@@ -2414,13 +2393,16 @@ const rule$g = {
2414
2393
  const argName = ((_a = context.options[0]) === null || _a === void 0 ? void 0 : _a.argumentName) || 'deletionDate';
2415
2394
  const deletionDateNode = node.arguments.find(arg => arg.name.value === argName);
2416
2395
  if (!deletionDateNode) {
2417
- context.report({ node: node.name, messageId: MESSAGE_REQUIRE_DATE });
2396
+ context.report({
2397
+ loc: getLocation(node.loc, node.name.value, { offsetEnd: 0 }),
2398
+ messageId: MESSAGE_REQUIRE_DATE,
2399
+ });
2418
2400
  return;
2419
2401
  }
2420
2402
  const deletionDate = valueFromNode(deletionDateNode.value);
2421
2403
  const isValidDate = DATE_REGEX.test(deletionDate);
2422
2404
  if (!isValidDate) {
2423
- context.report({ node: node.name, messageId: MESSAGE_INVALID_FORMAT });
2405
+ context.report({ node: deletionDateNode.value, messageId: MESSAGE_INVALID_FORMAT });
2424
2406
  return;
2425
2407
  }
2426
2408
  let [day, month, year] = deletionDate.split('/');
@@ -2429,7 +2411,7 @@ const rule$g = {
2429
2411
  const deletionDateInMS = Date.parse(`${year}-${month}-${day}`);
2430
2412
  if (Number.isNaN(deletionDateInMS)) {
2431
2413
  context.report({
2432
- node: node.name,
2414
+ node: deletionDateNode.value,
2433
2415
  messageId: MESSAGE_INVALID_DATE,
2434
2416
  data: {
2435
2417
  deletionDate,
@@ -2440,7 +2422,7 @@ const rule$g = {
2440
2422
  const canRemove = Date.now() > deletionDateInMS;
2441
2423
  if (canRemove) {
2442
2424
  context.report({
2443
- node: node.name,
2425
+ node,
2444
2426
  messageId: MESSAGE_CAN_BE_REMOVED,
2445
2427
  data: {
2446
2428
  nodeName: node.parent.name.value,
@@ -2491,17 +2473,15 @@ const rule$h = {
2491
2473
  },
2492
2474
  create(context) {
2493
2475
  return {
2494
- Directive(node) {
2495
- if (node && node.name && node.name.value === 'deprecated') {
2496
- const args = node.arguments || [];
2497
- const reasonArg = args.find(arg => arg.name && arg.name.value === 'reason');
2498
- const value = reasonArg ? String(valueFromNode(reasonArg.value) || '').trim() : null;
2499
- if (!value) {
2500
- context.report({
2501
- node: node.name,
2502
- message: 'Directive "@deprecated" must have a reason!',
2503
- });
2504
- }
2476
+ 'Directive[name.value=deprecated]'(node) {
2477
+ const args = node.arguments || [];
2478
+ const reasonArg = args.find(arg => arg.name && arg.name.value === 'reason');
2479
+ const value = reasonArg ? String(valueFromNode(reasonArg.value) || '').trim() : null;
2480
+ if (!value) {
2481
+ context.report({
2482
+ loc: getLocation(node.loc, node.name.value, { offsetEnd: 0 }),
2483
+ message: 'Directive "@deprecated" must have a reason!',
2484
+ });
2505
2485
  }
2506
2486
  },
2507
2487
  };
@@ -2524,20 +2504,8 @@ const DESCRIBABLE_NODES = [
2524
2504
  function verifyRule(context, node) {
2525
2505
  if (node) {
2526
2506
  if (!node.description || !node.description.value || node.description.value.trim().length === 0) {
2527
- const { start, end } = ('name' in node ? node.name : node).loc;
2528
2507
  context.report({
2529
- loc: {
2530
- start: {
2531
- line: start.line,
2532
- column: start.column - 1,
2533
- },
2534
- end: {
2535
- line: end.line,
2536
- column:
2537
- // node.name don't exist on SchemaDefinition
2538
- 'name' in node ? end.column - 1 + node.name.value.length : end.column,
2539
- },
2540
- },
2508
+ loc: getLocation(('name' in node ? node.name : node).loc, 'name' in node ? node.name.value : 'schema'),
2541
2509
  messageId: REQUIRE_DESCRIPTION_ERROR,
2542
2510
  data: {
2543
2511
  nodeType: node.kind,
@@ -2658,18 +2626,22 @@ const rule$j = {
2658
2626
  if (!mutationType || !queryType) {
2659
2627
  return {};
2660
2628
  }
2661
- const selector = `:matches(${graphql.Kind.OBJECT_TYPE_DEFINITION}, ${graphql.Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}] > ${graphql.Kind.FIELD_DEFINITION}`;
2629
+ const selector = [
2630
+ `:matches(${graphql.Kind.OBJECT_TYPE_DEFINITION}, ${graphql.Kind.OBJECT_TYPE_EXTENSION})[name.value=${mutationType.name}]`,
2631
+ '>',
2632
+ graphql.Kind.FIELD_DEFINITION,
2633
+ graphql.Kind.NAMED_TYPE,
2634
+ ].join(' ');
2662
2635
  return {
2663
2636
  [selector](node) {
2664
- const rawNode = node.rawNode();
2665
- const typeName = getTypeName(rawNode);
2637
+ const typeName = node.name.value;
2666
2638
  const graphQLType = schema.getType(typeName);
2667
2639
  if (graphql.isObjectType(graphQLType)) {
2668
2640
  const { fields } = graphQLType.astNode;
2669
2641
  const hasQueryType = fields.some(field => getTypeName(field) === queryType.name);
2670
2642
  if (!hasQueryType) {
2671
2643
  context.report({
2672
- node,
2644
+ loc: getLocation(node.loc, typeName),
2673
2645
  message: `Mutation result type "${graphQLType.name}" must contain field of type "${queryType.name}".`,
2674
2646
  });
2675
2647
  }
@@ -3008,7 +2980,7 @@ const rule$l = {
3008
2980
  getDocument: () => document,
3009
2981
  reportError: (error) => {
3010
2982
  context.report({
3011
- loc: error.locations[0],
2983
+ loc: getLocation({ start: error.locations[0] }),
3012
2984
  message: error.message,
3013
2985
  });
3014
2986
  },
@@ -3164,15 +3136,16 @@ const rule$m = {
3164
3136
  }
3165
3137
  return isValidIdName && isValidIdType;
3166
3138
  });
3139
+ const typeName = node.name.value;
3167
3140
  // Usually, there should be only one unique identifier field per type.
3168
3141
  // Some clients allow multiple fields to be used. If more people need this,
3169
3142
  // we can extend this rule later.
3170
3143
  if (validIds.length !== 1) {
3171
3144
  context.report({
3172
- node,
3173
- message: '{{nodeName}} must have exactly one non-nullable unique identifier. Accepted name(s): {{acceptedNamesString}} ; Accepted type(s): {{acceptedTypesString}}',
3145
+ loc: getLocation(node.name.loc, typeName),
3146
+ message: `{{ typeName }} must have exactly one non-nullable unique identifier. Accepted name(s): {{ acceptedNamesString }} ; Accepted type(s): {{ acceptedTypesString }}`,
3174
3147
  data: {
3175
- nodeName: node.name.value,
3148
+ typeName,
3176
3149
  acceptedNamesString: options.acceptedIdNames.join(','),
3177
3150
  acceptedTypesString: options.acceptedIdTypes.join(','),
3178
3151
  },
@@ -3768,7 +3741,25 @@ class GraphQLRuleTester extends eslint.RuleTester {
3768
3741
  return fs.readFileSync(path.resolve(__dirname, `../tests/mocks/${path$1}`), 'utf-8');
3769
3742
  }
3770
3743
  runGraphQLTests(name, rule, tests) {
3771
- super.run(name, rule, tests);
3744
+ const ruleTests = eslint.Linter.version.startsWith('8')
3745
+ ? tests
3746
+ : {
3747
+ valid: tests.valid.map(test => {
3748
+ if (typeof test === 'string') {
3749
+ return test;
3750
+ }
3751
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
3752
+ const { name, ...testCaseOptions } = test;
3753
+ return testCaseOptions;
3754
+ }),
3755
+ invalid: tests.invalid.map(test => {
3756
+ // ESLint 7 throws an error on CI - Unexpected top-level property "name"
3757
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
3758
+ const { name, ...testCaseOptions } = test;
3759
+ return testCaseOptions;
3760
+ }),
3761
+ };
3762
+ super.run(name, rule, ruleTests);
3772
3763
  // Skip snapshot testing if `expect` variable is not defined
3773
3764
  if (typeof expect === 'undefined') {
3774
3765
  return;
package/index.mjs CHANGED
@@ -269,7 +269,7 @@ function validateDoc(sourceNode, context, schema, documentNode, rules, ruleName
269
269
  for (const error of validationErrors) {
270
270
  const validateRuleName = ruleName || `[${extractRuleName(error.stack)}]`;
271
271
  context.report({
272
- loc: error.locations[0],
272
+ loc: getLocation({ start: error.locations[0] }),
273
273
  message: ruleName ? error.message : `${validateRuleName} ${error.message}`,
274
274
  });
275
275
  }
@@ -606,7 +606,7 @@ const rule = {
606
606
  ],
607
607
  },
608
608
  messages: {
609
- [ALPHABETIZE]: '"{{ currName }}" should be before "{{ prevName }}".',
609
+ [ALPHABETIZE]: '"{{ currName }}" should be before "{{ prevName }}"',
610
610
  },
611
611
  schema: {
612
612
  type: 'array',
@@ -663,16 +663,9 @@ const rule = {
663
663
  for (const node of nodes) {
664
664
  const currName = node.name.value;
665
665
  if (prevName && prevName > currName) {
666
- const { start, end } = node.name.loc;
667
666
  const isVariableNode = node.kind === Kind.VARIABLE;
668
667
  context.report({
669
- loc: {
670
- start: {
671
- line: start.line,
672
- column: start.column - (isVariableNode ? 2 : 1),
673
- },
674
- end,
675
- },
668
+ loc: getLocation(node.loc, node.name.value, { offsetEnd: isVariableNode ? 0 : 1 }),
676
669
  messageId: ALPHABETIZE,
677
670
  data: isVariableNode
678
671
  ? {
@@ -1022,23 +1015,13 @@ const rule$4 = {
1022
1015
  for (const field of node.fields) {
1023
1016
  const fieldName = field.name.value;
1024
1017
  if (fieldName.toLowerCase().startsWith(lowerTypeName)) {
1025
- const { start } = field.loc;
1026
1018
  context.report({
1027
1019
  data: {
1028
1020
  fieldName,
1029
1021
  typeName,
1030
1022
  },
1031
1023
  messageId: AVOID_TYPENAME_PREFIX,
1032
- loc: {
1033
- start: {
1034
- line: start.line,
1035
- column: start.column - 1,
1036
- },
1037
- end: {
1038
- line: start.line,
1039
- column: start.column - 1 + lowerTypeName.length,
1040
- },
1041
- },
1024
+ loc: getLocation(field.loc, lowerTypeName),
1042
1025
  });
1043
1026
  }
1044
1027
  }
@@ -1098,7 +1081,7 @@ const rule$5 = {
1098
1081
  '[description.type="StringValue"]': node => {
1099
1082
  if (node.description.block !== (style === 'block')) {
1100
1083
  context.report({
1101
- node: node.description,
1084
+ loc: getLocation(node.description.loc),
1102
1085
  message: `Unexpected ${wrongDescriptionType} description`,
1103
1086
  });
1104
1087
  }
@@ -1185,10 +1168,11 @@ const rule$6 = {
1185
1168
  const shouldCheckType = node => (options.checkMutations && isMutationType(node)) || (options.checkQueries && isQueryType(node));
1186
1169
  const listeners = {
1187
1170
  'FieldDefinition > InputValueDefinition': node => {
1188
- if (node.name.value !== 'input' && shouldCheckType(node.parent.parent)) {
1171
+ const name = node.name.value;
1172
+ if (name !== 'input' && shouldCheckType(node.parent.parent)) {
1189
1173
  context.report({
1190
- node: node.name,
1191
- message: `Input "${node.name.value}" should be called "input"`,
1174
+ loc: getLocation(node.loc, name),
1175
+ message: `Input "${name}" should be called "input"`,
1192
1176
  });
1193
1177
  }
1194
1178
  },
@@ -1205,11 +1189,12 @@ const rule$6 = {
1205
1189
  const inputValueNode = findInputType(node);
1206
1190
  if (shouldCheckType(inputValueNode.parent.parent)) {
1207
1191
  const mutationName = `${inputValueNode.parent.name.value}Input`;
1192
+ const name = node.name.value;
1208
1193
  if ((options.caseSensitiveInputType && node.name.value !== mutationName) ||
1209
- node.name.value.toLowerCase() !== mutationName.toLowerCase()) {
1194
+ name.toLowerCase() !== mutationName.toLowerCase()) {
1210
1195
  context.report({
1211
- node,
1212
- message: `InputType "${node.name.value}" name should be "${mutationName}"`,
1196
+ loc: getLocation(node.loc, name),
1197
+ message: `InputType "${name}" name should be "${mutationName}"`,
1213
1198
  });
1214
1199
  }
1215
1200
  }
@@ -1598,7 +1583,7 @@ const rule$8 = {
1598
1583
  });
1599
1584
  if (result.ok === false) {
1600
1585
  context.report({
1601
- node,
1586
+ loc: getLocation(node.loc, node.value),
1602
1587
  message: result.errorMessage,
1603
1588
  data: {
1604
1589
  prefix,
@@ -1625,10 +1610,16 @@ const rule$8 = {
1625
1610
  return {
1626
1611
  Name: node => {
1627
1612
  if (node.value.startsWith('_') && options.leadingUnderscore === 'forbid') {
1628
- 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
+ });
1629
1617
  }
1630
1618
  if (node.value.endsWith('_') && options.trailingUnderscore === 'forbid') {
1631
- 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
+ });
1632
1623
  }
1633
1624
  },
1634
1625
  ObjectTypeDefinition: node => {
@@ -1742,28 +1733,14 @@ const rule$9 = {
1742
1733
  },
1743
1734
  create(context) {
1744
1735
  return {
1745
- OperationDefinition(node) {
1746
- var _a;
1747
- const isAnonymous = (((_a = node.name) === null || _a === void 0 ? void 0 : _a.value) || '').length === 0;
1748
- if (isAnonymous) {
1749
- const { start } = node.loc;
1750
- context.report({
1751
- loc: {
1752
- start: {
1753
- column: start.column - 1,
1754
- line: start.line,
1755
- },
1756
- end: {
1757
- column: start.column - 1 + node.operation.length,
1758
- line: start.line,
1759
- },
1760
- },
1761
- data: {
1762
- operation: node.operation,
1763
- },
1764
- messageId: NO_ANONYMOUS_OPERATIONS,
1765
- });
1766
- }
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
+ });
1767
1744
  },
1768
1745
  };
1769
1746
  },
@@ -1870,8 +1847,8 @@ const rule$b = {
1870
1847
  mutation {
1871
1848
  changeSomething(
1872
1849
  type: OLD # This is deprecated, so you'll get an error
1873
- ) {
1874
- ...
1850
+ ) {
1851
+ ...
1875
1852
  }
1876
1853
  }
1877
1854
  `,
@@ -1909,8 +1886,9 @@ const rule$b = {
1909
1886
  const typeInfo = node.typeInfo();
1910
1887
  if (typeInfo && typeInfo.enumValue) {
1911
1888
  if (typeInfo.enumValue.isDeprecated) {
1889
+ const enumValueName = node.value;
1912
1890
  context.report({
1913
- loc: node.loc,
1891
+ loc: getLocation(node.loc, enumValueName),
1914
1892
  messageId: NO_DEPRECATED,
1915
1893
  data: {
1916
1894
  type: 'enum value',
@@ -1925,8 +1903,9 @@ const rule$b = {
1925
1903
  const typeInfo = node.typeInfo();
1926
1904
  if (typeInfo && typeInfo.fieldDef) {
1927
1905
  if (typeInfo.fieldDef.isDeprecated) {
1906
+ const fieldName = node.name.value;
1928
1907
  context.report({
1929
- loc: node.loc,
1908
+ loc: getLocation(node.loc, fieldName),
1930
1909
  messageId: NO_DEPRECATED,
1931
1910
  data: {
1932
1911
  type: 'field',
@@ -2131,7 +2110,7 @@ const rule$e = {
2131
2110
  const typeName = node.name.value;
2132
2111
  if (!reachableTypes.has(typeName)) {
2133
2112
  context.report({
2134
- node,
2113
+ loc: getLocation(node.name.loc, typeName, { offsetStart: node.kind === Kind.DIRECTIVE_DEFINITION ? 2 : 1 }),
2135
2114
  messageId: UNREACHABLE_TYPE,
2136
2115
  data: { typeName },
2137
2116
  fix: fixer => fixer.remove(node),
@@ -2229,7 +2208,7 @@ const rule$f = {
2229
2208
  return;
2230
2209
  }
2231
2210
  context.report({
2232
- node,
2211
+ loc: getLocation(node.loc, fieldName),
2233
2212
  messageId: UNUSED_FIELD,
2234
2213
  data: { fieldName },
2235
2214
  fix(fixer) {
@@ -2384,10 +2363,10 @@ const rule$g = {
2384
2363
  ],
2385
2364
  },
2386
2365
  messages: {
2387
- [MESSAGE_REQUIRE_DATE]: 'Directive "@deprecated" must have a deletion date.',
2388
- [MESSAGE_INVALID_FORMAT]: 'Deletion date must be in format "DD/MM/YYYY".',
2389
- [MESSAGE_INVALID_DATE]: 'Invalid "{{ deletionDate }}" deletion date.',
2390
- [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',
2391
2370
  },
2392
2371
  schema: [
2393
2372
  {
@@ -2408,13 +2387,16 @@ const rule$g = {
2408
2387
  const argName = ((_a = context.options[0]) === null || _a === void 0 ? void 0 : _a.argumentName) || 'deletionDate';
2409
2388
  const deletionDateNode = node.arguments.find(arg => arg.name.value === argName);
2410
2389
  if (!deletionDateNode) {
2411
- 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
+ });
2412
2394
  return;
2413
2395
  }
2414
2396
  const deletionDate = valueFromNode(deletionDateNode.value);
2415
2397
  const isValidDate = DATE_REGEX.test(deletionDate);
2416
2398
  if (!isValidDate) {
2417
- context.report({ node: node.name, messageId: MESSAGE_INVALID_FORMAT });
2399
+ context.report({ node: deletionDateNode.value, messageId: MESSAGE_INVALID_FORMAT });
2418
2400
  return;
2419
2401
  }
2420
2402
  let [day, month, year] = deletionDate.split('/');
@@ -2423,7 +2405,7 @@ const rule$g = {
2423
2405
  const deletionDateInMS = Date.parse(`${year}-${month}-${day}`);
2424
2406
  if (Number.isNaN(deletionDateInMS)) {
2425
2407
  context.report({
2426
- node: node.name,
2408
+ node: deletionDateNode.value,
2427
2409
  messageId: MESSAGE_INVALID_DATE,
2428
2410
  data: {
2429
2411
  deletionDate,
@@ -2434,7 +2416,7 @@ const rule$g = {
2434
2416
  const canRemove = Date.now() > deletionDateInMS;
2435
2417
  if (canRemove) {
2436
2418
  context.report({
2437
- node: node.name,
2419
+ node,
2438
2420
  messageId: MESSAGE_CAN_BE_REMOVED,
2439
2421
  data: {
2440
2422
  nodeName: node.parent.name.value,
@@ -2485,17 +2467,15 @@ const rule$h = {
2485
2467
  },
2486
2468
  create(context) {
2487
2469
  return {
2488
- Directive(node) {
2489
- if (node && node.name && node.name.value === 'deprecated') {
2490
- const args = node.arguments || [];
2491
- const reasonArg = args.find(arg => arg.name && arg.name.value === 'reason');
2492
- const value = reasonArg ? String(valueFromNode(reasonArg.value) || '').trim() : null;
2493
- if (!value) {
2494
- context.report({
2495
- node: node.name,
2496
- message: 'Directive "@deprecated" must have a reason!',
2497
- });
2498
- }
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
+ });
2499
2479
  }
2500
2480
  },
2501
2481
  };
@@ -2518,20 +2498,8 @@ const DESCRIBABLE_NODES = [
2518
2498
  function verifyRule(context, node) {
2519
2499
  if (node) {
2520
2500
  if (!node.description || !node.description.value || node.description.value.trim().length === 0) {
2521
- const { start, end } = ('name' in node ? node.name : node).loc;
2522
2501
  context.report({
2523
- loc: {
2524
- start: {
2525
- line: start.line,
2526
- column: start.column - 1,
2527
- },
2528
- end: {
2529
- line: end.line,
2530
- column:
2531
- // node.name don't exist on SchemaDefinition
2532
- 'name' in node ? end.column - 1 + node.name.value.length : end.column,
2533
- },
2534
- },
2502
+ loc: getLocation(('name' in node ? node.name : node).loc, 'name' in node ? node.name.value : 'schema'),
2535
2503
  messageId: REQUIRE_DESCRIPTION_ERROR,
2536
2504
  data: {
2537
2505
  nodeType: node.kind,
@@ -2652,18 +2620,22 @@ const rule$j = {
2652
2620
  if (!mutationType || !queryType) {
2653
2621
  return {};
2654
2622
  }
2655
- 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(' ');
2656
2629
  return {
2657
2630
  [selector](node) {
2658
- const rawNode = node.rawNode();
2659
- const typeName = getTypeName(rawNode);
2631
+ const typeName = node.name.value;
2660
2632
  const graphQLType = schema.getType(typeName);
2661
2633
  if (isObjectType$1(graphQLType)) {
2662
2634
  const { fields } = graphQLType.astNode;
2663
2635
  const hasQueryType = fields.some(field => getTypeName(field) === queryType.name);
2664
2636
  if (!hasQueryType) {
2665
2637
  context.report({
2666
- node,
2638
+ loc: getLocation(node.loc, typeName),
2667
2639
  message: `Mutation result type "${graphQLType.name}" must contain field of type "${queryType.name}".`,
2668
2640
  });
2669
2641
  }
@@ -3002,7 +2974,7 @@ const rule$l = {
3002
2974
  getDocument: () => document,
3003
2975
  reportError: (error) => {
3004
2976
  context.report({
3005
- loc: error.locations[0],
2977
+ loc: getLocation({ start: error.locations[0] }),
3006
2978
  message: error.message,
3007
2979
  });
3008
2980
  },
@@ -3158,15 +3130,16 @@ const rule$m = {
3158
3130
  }
3159
3131
  return isValidIdName && isValidIdType;
3160
3132
  });
3133
+ const typeName = node.name.value;
3161
3134
  // Usually, there should be only one unique identifier field per type.
3162
3135
  // Some clients allow multiple fields to be used. If more people need this,
3163
3136
  // we can extend this rule later.
3164
3137
  if (validIds.length !== 1) {
3165
3138
  context.report({
3166
- node,
3167
- 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 }}`,
3168
3141
  data: {
3169
- nodeName: node.name.value,
3142
+ typeName,
3170
3143
  acceptedNamesString: options.acceptedIdNames.join(','),
3171
3144
  acceptedTypesString: options.acceptedIdTypes.join(','),
3172
3145
  },
@@ -3762,7 +3735,25 @@ class GraphQLRuleTester extends RuleTester {
3762
3735
  return readFileSync(resolve(__dirname, `../tests/mocks/${path}`), 'utf-8');
3763
3736
  }
3764
3737
  runGraphQLTests(name, rule, tests) {
3765
- 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);
3766
3757
  // Skip snapshot testing if `expect` variable is not defined
3767
3758
  if (typeof expect === 'undefined') {
3768
3759
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphql-eslint/eslint-plugin",
3
- "version": "2.3.2-alpha-5497183.0",
3
+ "version": "2.3.2",
4
4
  "sideEffects": false,
5
5
  "peerDependencies": {
6
6
  "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0"
package/testkit.d.ts CHANGED
@@ -6,6 +6,7 @@ export declare type GraphQLESLintRuleListener<WithTypeInfo extends boolean = fal
6
6
  [K in keyof ASTKindToNode]?: (node: GraphQLESTreeNode<ASTKindToNode[K], WithTypeInfo>) => void;
7
7
  } & Record<string, any>;
8
8
  export declare type GraphQLValidTestCase<Options> = Omit<RuleTester.ValidTestCase, 'options' | 'parserOptions'> & {
9
+ name: string;
9
10
  options?: Options;
10
11
  parserOptions?: ParserOptions;
11
12
  };