@graphql-eslint/eslint-plugin 2.3.0-alpha-6ba4002.0 → 2.3.2-alpha-99be3d2.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.
Files changed (70) hide show
  1. package/README.md +1 -1
  2. package/configs/all.d.ts +7 -0
  3. package/configs/index.d.ts +7 -0
  4. package/docs/README.md +2 -1
  5. package/docs/custom-rules.md +3 -3
  6. package/docs/rules/alphabetize.md +145 -0
  7. package/docs/rules/avoid-duplicate-fields.md +6 -1
  8. package/docs/rules/avoid-operation-name-prefix.md +8 -8
  9. package/docs/rules/avoid-scalar-result-type-on-mutation.md +6 -1
  10. package/docs/rules/avoid-typename-prefix.md +6 -1
  11. package/docs/rules/description-style.md +10 -9
  12. package/docs/rules/executable-definitions.md +6 -1
  13. package/docs/rules/fields-on-correct-type.md +6 -1
  14. package/docs/rules/fragments-on-composite-type.md +6 -1
  15. package/docs/rules/input-name.md +11 -10
  16. package/docs/rules/known-argument-names.md +6 -1
  17. package/docs/rules/known-directives.md +6 -1
  18. package/docs/rules/known-fragment-names.md +6 -1
  19. package/docs/rules/known-type-names.md +6 -1
  20. package/docs/rules/lone-anonymous-operation.md +6 -1
  21. package/docs/rules/lone-schema-definition.md +6 -1
  22. package/docs/rules/match-document-filename.md +12 -11
  23. package/docs/rules/naming-convention.md +21 -20
  24. package/docs/rules/no-anonymous-operations.md +6 -1
  25. package/docs/rules/no-case-insensitive-enum-values-duplicates.md +5 -1
  26. package/docs/rules/no-deprecated.md +6 -1
  27. package/docs/rules/no-fragment-cycles.md +6 -1
  28. package/docs/rules/no-hashtag-description.md +6 -1
  29. package/docs/rules/no-operation-name-suffix.md +5 -1
  30. package/docs/rules/no-undefined-variables.md +6 -1
  31. package/docs/rules/no-unreachable-types.md +6 -1
  32. package/docs/rules/no-unused-fields.md +6 -1
  33. package/docs/rules/no-unused-fragments.md +6 -1
  34. package/docs/rules/no-unused-variables.md +6 -1
  35. package/docs/rules/one-field-subscriptions.md +6 -1
  36. package/docs/rules/overlapping-fields-can-be-merged.md +6 -1
  37. package/docs/rules/possible-fragment-spread.md +6 -1
  38. package/docs/rules/possible-type-extension.md +6 -1
  39. package/docs/rules/provided-required-arguments.md +6 -1
  40. package/docs/rules/require-deprecation-date.md +5 -4
  41. package/docs/rules/require-deprecation-reason.md +6 -1
  42. package/docs/rules/require-description.md +5 -8
  43. package/docs/rules/require-field-of-type-query-in-mutation-result.md +6 -1
  44. package/docs/rules/require-id-when-available.md +6 -5
  45. package/docs/rules/scalar-leafs.md +6 -1
  46. package/docs/rules/selection-set-depth.md +6 -9
  47. package/docs/rules/strict-id-in-types.md +12 -11
  48. package/docs/rules/unique-argument-names.md +6 -1
  49. package/docs/rules/unique-directive-names-per-location.md +6 -1
  50. package/docs/rules/unique-directive-names.md +6 -1
  51. package/docs/rules/unique-enum-value-names.md +6 -1
  52. package/docs/rules/unique-field-definition-names.md +6 -1
  53. package/docs/rules/unique-fragment-name.md +6 -1
  54. package/docs/rules/unique-input-field-names.md +6 -1
  55. package/docs/rules/unique-operation-name.md +6 -1
  56. package/docs/rules/unique-operation-types.md +6 -1
  57. package/docs/rules/unique-type-names.md +6 -1
  58. package/docs/rules/unique-variable-names.md +6 -1
  59. package/docs/rules/value-literals-of-correct-type.md +6 -1
  60. package/docs/rules/variables-are-input-types.md +6 -1
  61. package/docs/rules/variables-in-allowed-position.md +6 -1
  62. package/estree-parser/estree-ast.d.ts +2 -2
  63. package/index.js +466 -102
  64. package/index.mjs +466 -102
  65. package/package.json +1 -1
  66. package/rules/alphabetize.d.ts +17 -0
  67. package/rules/index.d.ts +7 -0
  68. package/testkit.d.ts +6 -4
  69. package/types.d.ts +3 -0
  70. package/utils.d.ts +4 -0
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`
@@ -65,6 +67,16 @@ const allConfig = {
65
67
  ...recommendedConfig,
66
68
  rules: {
67
69
  ...recommendedConfig.rules,
70
+ '@graphql-eslint/alphabetize': [
71
+ 'error',
72
+ {
73
+ fields: ['ObjectTypeDefinition', 'InterfaceTypeDefinition', 'InputObjectTypeDefinition'],
74
+ values: ['EnumTypeDefinition'],
75
+ selections: ['OperationDefinition', 'FragmentDefinition'],
76
+ variables: ['OperationDefinition'],
77
+ arguments: ['FieldDefinition', 'Field', 'DirectiveDefinition', 'Directive'],
78
+ },
79
+ ],
68
80
  '@graphql-eslint/avoid-duplicate-fields': 'error',
69
81
  '@graphql-eslint/avoid-operation-name-prefix': 'error',
70
82
  '@graphql-eslint/avoid-scalar-result-type-on-mutation': 'error',
@@ -226,6 +238,24 @@ const convertCase = (style, str) => {
226
238
  return lowerCase(str).replace(/ /g, '-');
227
239
  }
228
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
+ }
229
259
 
230
260
  function extractRuleName(stack) {
231
261
  const match = (stack || '').match(/validation[/\\\\]rules[/\\\\](.*?)\.js:/) || [];
@@ -276,6 +306,7 @@ const validationToRule = (name, ruleName, docs, getDocumentNode) => {
276
306
  meta: {
277
307
  docs: {
278
308
  ...docs,
309
+ graphQLJSRuleName: ruleName,
279
310
  category: 'Validation',
280
311
  recommended: true,
281
312
  requiresSchema,
@@ -399,7 +430,7 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
399
430
  description: `A GraphQL fragment is only valid when it does not have cycles in fragments usage.`,
400
431
  }), validationToRule('no-undefined-variables', 'NoUndefinedVariables', {
401
432
  description: `A GraphQL operation is only valid if all variables encountered, both directly and via fragment spreads, are defined by that operation.`,
402
- }), validationToRule('no-unused-fragments', 'NoUnusedFragments', {
433
+ }, importFiles), validationToRule('no-unused-fragments', 'NoUnusedFragments', {
403
434
  description: `A GraphQL document is only valid if all fragment definitions are spread within operations, or spread within other fragments spread within operations.`,
404
435
  requiresSiblings: true,
405
436
  }, context => {
@@ -475,6 +506,237 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
475
506
  description: `Variables passed to field arguments conform to type.`,
476
507
  }));
477
508
 
509
+ const ALPHABETIZE = 'ALPHABETIZE';
510
+ const fieldsEnum = [Kind.OBJECT_TYPE_DEFINITION, Kind.INTERFACE_TYPE_DEFINITION, Kind.INPUT_OBJECT_TYPE_DEFINITION];
511
+ const valuesEnum = [Kind.ENUM_TYPE_DEFINITION];
512
+ const selectionsEnum = [Kind.OPERATION_DEFINITION, Kind.FRAGMENT_DEFINITION];
513
+ const variablesEnum = [Kind.OPERATION_DEFINITION];
514
+ const argumentsEnum = [Kind.FIELD_DEFINITION, Kind.FIELD, Kind.DIRECTIVE_DEFINITION, Kind.DIRECTIVE];
515
+ const rule = {
516
+ meta: {
517
+ type: 'suggestion',
518
+ docs: {
519
+ category: 'Best Practices',
520
+ description: 'Enforce arrange in alphabetical order for type fields, enum values, input object fields, operation selections and more.',
521
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/alphabetize.md',
522
+ examples: [
523
+ {
524
+ title: 'Incorrect',
525
+ usage: [{ fields: [Kind.OBJECT_TYPE_DEFINITION] }],
526
+ code: /* GraphQL */ `
527
+ type User {
528
+ password: String
529
+ firstName: String! # should be before "password"
530
+ age: Int # should be before "firstName"
531
+ lastName: String!
532
+ }
533
+ `,
534
+ },
535
+ {
536
+ title: 'Correct',
537
+ usage: [{ fields: [Kind.OBJECT_TYPE_DEFINITION] }],
538
+ code: /* GraphQL */ `
539
+ type User {
540
+ age: Int
541
+ firstName: String!
542
+ lastName: String!
543
+ password: String
544
+ }
545
+ `,
546
+ },
547
+ {
548
+ title: 'Incorrect',
549
+ usage: [{ values: [Kind.ENUM_TYPE_DEFINITION] }],
550
+ code: /* GraphQL */ `
551
+ enum Role {
552
+ SUPER_ADMIN
553
+ ADMIN # should be before "SUPER_ADMIN"
554
+ USER
555
+ GOD # should be before "USER"
556
+ }
557
+ `,
558
+ },
559
+ {
560
+ title: 'Correct',
561
+ usage: [{ values: [Kind.ENUM_TYPE_DEFINITION] }],
562
+ code: /* GraphQL */ `
563
+ enum Role {
564
+ ADMIN
565
+ GOD
566
+ SUPER_ADMIN
567
+ USER
568
+ }
569
+ `,
570
+ },
571
+ {
572
+ title: 'Incorrect',
573
+ usage: [{ selections: [Kind.OPERATION_DEFINITION] }],
574
+ code: /* GraphQL */ `
575
+ query {
576
+ me {
577
+ firstName
578
+ lastName
579
+ email # should be before "lastName"
580
+ }
581
+ }
582
+ `,
583
+ },
584
+ {
585
+ title: 'Correct',
586
+ usage: [{ selections: [Kind.OPERATION_DEFINITION] }],
587
+ code: /* GraphQL */ `
588
+ query {
589
+ me {
590
+ email
591
+ firstName
592
+ lastName
593
+ }
594
+ }
595
+ `,
596
+ },
597
+ ],
598
+ optionsForConfig: [
599
+ {
600
+ fields: fieldsEnum,
601
+ values: valuesEnum,
602
+ selections: selectionsEnum,
603
+ variables: variablesEnum,
604
+ arguments: argumentsEnum,
605
+ },
606
+ ],
607
+ },
608
+ messages: {
609
+ [ALPHABETIZE]: '"{{ currName }}" should be before "{{ prevName }}".',
610
+ },
611
+ schema: {
612
+ type: 'array',
613
+ minItems: 1,
614
+ maxItems: 1,
615
+ items: {
616
+ type: 'object',
617
+ additionalProperties: false,
618
+ minProperties: 1,
619
+ properties: {
620
+ fields: {
621
+ type: 'array',
622
+ contains: {
623
+ enum: fieldsEnum,
624
+ },
625
+ description: 'Fields of `type`, `interface`, and `input`.',
626
+ },
627
+ values: {
628
+ type: 'array',
629
+ contains: {
630
+ enum: valuesEnum,
631
+ },
632
+ description: 'Values of `enum`.',
633
+ },
634
+ selections: {
635
+ type: 'array',
636
+ contains: {
637
+ enum: selectionsEnum,
638
+ },
639
+ description: 'Selections of operations (`query`, `mutation` and `subscription`) and `fragment`.',
640
+ },
641
+ variables: {
642
+ type: 'array',
643
+ contains: {
644
+ enum: variablesEnum,
645
+ },
646
+ description: 'Variables of operations (`query`, `mutation` and `subscription`).',
647
+ },
648
+ arguments: {
649
+ type: 'array',
650
+ contains: {
651
+ enum: argumentsEnum,
652
+ },
653
+ description: 'Arguments of fields and directives.',
654
+ },
655
+ },
656
+ },
657
+ },
658
+ },
659
+ create(context) {
660
+ var _a, _b, _c, _d, _e;
661
+ function checkNodes(nodes) {
662
+ let prevName = null;
663
+ for (const node of nodes) {
664
+ const currName = node.name.value;
665
+ if (prevName && prevName > currName) {
666
+ const { start, end } = node.name.loc;
667
+ const isVariableNode = node.kind === Kind.VARIABLE;
668
+ context.report({
669
+ loc: {
670
+ start: {
671
+ line: start.line,
672
+ column: start.column - (isVariableNode ? 2 : 1),
673
+ },
674
+ end,
675
+ },
676
+ messageId: ALPHABETIZE,
677
+ data: isVariableNode
678
+ ? {
679
+ currName: `$${currName}`,
680
+ prevName: `$${prevName}`,
681
+ }
682
+ : { currName, prevName },
683
+ });
684
+ }
685
+ prevName = currName;
686
+ }
687
+ }
688
+ const opts = context.options[0];
689
+ const fields = new Set((_a = opts.fields) !== null && _a !== void 0 ? _a : []);
690
+ const listeners = {};
691
+ const fieldsSelector = [
692
+ fields.has(Kind.OBJECT_TYPE_DEFINITION) && [Kind.OBJECT_TYPE_DEFINITION, Kind.OBJECT_TYPE_EXTENSION],
693
+ fields.has(Kind.INTERFACE_TYPE_DEFINITION) && [Kind.INTERFACE_TYPE_DEFINITION, Kind.INTERFACE_TYPE_EXTENSION],
694
+ fields.has(Kind.INPUT_OBJECT_TYPE_DEFINITION) && [
695
+ Kind.INPUT_OBJECT_TYPE_DEFINITION,
696
+ Kind.INPUT_OBJECT_TYPE_EXTENSION,
697
+ ],
698
+ ]
699
+ .flat()
700
+ .join(',');
701
+ const hasEnumValues = ((_b = opts.values) === null || _b === void 0 ? void 0 : _b[0]) === Kind.ENUM_TYPE_DEFINITION;
702
+ const selectionsSelector = (_c = opts.selections) === null || _c === void 0 ? void 0 : _c.join(',');
703
+ const hasVariables = ((_d = opts.variables) === null || _d === void 0 ? void 0 : _d[0]) === Kind.OPERATION_DEFINITION;
704
+ const argumentsSelector = (_e = opts.arguments) === null || _e === void 0 ? void 0 : _e.join(',');
705
+ if (fieldsSelector) {
706
+ listeners[fieldsSelector] = (node) => {
707
+ checkNodes(node.fields);
708
+ };
709
+ }
710
+ if (hasEnumValues) {
711
+ const enumValuesSelector = [Kind.ENUM_TYPE_DEFINITION, Kind.ENUM_TYPE_EXTENSION].join(',');
712
+ listeners[enumValuesSelector] = (node) => {
713
+ checkNodes(node.values);
714
+ };
715
+ }
716
+ if (selectionsSelector) {
717
+ listeners[`:matches(${selectionsSelector}) SelectionSet`] = (node) => {
718
+ checkNodes(node.selections
719
+ // inline fragment don't have name, so we skip them
720
+ .filter(selection => selection.kind !== Kind.INLINE_FRAGMENT)
721
+ .map(selection =>
722
+ // sort by alias is field is renamed
723
+ 'alias' in selection && selection.alias ? { name: selection.alias } : selection));
724
+ };
725
+ }
726
+ if (hasVariables) {
727
+ listeners.OperationDefinition = (node) => {
728
+ checkNodes(node.variableDefinitions.map(varDef => varDef.variable));
729
+ };
730
+ }
731
+ if (argumentsSelector) {
732
+ listeners[argumentsSelector] = (node) => {
733
+ checkNodes(node.arguments);
734
+ };
735
+ }
736
+ return listeners;
737
+ },
738
+ };
739
+
478
740
  const AVOID_DUPLICATE_FIELDS = 'AVOID_DUPLICATE_FIELDS';
479
741
  const ensureUnique = () => {
480
742
  const set = new Set();
@@ -489,7 +751,7 @@ const ensureUnique = () => {
489
751
  },
490
752
  };
491
753
  };
492
- const rule = {
754
+ const rule$1 = {
493
755
  meta: {
494
756
  type: 'suggestion',
495
757
  docs: {
@@ -540,6 +802,7 @@ const rule = {
540
802
  messages: {
541
803
  [AVOID_DUPLICATE_FIELDS]: `{{ type }} "{{ fieldName }}" defined multiple times.`,
542
804
  },
805
+ schema: [],
543
806
  },
544
807
  create(context) {
545
808
  return {
@@ -597,7 +860,7 @@ const rule = {
597
860
  };
598
861
 
599
862
  const AVOID_OPERATION_NAME_PREFIX = 'AVOID_OPERATION_NAME_PREFIX';
600
- const rule$1 = {
863
+ const rule$2 = {
601
864
  meta: {
602
865
  type: 'suggestion',
603
866
  docs: {
@@ -659,15 +922,16 @@ const rule$1 = {
659
922
  const testKeyword = caseSensitive ? keyword : keyword.toLowerCase();
660
923
  const testName = caseSensitive ? node.name.value : node.name.value.toLowerCase();
661
924
  if (testName.startsWith(testKeyword)) {
925
+ const { start } = node.name.loc;
662
926
  context.report({
663
927
  loc: {
664
928
  start: {
665
- line: node.name.loc.start.line,
666
- column: node.name.loc.start.column - 1,
929
+ line: start.line,
930
+ column: start.column - 1,
667
931
  },
668
932
  end: {
669
- line: node.name.loc.start.line,
670
- column: node.name.loc.start.column + testKeyword.length - 1,
933
+ line: start.line,
934
+ column: start.column - 1 + testKeyword.length,
671
935
  },
672
936
  },
673
937
  data: {
@@ -683,7 +947,7 @@ const rule$1 = {
683
947
  },
684
948
  };
685
949
 
686
- const rule$2 = {
950
+ const rule$3 = {
687
951
  meta: {
688
952
  type: 'suggestion',
689
953
  docs: {
@@ -710,6 +974,7 @@ const rule$2 = {
710
974
  },
711
975
  ],
712
976
  },
977
+ schema: [],
713
978
  },
714
979
  create(context) {
715
980
  const schema = requireGraphQLSchemaFromContext('avoid-scalar-result-type-on-mutation', context);
@@ -735,7 +1000,7 @@ const rule$2 = {
735
1000
  };
736
1001
 
737
1002
  const AVOID_TYPENAME_PREFIX = 'AVOID_TYPENAME_PREFIX';
738
- const rule$3 = {
1003
+ const rule$4 = {
739
1004
  meta: {
740
1005
  type: 'suggestion',
741
1006
  docs: {
@@ -765,22 +1030,33 @@ const rule$3 = {
765
1030
  messages: {
766
1031
  [AVOID_TYPENAME_PREFIX]: `Field "{{ fieldName }}" starts with the name of the parent type "{{ typeName }}"`,
767
1032
  },
1033
+ schema: [],
768
1034
  },
769
1035
  create(context) {
770
1036
  return {
771
1037
  'ObjectTypeDefinition, ObjectTypeExtension, InterfaceTypeDefinition, InterfaceTypeExtension'(node) {
772
1038
  const typeName = node.name.value;
773
- const lowerTypeName = (typeName || '').toLowerCase();
1039
+ const lowerTypeName = typeName.toLowerCase();
774
1040
  for (const field of node.fields) {
775
- const fieldName = field.name.value || '';
776
- if (fieldName && lowerTypeName && fieldName.toLowerCase().startsWith(lowerTypeName)) {
1041
+ const fieldName = field.name.value;
1042
+ if (fieldName.toLowerCase().startsWith(lowerTypeName)) {
1043
+ const { start } = field.loc;
777
1044
  context.report({
778
- node: field.name,
779
1045
  data: {
780
1046
  fieldName,
781
1047
  typeName,
782
1048
  },
783
1049
  messageId: AVOID_TYPENAME_PREFIX,
1050
+ loc: {
1051
+ start: {
1052
+ line: start.line,
1053
+ column: start.column - 1,
1054
+ },
1055
+ end: {
1056
+ line: start.line,
1057
+ column: start.column - 1 + lowerTypeName.length,
1058
+ },
1059
+ },
784
1060
  });
785
1061
  }
786
1062
  }
@@ -789,7 +1065,7 @@ const rule$3 = {
789
1065
  },
790
1066
  };
791
1067
 
792
- const rule$4 = {
1068
+ const rule$5 = {
793
1069
  meta: {
794
1070
  type: 'suggestion',
795
1071
  docs: {
@@ -800,9 +1076,9 @@ const rule$4 = {
800
1076
  code: /* GraphQL */ `
801
1077
  """ Description """
802
1078
  type someTypeName {
803
- ...
1079
+ # ...
804
1080
  }
805
- `,
1081
+ `,
806
1082
  },
807
1083
  {
808
1084
  title: 'Correct',
@@ -810,9 +1086,9 @@ const rule$4 = {
810
1086
  code: /* GraphQL */ `
811
1087
  " Description "
812
1088
  type someTypeName {
813
- ...
1089
+ # ...
814
1090
  }
815
- `,
1091
+ `,
816
1092
  },
817
1093
  ],
818
1094
  description: 'Require all comments to follow the same style (either block or inline).',
@@ -849,7 +1125,7 @@ const rule$4 = {
849
1125
  },
850
1126
  };
851
1127
 
852
- const rule$5 = {
1128
+ const rule$6 = {
853
1129
  meta: {
854
1130
  type: 'suggestion',
855
1131
  docs: {
@@ -974,7 +1250,7 @@ const CASE_STYLES = [
974
1250
  const schemaOption = {
975
1251
  oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
976
1252
  };
977
- const rule$6 = {
1253
+ const rule$7 = {
978
1254
  meta: {
979
1255
  type: 'suggestion',
980
1256
  docs: {
@@ -1108,7 +1384,8 @@ const rule$6 = {
1108
1384
  var _a;
1109
1385
  if (options.fileExtension && options.fileExtension !== fileExtension) {
1110
1386
  context.report({
1111
- node: documentNode,
1387
+ // Report on first character
1388
+ loc: { column: 0, line: 1 },
1112
1389
  messageId: MATCH_EXTENSION,
1113
1390
  data: {
1114
1391
  fileExtension,
@@ -1140,7 +1417,8 @@ const rule$6 = {
1140
1417
  const filenameWithExtension = filename + expectedExtension;
1141
1418
  if (expectedFilename !== filenameWithExtension) {
1142
1419
  context.report({
1143
- node: documentNode,
1420
+ // Report on first character
1421
+ loc: { column: 0, line: 1 },
1144
1422
  messageId: MATCH_STYLE,
1145
1423
  data: {
1146
1424
  expectedFilename,
@@ -1219,7 +1497,7 @@ function checkNameFormat(params) {
1219
1497
  const schemaOption$1 = {
1220
1498
  oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
1221
1499
  };
1222
- const rule$7 = {
1500
+ const rule$8 = {
1223
1501
  meta: {
1224
1502
  type: 'suggestion',
1225
1503
  docs: {
@@ -1331,18 +1609,18 @@ const rule$7 = {
1331
1609
  style,
1332
1610
  leadingUnderscore: options.leadingUnderscore,
1333
1611
  trailingUnderscore: options.trailingUnderscore,
1334
- prefix: prefix,
1335
- suffix: suffix,
1336
- forbiddenPrefixes: forbiddenPrefixes,
1337
- forbiddenSuffixes: forbiddenSuffixes,
1612
+ prefix,
1613
+ suffix,
1614
+ forbiddenPrefixes,
1615
+ forbiddenSuffixes,
1338
1616
  });
1339
1617
  if (result.ok === false) {
1340
1618
  context.report({
1341
1619
  node,
1342
1620
  message: result.errorMessage,
1343
1621
  data: {
1344
- prefix: prefix,
1345
- suffix: suffix,
1622
+ prefix,
1623
+ suffix,
1346
1624
  format: style,
1347
1625
  forbiddenPrefixes: forbiddenPrefixes.join(', '),
1348
1626
  forbiddenSuffixes: forbiddenSuffixes.join(', '),
@@ -1448,7 +1726,7 @@ const rule$7 = {
1448
1726
  };
1449
1727
 
1450
1728
  const NO_ANONYMOUS_OPERATIONS = 'NO_ANONYMOUS_OPERATIONS';
1451
- const rule$8 = {
1729
+ const rule$9 = {
1452
1730
  meta: {
1453
1731
  type: 'suggestion',
1454
1732
  docs: {
@@ -1478,20 +1756,24 @@ const rule$8 = {
1478
1756
  messages: {
1479
1757
  [NO_ANONYMOUS_OPERATIONS]: `Anonymous GraphQL operations are forbidden. Please make sure to name your {{ operation }}!`,
1480
1758
  },
1759
+ schema: [],
1481
1760
  },
1482
1761
  create(context) {
1483
1762
  return {
1484
1763
  OperationDefinition(node) {
1485
- if (node && (!node.name || node.name.value === '')) {
1764
+ var _a;
1765
+ const isAnonymous = (((_a = node.name) === null || _a === void 0 ? void 0 : _a.value) || '').length === 0;
1766
+ if (isAnonymous) {
1767
+ const { start } = node.loc;
1486
1768
  context.report({
1487
1769
  loc: {
1488
1770
  start: {
1489
- column: node.loc.start.column - 1,
1490
- line: node.loc.start.line,
1771
+ column: start.column - 1,
1772
+ line: start.line,
1491
1773
  },
1492
1774
  end: {
1493
- column: node.loc.start.column + node.operation.length,
1494
- line: node.loc.start.line,
1775
+ column: start.column - 1 + node.operation.length,
1776
+ line: start.line,
1495
1777
  },
1496
1778
  },
1497
1779
  data: {
@@ -1506,7 +1788,7 @@ const rule$8 = {
1506
1788
  };
1507
1789
 
1508
1790
  const ERROR_MESSAGE_ID = 'NO_CASE_INSENSITIVE_ENUM_VALUES_DUPLICATES';
1509
- const rule$9 = {
1791
+ const rule$a = {
1510
1792
  meta: {
1511
1793
  type: 'suggestion',
1512
1794
  docs: {
@@ -1541,6 +1823,7 @@ const rule$9 = {
1541
1823
  messages: {
1542
1824
  [ERROR_MESSAGE_ID]: `Case-insensitive enum values duplicates are not allowed! Found: "{{ found }}"`,
1543
1825
  },
1826
+ schema: [],
1544
1827
  },
1545
1828
  create(context) {
1546
1829
  return {
@@ -1561,7 +1844,7 @@ const rule$9 = {
1561
1844
  };
1562
1845
 
1563
1846
  const NO_DEPRECATED = 'NO_DEPRECATED';
1564
- const rule$a = {
1847
+ const rule$b = {
1565
1848
  meta: {
1566
1849
  type: 'suggestion',
1567
1850
  docs: {
@@ -1635,6 +1918,7 @@ const rule$a = {
1635
1918
  messages: {
1636
1919
  [NO_DEPRECATED]: `This {{ type }} is marked as deprecated in your GraphQL schema {{ reason }}`,
1637
1920
  },
1921
+ schema: [],
1638
1922
  },
1639
1923
  create(context) {
1640
1924
  return {
@@ -1675,7 +1959,7 @@ const rule$a = {
1675
1959
  };
1676
1960
 
1677
1961
  const HASHTAG_COMMENT = 'HASHTAG_COMMENT';
1678
- const rule$b = {
1962
+ const rule$c = {
1679
1963
  meta: {
1680
1964
  messages: {
1681
1965
  [HASHTAG_COMMENT]: 'Using hashtag (#) for adding GraphQL descriptions is not allowed. Prefer using """ for multiline, or " for a single line description.',
@@ -1721,6 +2005,7 @@ const rule$b = {
1721
2005
  ],
1722
2006
  },
1723
2007
  type: 'suggestion',
2008
+ schema: [],
1724
2009
  },
1725
2010
  create(context) {
1726
2011
  return {
@@ -1750,7 +2035,7 @@ const rule$b = {
1750
2035
  };
1751
2036
 
1752
2037
  const NO_OPERATION_NAME_SUFFIX = 'NO_OPERATION_NAME_SUFFIX';
1753
- const rule$c = {
2038
+ const rule$d = {
1754
2039
  meta: {
1755
2040
  fixable: 'code',
1756
2041
  type: 'suggestion',
@@ -1781,15 +2066,28 @@ const rule$c = {
1781
2066
  messages: {
1782
2067
  [NO_OPERATION_NAME_SUFFIX]: `Unnecessary "{{ invalidSuffix }}" suffix in your operation name!`,
1783
2068
  },
2069
+ schema: [],
1784
2070
  },
1785
2071
  create(context) {
1786
2072
  return {
1787
2073
  'OperationDefinition, FragmentDefinition'(node) {
1788
- if (node && node.name && node.name.value !== '') {
1789
- const invalidSuffix = (node.type === 'OperationDefinition' ? node.operation : 'fragment').toLowerCase();
1790
- if (node.name.value.toLowerCase().endsWith(invalidSuffix)) {
2074
+ var _a;
2075
+ const name = ((_a = node.name) === null || _a === void 0 ? void 0 : _a.value) || '';
2076
+ if (name.length > 0) {
2077
+ const invalidSuffix = 'operation' in node ? node.operation : 'fragment';
2078
+ if (name.toLowerCase().endsWith(invalidSuffix)) {
2079
+ const { start, end } = node.name.loc;
1791
2080
  context.report({
1792
- node: node.name,
2081
+ loc: {
2082
+ start: {
2083
+ column: start.column - 1 + name.length - invalidSuffix.length,
2084
+ line: start.line,
2085
+ },
2086
+ end: {
2087
+ column: end.column - 1 + name.length,
2088
+ line: end.line,
2089
+ },
2090
+ },
1793
2091
  data: {
1794
2092
  invalidSuffix,
1795
2093
  },
@@ -1805,7 +2103,7 @@ const rule$c = {
1805
2103
 
1806
2104
  const UNREACHABLE_TYPE = 'UNREACHABLE_TYPE';
1807
2105
  const RULE_NAME = 'no-unreachable-types';
1808
- const rule$d = {
2106
+ const rule$e = {
1809
2107
  meta: {
1810
2108
  messages: {
1811
2109
  [UNREACHABLE_TYPE]: `Type "{{ typeName }}" is unreachable`,
@@ -1846,6 +2144,7 @@ const rule$d = {
1846
2144
  },
1847
2145
  fixable: 'code',
1848
2146
  type: 'suggestion',
2147
+ schema: [],
1849
2148
  },
1850
2149
  create(context) {
1851
2150
  const reachableTypes = requireReachableTypesFromContext(RULE_NAME, context);
@@ -1880,7 +2179,7 @@ const rule$d = {
1880
2179
 
1881
2180
  const UNUSED_FIELD = 'UNUSED_FIELD';
1882
2181
  const RULE_NAME$1 = 'no-unused-fields';
1883
- const rule$e = {
2182
+ const rule$f = {
1884
2183
  meta: {
1885
2184
  messages: {
1886
2185
  [UNUSED_FIELD]: `Field "{{fieldName}}" is unused`,
@@ -1937,6 +2236,7 @@ const rule$e = {
1937
2236
  },
1938
2237
  fixable: 'code',
1939
2238
  type: 'suggestion',
2239
+ schema: [],
1940
2240
  },
1941
2241
  create(context) {
1942
2242
  const usedFields = requireUsedFieldsFromContext(RULE_NAME$1, context);
@@ -2067,7 +2367,7 @@ const MESSAGE_REQUIRE_DATE = 'MESSAGE_REQUIRE_DATE';
2067
2367
  const MESSAGE_INVALID_FORMAT = 'MESSAGE_INVALID_FORMAT';
2068
2368
  const MESSAGE_INVALID_DATE = 'MESSAGE_INVALID_DATE';
2069
2369
  const MESSAGE_CAN_BE_REMOVED = 'MESSAGE_CAN_BE_REMOVED';
2070
- const rule$f = {
2370
+ const rule$g = {
2071
2371
  meta: {
2072
2372
  type: 'suggestion',
2073
2373
  docs: {
@@ -2167,7 +2467,7 @@ const rule$f = {
2167
2467
  },
2168
2468
  };
2169
2469
 
2170
- const rule$g = {
2470
+ const rule$h = {
2171
2471
  meta: {
2172
2472
  docs: {
2173
2473
  description: `Require all deprecation directives to specify a reason.`,
@@ -2202,6 +2502,7 @@ const rule$g = {
2202
2502
  ],
2203
2503
  },
2204
2504
  type: 'suggestion',
2505
+ schema: [],
2205
2506
  },
2206
2507
  create(context) {
2207
2508
  return {
@@ -2238,15 +2539,18 @@ const DESCRIBABLE_NODES = [
2238
2539
  function verifyRule(context, node) {
2239
2540
  if (node) {
2240
2541
  if (!node.description || !node.description.value || node.description.value.trim().length === 0) {
2542
+ const { start, end } = ('name' in node ? node.name : node).loc;
2241
2543
  context.report({
2242
2544
  loc: {
2243
2545
  start: {
2244
- line: node.loc.start.line,
2245
- column: node.loc.start.column - 1,
2546
+ line: start.line,
2547
+ column: start.column - 1,
2246
2548
  },
2247
2549
  end: {
2248
- line: node.loc.end.line,
2249
- column: node.loc.end.column,
2550
+ line: end.line,
2551
+ column:
2552
+ // node.name don't exist on SchemaDefinition
2553
+ 'name' in node ? end.column - 1 + node.name.value.length : end.column,
2250
2554
  },
2251
2555
  },
2252
2556
  messageId: REQUIRE_DESCRIPTION_ERROR,
@@ -2257,7 +2561,7 @@ function verifyRule(context, node) {
2257
2561
  }
2258
2562
  }
2259
2563
  }
2260
- const rule$h = {
2564
+ const rule$i = {
2261
2565
  meta: {
2262
2566
  docs: {
2263
2567
  category: 'Best Practices',
@@ -2322,7 +2626,7 @@ const rule$h = {
2322
2626
  };
2323
2627
 
2324
2628
  const RULE_NAME$2 = 'require-field-of-type-query-in-mutation-result';
2325
- const rule$i = {
2629
+ const rule$j = {
2326
2630
  meta: {
2327
2631
  type: 'suggestion',
2328
2632
  docs: {
@@ -2360,6 +2664,7 @@ const rule$i = {
2360
2664
  },
2361
2665
  ],
2362
2666
  },
2667
+ schema: [],
2363
2668
  },
2364
2669
  create(context) {
2365
2670
  const schema = requireGraphQLSchemaFromContext(RULE_NAME$2, context);
@@ -2485,7 +2790,7 @@ const convertNode = (typeInfo) => (node, key, parent) => {
2485
2790
 
2486
2791
  const REQUIRE_ID_WHEN_AVAILABLE = 'REQUIRE_ID_WHEN_AVAILABLE';
2487
2792
  const DEFAULT_ID_FIELD_NAME = 'id';
2488
- const rule$j = {
2793
+ const rule$k = {
2489
2794
  meta: {
2490
2795
  type: 'suggestion',
2491
2796
  docs: {
@@ -2621,7 +2926,7 @@ const rule$j = {
2621
2926
  },
2622
2927
  };
2623
2928
 
2624
- const rule$k = {
2929
+ const rule$l = {
2625
2930
  meta: {
2626
2931
  docs: {
2627
2932
  category: 'Best Practices',
@@ -2743,7 +3048,7 @@ const shouldIgnoreNode = ({ node, exceptions }) => {
2743
3048
  }
2744
3049
  return false;
2745
3050
  };
2746
- const rule$l = {
3051
+ const rule$m = {
2747
3052
  meta: {
2748
3053
  type: 'suggestion',
2749
3054
  docs: {
@@ -2896,11 +3201,7 @@ const rule$l = {
2896
3201
  const RULE_NAME$3 = 'unique-fragment-name';
2897
3202
  const UNIQUE_FRAGMENT_NAME = 'UNIQUE_FRAGMENT_NAME';
2898
3203
  const checkNode = (context, node, ruleName, messageId) => {
2899
- var _a;
2900
- const documentName = (_a = node.name) === null || _a === void 0 ? void 0 : _a.value;
2901
- if (!documentName) {
2902
- return;
2903
- }
3204
+ const documentName = node.name.value;
2904
3205
  const siblings = requireSiblingsOperations(ruleName, context);
2905
3206
  const siblingDocuments = node.kind === Kind.FRAGMENT_DEFINITION ? siblings.getFragment(documentName) : siblings.getOperation(documentName);
2906
3207
  const filepath = context.getFilename();
@@ -2911,7 +3212,6 @@ const checkNode = (context, node, ruleName, messageId) => {
2911
3212
  return isSameName && !isSamePath;
2912
3213
  });
2913
3214
  if (conflictingDocuments.length > 0) {
2914
- const { start, end } = node.name.loc;
2915
3215
  context.report({
2916
3216
  messageId,
2917
3217
  data: {
@@ -2920,20 +3220,11 @@ const checkNode = (context, node, ruleName, messageId) => {
2920
3220
  .map(f => `\t${relative(process.cwd(), getOnDiskFilepath(f.filePath))}`)
2921
3221
  .join('\n'),
2922
3222
  },
2923
- loc: {
2924
- start: {
2925
- line: start.line,
2926
- column: start.column - 1,
2927
- },
2928
- end: {
2929
- line: end.line,
2930
- column: end.column - 1,
2931
- },
2932
- },
3223
+ loc: getLocation(node.name.loc, documentName),
2933
3224
  });
2934
3225
  }
2935
3226
  };
2936
- const rule$m = {
3227
+ const rule$n = {
2937
3228
  meta: {
2938
3229
  type: 'suggestion',
2939
3230
  docs: {
@@ -2979,6 +3270,7 @@ const rule$m = {
2979
3270
  messages: {
2980
3271
  [UNIQUE_FRAGMENT_NAME]: 'Fragment named "{{ documentName }}" already defined in:\n{{ summary }}',
2981
3272
  },
3273
+ schema: [],
2982
3274
  },
2983
3275
  create(context) {
2984
3276
  return {
@@ -2991,7 +3283,7 @@ const rule$m = {
2991
3283
 
2992
3284
  const RULE_NAME$4 = 'unique-operation-name';
2993
3285
  const UNIQUE_OPERATION_NAME = 'UNIQUE_OPERATION_NAME';
2994
- const rule$n = {
3286
+ const rule$o = {
2995
3287
  meta: {
2996
3288
  type: 'suggestion',
2997
3289
  docs: {
@@ -3041,10 +3333,11 @@ const rule$n = {
3041
3333
  messages: {
3042
3334
  [UNIQUE_OPERATION_NAME]: 'Operation named "{{ documentName }}" already defined in:\n{{ summary }}',
3043
3335
  },
3336
+ schema: [],
3044
3337
  },
3045
3338
  create(context) {
3046
3339
  return {
3047
- OperationDefinition(node) {
3340
+ 'OperationDefinition[name!=undefined]'(node) {
3048
3341
  checkNode(context, node, RULE_NAME$4, UNIQUE_OPERATION_NAME);
3049
3342
  },
3050
3343
  };
@@ -3056,30 +3349,31 @@ const rule$n = {
3056
3349
  */
3057
3350
  const rules = {
3058
3351
  ...GRAPHQL_JS_VALIDATIONS,
3059
- 'avoid-duplicate-fields': rule,
3060
- 'avoid-operation-name-prefix': rule$1,
3061
- 'avoid-scalar-result-type-on-mutation': rule$2,
3062
- 'avoid-typename-prefix': rule$3,
3063
- 'description-style': rule$4,
3064
- 'input-name': rule$5,
3065
- 'match-document-filename': rule$6,
3066
- 'naming-convention': rule$7,
3067
- 'no-anonymous-operations': rule$8,
3068
- 'no-case-insensitive-enum-values-duplicates': rule$9,
3069
- 'no-deprecated': rule$a,
3070
- 'no-hashtag-description': rule$b,
3071
- 'no-operation-name-suffix': rule$c,
3072
- 'no-unreachable-types': rule$d,
3073
- 'no-unused-fields': rule$e,
3074
- 'require-deprecation-date': rule$f,
3075
- 'require-deprecation-reason': rule$g,
3076
- 'require-description': rule$h,
3077
- 'require-field-of-type-query-in-mutation-result': rule$i,
3078
- 'require-id-when-available': rule$j,
3079
- 'selection-set-depth': rule$k,
3080
- 'strict-id-in-types': rule$l,
3081
- 'unique-fragment-name': rule$m,
3082
- 'unique-operation-name': rule$n,
3352
+ alphabetize: rule,
3353
+ 'avoid-duplicate-fields': rule$1,
3354
+ 'avoid-operation-name-prefix': rule$2,
3355
+ 'avoid-scalar-result-type-on-mutation': rule$3,
3356
+ 'avoid-typename-prefix': rule$4,
3357
+ 'description-style': rule$5,
3358
+ 'input-name': rule$6,
3359
+ 'match-document-filename': rule$7,
3360
+ 'naming-convention': rule$8,
3361
+ 'no-anonymous-operations': rule$9,
3362
+ 'no-case-insensitive-enum-values-duplicates': rule$a,
3363
+ 'no-deprecated': rule$b,
3364
+ 'no-hashtag-description': rule$c,
3365
+ 'no-operation-name-suffix': rule$d,
3366
+ 'no-unreachable-types': rule$e,
3367
+ 'no-unused-fields': rule$f,
3368
+ 'require-deprecation-date': rule$g,
3369
+ 'require-deprecation-reason': rule$h,
3370
+ 'require-description': rule$i,
3371
+ 'require-field-of-type-query-in-mutation-result': rule$j,
3372
+ 'require-id-when-available': rule$k,
3373
+ 'selection-set-depth': rule$l,
3374
+ 'strict-id-in-types': rule$m,
3375
+ 'unique-fragment-name': rule$n,
3376
+ 'unique-operation-name': rule$o,
3083
3377
  };
3084
3378
 
3085
3379
  const RELEVANT_KEYWORDS = ['gql', 'graphql', '/* GraphQL */'];
@@ -3473,22 +3767,92 @@ function parseForESLint(code, options = {}) {
3473
3767
  }
3474
3768
  }
3475
3769
 
3476
- class GraphQLRuleTester extends require('eslint').RuleTester {
3770
+ class GraphQLRuleTester extends RuleTester {
3477
3771
  constructor(parserOptions = {}) {
3478
- super({
3772
+ const config = {
3479
3773
  parser: require.resolve('@graphql-eslint/eslint-plugin'),
3480
3774
  parserOptions: {
3481
3775
  ...parserOptions,
3482
3776
  skipGraphQLConfig: true,
3483
3777
  },
3484
- });
3778
+ };
3779
+ super(config);
3780
+ this.config = config;
3485
3781
  }
3486
3782
  fromMockFile(path) {
3487
3783
  return readFileSync(resolve(__dirname, `../tests/mocks/${path}`), 'utf-8');
3488
3784
  }
3489
3785
  runGraphQLTests(name, rule, tests) {
3490
3786
  super.run(name, rule, tests);
3787
+ // Skip snapshot testing if `expect` variable is not defined
3788
+ if (typeof expect === 'undefined') {
3789
+ return;
3790
+ }
3791
+ const linter = new Linter();
3792
+ linter.defineRule(name, rule);
3793
+ for (const testCase of tests.invalid) {
3794
+ const verifyConfig = getVerifyConfig(name, this.config, testCase);
3795
+ defineParser(linter, verifyConfig.parser);
3796
+ const { code, filename } = testCase;
3797
+ const messages = linter.verify(code, verifyConfig, { filename });
3798
+ for (const message of messages) {
3799
+ if (message.fatal) {
3800
+ throw new Error(message.message);
3801
+ }
3802
+ const messageForSnapshot = visualizeEslintMessage(code, message);
3803
+ // eslint-disable-next-line no-undef
3804
+ expect(messageForSnapshot).toMatchSnapshot();
3805
+ }
3806
+ }
3491
3807
  }
3492
3808
  }
3809
+ function getVerifyConfig(ruleId, testerConfig, testCase) {
3810
+ const { options, parserOptions, parser = testerConfig.parser } = testCase;
3811
+ return {
3812
+ ...testerConfig,
3813
+ parser,
3814
+ parserOptions: {
3815
+ ...testerConfig.parserOptions,
3816
+ ...parserOptions,
3817
+ },
3818
+ rules: {
3819
+ [ruleId]: ['error', ...(Array.isArray(options) ? options : [])],
3820
+ },
3821
+ };
3822
+ }
3823
+ const parsers = new WeakMap();
3824
+ function defineParser(linter, parser) {
3825
+ if (!parser) {
3826
+ return;
3827
+ }
3828
+ if (!parsers.has(linter)) {
3829
+ parsers.set(linter, new Set());
3830
+ }
3831
+ const defined = parsers.get(linter);
3832
+ if (!defined.has(parser)) {
3833
+ defined.add(parser);
3834
+ linter.defineParser(parser, require(parser));
3835
+ }
3836
+ }
3837
+ function visualizeEslintMessage(text, result) {
3838
+ const { line, column, endLine, endColumn, message } = result;
3839
+ const location = {
3840
+ start: {
3841
+ line,
3842
+ column,
3843
+ },
3844
+ };
3845
+ if (typeof endLine === 'number' && typeof endColumn === 'number') {
3846
+ location.end = {
3847
+ line: endLine,
3848
+ column: endColumn,
3849
+ };
3850
+ }
3851
+ return codeFrameColumns(text, location, {
3852
+ linesAbove: Number.POSITIVE_INFINITY,
3853
+ linesBelow: Number.POSITIVE_INFINITY,
3854
+ message,
3855
+ });
3856
+ }
3493
3857
 
3494
3858
  export { GraphQLRuleTester, configs, convertDescription, convertLocation, convertRange, convertToESTree, extractCommentsFromAst, getBaseType, isNodeWithDescription, parse, parseForESLint, processors, rules, valueFromNode };