@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.js CHANGED
@@ -15,6 +15,8 @@ const depthLimit = _interopDefault(require('graphql-depth-limit'));
15
15
  const graphqlTagPluck = require('@graphql-tools/graphql-tag-pluck');
16
16
  const graphqlConfig$1 = require('graphql-config');
17
17
  const codeFileLoader = require('@graphql-tools/code-file-loader');
18
+ const eslint = require('eslint');
19
+ const codeFrame = require('@babel/code-frame');
18
20
 
19
21
  /*
20
22
  * 🚨 IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`
@@ -71,6 +73,16 @@ const allConfig = {
71
73
  ...recommendedConfig,
72
74
  rules: {
73
75
  ...recommendedConfig.rules,
76
+ '@graphql-eslint/alphabetize': [
77
+ 'error',
78
+ {
79
+ fields: ['ObjectTypeDefinition', 'InterfaceTypeDefinition', 'InputObjectTypeDefinition'],
80
+ values: ['EnumTypeDefinition'],
81
+ selections: ['OperationDefinition', 'FragmentDefinition'],
82
+ variables: ['OperationDefinition'],
83
+ arguments: ['FieldDefinition', 'Field', 'DirectiveDefinition', 'Directive'],
84
+ },
85
+ ],
74
86
  '@graphql-eslint/avoid-duplicate-fields': 'error',
75
87
  '@graphql-eslint/avoid-operation-name-prefix': 'error',
76
88
  '@graphql-eslint/avoid-scalar-result-type-on-mutation': 'error',
@@ -232,6 +244,24 @@ const convertCase = (style, str) => {
232
244
  return lowerCase(str).replace(/ /g, '-');
233
245
  }
234
246
  };
247
+ function getLocation(loc, fieldName = '', offset) {
248
+ const { start } = loc;
249
+ /*
250
+ * ESLint has 0-based column number
251
+ * https://eslint.org/docs/developer-guide/working-with-rules#contextreport
252
+ */
253
+ const { offsetStart = 1, offsetEnd = 1 } = offset !== null && offset !== void 0 ? offset : {};
254
+ return {
255
+ start: {
256
+ line: start.line,
257
+ column: start.column - offsetStart,
258
+ },
259
+ end: {
260
+ line: start.line,
261
+ column: start.column - offsetEnd + fieldName.length,
262
+ },
263
+ };
264
+ }
235
265
 
236
266
  function extractRuleName(stack) {
237
267
  const match = (stack || '').match(/validation[/\\\\]rules[/\\\\](.*?)\.js:/) || [];
@@ -282,6 +312,7 @@ const validationToRule = (name, ruleName, docs, getDocumentNode) => {
282
312
  meta: {
283
313
  docs: {
284
314
  ...docs,
315
+ graphQLJSRuleName: ruleName,
285
316
  category: 'Validation',
286
317
  recommended: true,
287
318
  requiresSchema,
@@ -405,7 +436,7 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
405
436
  description: `A GraphQL fragment is only valid when it does not have cycles in fragments usage.`,
406
437
  }), validationToRule('no-undefined-variables', 'NoUndefinedVariables', {
407
438
  description: `A GraphQL operation is only valid if all variables encountered, both directly and via fragment spreads, are defined by that operation.`,
408
- }), validationToRule('no-unused-fragments', 'NoUnusedFragments', {
439
+ }, importFiles), validationToRule('no-unused-fragments', 'NoUnusedFragments', {
409
440
  description: `A GraphQL document is only valid if all fragment definitions are spread within operations, or spread within other fragments spread within operations.`,
410
441
  requiresSiblings: true,
411
442
  }, context => {
@@ -481,6 +512,237 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
481
512
  description: `Variables passed to field arguments conform to type.`,
482
513
  }));
483
514
 
515
+ const ALPHABETIZE = 'ALPHABETIZE';
516
+ const fieldsEnum = [graphql.Kind.OBJECT_TYPE_DEFINITION, graphql.Kind.INTERFACE_TYPE_DEFINITION, graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION];
517
+ const valuesEnum = [graphql.Kind.ENUM_TYPE_DEFINITION];
518
+ const selectionsEnum = [graphql.Kind.OPERATION_DEFINITION, graphql.Kind.FRAGMENT_DEFINITION];
519
+ const variablesEnum = [graphql.Kind.OPERATION_DEFINITION];
520
+ const argumentsEnum = [graphql.Kind.FIELD_DEFINITION, graphql.Kind.FIELD, graphql.Kind.DIRECTIVE_DEFINITION, graphql.Kind.DIRECTIVE];
521
+ const rule = {
522
+ meta: {
523
+ type: 'suggestion',
524
+ docs: {
525
+ category: 'Best Practices',
526
+ description: 'Enforce arrange in alphabetical order for type fields, enum values, input object fields, operation selections and more.',
527
+ url: 'https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/alphabetize.md',
528
+ examples: [
529
+ {
530
+ title: 'Incorrect',
531
+ usage: [{ fields: [graphql.Kind.OBJECT_TYPE_DEFINITION] }],
532
+ code: /* GraphQL */ `
533
+ type User {
534
+ password: String
535
+ firstName: String! # should be before "password"
536
+ age: Int # should be before "firstName"
537
+ lastName: String!
538
+ }
539
+ `,
540
+ },
541
+ {
542
+ title: 'Correct',
543
+ usage: [{ fields: [graphql.Kind.OBJECT_TYPE_DEFINITION] }],
544
+ code: /* GraphQL */ `
545
+ type User {
546
+ age: Int
547
+ firstName: String!
548
+ lastName: String!
549
+ password: String
550
+ }
551
+ `,
552
+ },
553
+ {
554
+ title: 'Incorrect',
555
+ usage: [{ values: [graphql.Kind.ENUM_TYPE_DEFINITION] }],
556
+ code: /* GraphQL */ `
557
+ enum Role {
558
+ SUPER_ADMIN
559
+ ADMIN # should be before "SUPER_ADMIN"
560
+ USER
561
+ GOD # should be before "USER"
562
+ }
563
+ `,
564
+ },
565
+ {
566
+ title: 'Correct',
567
+ usage: [{ values: [graphql.Kind.ENUM_TYPE_DEFINITION] }],
568
+ code: /* GraphQL */ `
569
+ enum Role {
570
+ ADMIN
571
+ GOD
572
+ SUPER_ADMIN
573
+ USER
574
+ }
575
+ `,
576
+ },
577
+ {
578
+ title: 'Incorrect',
579
+ usage: [{ selections: [graphql.Kind.OPERATION_DEFINITION] }],
580
+ code: /* GraphQL */ `
581
+ query {
582
+ me {
583
+ firstName
584
+ lastName
585
+ email # should be before "lastName"
586
+ }
587
+ }
588
+ `,
589
+ },
590
+ {
591
+ title: 'Correct',
592
+ usage: [{ selections: [graphql.Kind.OPERATION_DEFINITION] }],
593
+ code: /* GraphQL */ `
594
+ query {
595
+ me {
596
+ email
597
+ firstName
598
+ lastName
599
+ }
600
+ }
601
+ `,
602
+ },
603
+ ],
604
+ optionsForConfig: [
605
+ {
606
+ fields: fieldsEnum,
607
+ values: valuesEnum,
608
+ selections: selectionsEnum,
609
+ variables: variablesEnum,
610
+ arguments: argumentsEnum,
611
+ },
612
+ ],
613
+ },
614
+ messages: {
615
+ [ALPHABETIZE]: '"{{ currName }}" should be before "{{ prevName }}".',
616
+ },
617
+ schema: {
618
+ type: 'array',
619
+ minItems: 1,
620
+ maxItems: 1,
621
+ items: {
622
+ type: 'object',
623
+ additionalProperties: false,
624
+ minProperties: 1,
625
+ properties: {
626
+ fields: {
627
+ type: 'array',
628
+ contains: {
629
+ enum: fieldsEnum,
630
+ },
631
+ description: 'Fields of `type`, `interface`, and `input`.',
632
+ },
633
+ values: {
634
+ type: 'array',
635
+ contains: {
636
+ enum: valuesEnum,
637
+ },
638
+ description: 'Values of `enum`.',
639
+ },
640
+ selections: {
641
+ type: 'array',
642
+ contains: {
643
+ enum: selectionsEnum,
644
+ },
645
+ description: 'Selections of operations (`query`, `mutation` and `subscription`) and `fragment`.',
646
+ },
647
+ variables: {
648
+ type: 'array',
649
+ contains: {
650
+ enum: variablesEnum,
651
+ },
652
+ description: 'Variables of operations (`query`, `mutation` and `subscription`).',
653
+ },
654
+ arguments: {
655
+ type: 'array',
656
+ contains: {
657
+ enum: argumentsEnum,
658
+ },
659
+ description: 'Arguments of fields and directives.',
660
+ },
661
+ },
662
+ },
663
+ },
664
+ },
665
+ create(context) {
666
+ var _a, _b, _c, _d, _e;
667
+ function checkNodes(nodes) {
668
+ let prevName = null;
669
+ for (const node of nodes) {
670
+ const currName = node.name.value;
671
+ if (prevName && prevName > currName) {
672
+ const { start, end } = node.name.loc;
673
+ const isVariableNode = node.kind === graphql.Kind.VARIABLE;
674
+ context.report({
675
+ loc: {
676
+ start: {
677
+ line: start.line,
678
+ column: start.column - (isVariableNode ? 2 : 1),
679
+ },
680
+ end,
681
+ },
682
+ messageId: ALPHABETIZE,
683
+ data: isVariableNode
684
+ ? {
685
+ currName: `$${currName}`,
686
+ prevName: `$${prevName}`,
687
+ }
688
+ : { currName, prevName },
689
+ });
690
+ }
691
+ prevName = currName;
692
+ }
693
+ }
694
+ const opts = context.options[0];
695
+ const fields = new Set((_a = opts.fields) !== null && _a !== void 0 ? _a : []);
696
+ const listeners = {};
697
+ const fieldsSelector = [
698
+ fields.has(graphql.Kind.OBJECT_TYPE_DEFINITION) && [graphql.Kind.OBJECT_TYPE_DEFINITION, graphql.Kind.OBJECT_TYPE_EXTENSION],
699
+ fields.has(graphql.Kind.INTERFACE_TYPE_DEFINITION) && [graphql.Kind.INTERFACE_TYPE_DEFINITION, graphql.Kind.INTERFACE_TYPE_EXTENSION],
700
+ fields.has(graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION) && [
701
+ graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION,
702
+ graphql.Kind.INPUT_OBJECT_TYPE_EXTENSION,
703
+ ],
704
+ ]
705
+ .flat()
706
+ .join(',');
707
+ const hasEnumValues = ((_b = opts.values) === null || _b === void 0 ? void 0 : _b[0]) === graphql.Kind.ENUM_TYPE_DEFINITION;
708
+ const selectionsSelector = (_c = opts.selections) === null || _c === void 0 ? void 0 : _c.join(',');
709
+ const hasVariables = ((_d = opts.variables) === null || _d === void 0 ? void 0 : _d[0]) === graphql.Kind.OPERATION_DEFINITION;
710
+ const argumentsSelector = (_e = opts.arguments) === null || _e === void 0 ? void 0 : _e.join(',');
711
+ if (fieldsSelector) {
712
+ listeners[fieldsSelector] = (node) => {
713
+ checkNodes(node.fields);
714
+ };
715
+ }
716
+ if (hasEnumValues) {
717
+ const enumValuesSelector = [graphql.Kind.ENUM_TYPE_DEFINITION, graphql.Kind.ENUM_TYPE_EXTENSION].join(',');
718
+ listeners[enumValuesSelector] = (node) => {
719
+ checkNodes(node.values);
720
+ };
721
+ }
722
+ if (selectionsSelector) {
723
+ listeners[`:matches(${selectionsSelector}) SelectionSet`] = (node) => {
724
+ checkNodes(node.selections
725
+ // inline fragment don't have name, so we skip them
726
+ .filter(selection => selection.kind !== graphql.Kind.INLINE_FRAGMENT)
727
+ .map(selection =>
728
+ // sort by alias is field is renamed
729
+ 'alias' in selection && selection.alias ? { name: selection.alias } : selection));
730
+ };
731
+ }
732
+ if (hasVariables) {
733
+ listeners.OperationDefinition = (node) => {
734
+ checkNodes(node.variableDefinitions.map(varDef => varDef.variable));
735
+ };
736
+ }
737
+ if (argumentsSelector) {
738
+ listeners[argumentsSelector] = (node) => {
739
+ checkNodes(node.arguments);
740
+ };
741
+ }
742
+ return listeners;
743
+ },
744
+ };
745
+
484
746
  const AVOID_DUPLICATE_FIELDS = 'AVOID_DUPLICATE_FIELDS';
485
747
  const ensureUnique = () => {
486
748
  const set = new Set();
@@ -495,7 +757,7 @@ const ensureUnique = () => {
495
757
  },
496
758
  };
497
759
  };
498
- const rule = {
760
+ const rule$1 = {
499
761
  meta: {
500
762
  type: 'suggestion',
501
763
  docs: {
@@ -546,6 +808,7 @@ const rule = {
546
808
  messages: {
547
809
  [AVOID_DUPLICATE_FIELDS]: `{{ type }} "{{ fieldName }}" defined multiple times.`,
548
810
  },
811
+ schema: [],
549
812
  },
550
813
  create(context) {
551
814
  return {
@@ -603,7 +866,7 @@ const rule = {
603
866
  };
604
867
 
605
868
  const AVOID_OPERATION_NAME_PREFIX = 'AVOID_OPERATION_NAME_PREFIX';
606
- const rule$1 = {
869
+ const rule$2 = {
607
870
  meta: {
608
871
  type: 'suggestion',
609
872
  docs: {
@@ -665,15 +928,16 @@ const rule$1 = {
665
928
  const testKeyword = caseSensitive ? keyword : keyword.toLowerCase();
666
929
  const testName = caseSensitive ? node.name.value : node.name.value.toLowerCase();
667
930
  if (testName.startsWith(testKeyword)) {
931
+ const { start } = node.name.loc;
668
932
  context.report({
669
933
  loc: {
670
934
  start: {
671
- line: node.name.loc.start.line,
672
- column: node.name.loc.start.column - 1,
935
+ line: start.line,
936
+ column: start.column - 1,
673
937
  },
674
938
  end: {
675
- line: node.name.loc.start.line,
676
- column: node.name.loc.start.column + testKeyword.length - 1,
939
+ line: start.line,
940
+ column: start.column - 1 + testKeyword.length,
677
941
  },
678
942
  },
679
943
  data: {
@@ -689,7 +953,7 @@ const rule$1 = {
689
953
  },
690
954
  };
691
955
 
692
- const rule$2 = {
956
+ const rule$3 = {
693
957
  meta: {
694
958
  type: 'suggestion',
695
959
  docs: {
@@ -716,6 +980,7 @@ const rule$2 = {
716
980
  },
717
981
  ],
718
982
  },
983
+ schema: [],
719
984
  },
720
985
  create(context) {
721
986
  const schema = requireGraphQLSchemaFromContext('avoid-scalar-result-type-on-mutation', context);
@@ -741,7 +1006,7 @@ const rule$2 = {
741
1006
  };
742
1007
 
743
1008
  const AVOID_TYPENAME_PREFIX = 'AVOID_TYPENAME_PREFIX';
744
- const rule$3 = {
1009
+ const rule$4 = {
745
1010
  meta: {
746
1011
  type: 'suggestion',
747
1012
  docs: {
@@ -771,22 +1036,33 @@ const rule$3 = {
771
1036
  messages: {
772
1037
  [AVOID_TYPENAME_PREFIX]: `Field "{{ fieldName }}" starts with the name of the parent type "{{ typeName }}"`,
773
1038
  },
1039
+ schema: [],
774
1040
  },
775
1041
  create(context) {
776
1042
  return {
777
1043
  'ObjectTypeDefinition, ObjectTypeExtension, InterfaceTypeDefinition, InterfaceTypeExtension'(node) {
778
1044
  const typeName = node.name.value;
779
- const lowerTypeName = (typeName || '').toLowerCase();
1045
+ const lowerTypeName = typeName.toLowerCase();
780
1046
  for (const field of node.fields) {
781
- const fieldName = field.name.value || '';
782
- if (fieldName && lowerTypeName && fieldName.toLowerCase().startsWith(lowerTypeName)) {
1047
+ const fieldName = field.name.value;
1048
+ if (fieldName.toLowerCase().startsWith(lowerTypeName)) {
1049
+ const { start } = field.loc;
783
1050
  context.report({
784
- node: field.name,
785
1051
  data: {
786
1052
  fieldName,
787
1053
  typeName,
788
1054
  },
789
1055
  messageId: AVOID_TYPENAME_PREFIX,
1056
+ loc: {
1057
+ start: {
1058
+ line: start.line,
1059
+ column: start.column - 1,
1060
+ },
1061
+ end: {
1062
+ line: start.line,
1063
+ column: start.column - 1 + lowerTypeName.length,
1064
+ },
1065
+ },
790
1066
  });
791
1067
  }
792
1068
  }
@@ -795,7 +1071,7 @@ const rule$3 = {
795
1071
  },
796
1072
  };
797
1073
 
798
- const rule$4 = {
1074
+ const rule$5 = {
799
1075
  meta: {
800
1076
  type: 'suggestion',
801
1077
  docs: {
@@ -806,9 +1082,9 @@ const rule$4 = {
806
1082
  code: /* GraphQL */ `
807
1083
  """ Description """
808
1084
  type someTypeName {
809
- ...
1085
+ # ...
810
1086
  }
811
- `,
1087
+ `,
812
1088
  },
813
1089
  {
814
1090
  title: 'Correct',
@@ -816,9 +1092,9 @@ const rule$4 = {
816
1092
  code: /* GraphQL */ `
817
1093
  " Description "
818
1094
  type someTypeName {
819
- ...
1095
+ # ...
820
1096
  }
821
- `,
1097
+ `,
822
1098
  },
823
1099
  ],
824
1100
  description: 'Require all comments to follow the same style (either block or inline).',
@@ -855,7 +1131,7 @@ const rule$4 = {
855
1131
  },
856
1132
  };
857
1133
 
858
- const rule$5 = {
1134
+ const rule$6 = {
859
1135
  meta: {
860
1136
  type: 'suggestion',
861
1137
  docs: {
@@ -980,7 +1256,7 @@ const CASE_STYLES = [
980
1256
  const schemaOption = {
981
1257
  oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
982
1258
  };
983
- const rule$6 = {
1259
+ const rule$7 = {
984
1260
  meta: {
985
1261
  type: 'suggestion',
986
1262
  docs: {
@@ -1114,7 +1390,8 @@ const rule$6 = {
1114
1390
  var _a;
1115
1391
  if (options.fileExtension && options.fileExtension !== fileExtension) {
1116
1392
  context.report({
1117
- node: documentNode,
1393
+ // Report on first character
1394
+ loc: { column: 0, line: 1 },
1118
1395
  messageId: MATCH_EXTENSION,
1119
1396
  data: {
1120
1397
  fileExtension,
@@ -1146,7 +1423,8 @@ const rule$6 = {
1146
1423
  const filenameWithExtension = filename + expectedExtension;
1147
1424
  if (expectedFilename !== filenameWithExtension) {
1148
1425
  context.report({
1149
- node: documentNode,
1426
+ // Report on first character
1427
+ loc: { column: 0, line: 1 },
1150
1428
  messageId: MATCH_STYLE,
1151
1429
  data: {
1152
1430
  expectedFilename,
@@ -1225,7 +1503,7 @@ function checkNameFormat(params) {
1225
1503
  const schemaOption$1 = {
1226
1504
  oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asObject' }],
1227
1505
  };
1228
- const rule$7 = {
1506
+ const rule$8 = {
1229
1507
  meta: {
1230
1508
  type: 'suggestion',
1231
1509
  docs: {
@@ -1337,18 +1615,18 @@ const rule$7 = {
1337
1615
  style,
1338
1616
  leadingUnderscore: options.leadingUnderscore,
1339
1617
  trailingUnderscore: options.trailingUnderscore,
1340
- prefix: prefix,
1341
- suffix: suffix,
1342
- forbiddenPrefixes: forbiddenPrefixes,
1343
- forbiddenSuffixes: forbiddenSuffixes,
1618
+ prefix,
1619
+ suffix,
1620
+ forbiddenPrefixes,
1621
+ forbiddenSuffixes,
1344
1622
  });
1345
1623
  if (result.ok === false) {
1346
1624
  context.report({
1347
1625
  node,
1348
1626
  message: result.errorMessage,
1349
1627
  data: {
1350
- prefix: prefix,
1351
- suffix: suffix,
1628
+ prefix,
1629
+ suffix,
1352
1630
  format: style,
1353
1631
  forbiddenPrefixes: forbiddenPrefixes.join(', '),
1354
1632
  forbiddenSuffixes: forbiddenSuffixes.join(', '),
@@ -1454,7 +1732,7 @@ const rule$7 = {
1454
1732
  };
1455
1733
 
1456
1734
  const NO_ANONYMOUS_OPERATIONS = 'NO_ANONYMOUS_OPERATIONS';
1457
- const rule$8 = {
1735
+ const rule$9 = {
1458
1736
  meta: {
1459
1737
  type: 'suggestion',
1460
1738
  docs: {
@@ -1484,20 +1762,24 @@ const rule$8 = {
1484
1762
  messages: {
1485
1763
  [NO_ANONYMOUS_OPERATIONS]: `Anonymous GraphQL operations are forbidden. Please make sure to name your {{ operation }}!`,
1486
1764
  },
1765
+ schema: [],
1487
1766
  },
1488
1767
  create(context) {
1489
1768
  return {
1490
1769
  OperationDefinition(node) {
1491
- if (node && (!node.name || node.name.value === '')) {
1770
+ var _a;
1771
+ const isAnonymous = (((_a = node.name) === null || _a === void 0 ? void 0 : _a.value) || '').length === 0;
1772
+ if (isAnonymous) {
1773
+ const { start } = node.loc;
1492
1774
  context.report({
1493
1775
  loc: {
1494
1776
  start: {
1495
- column: node.loc.start.column - 1,
1496
- line: node.loc.start.line,
1777
+ column: start.column - 1,
1778
+ line: start.line,
1497
1779
  },
1498
1780
  end: {
1499
- column: node.loc.start.column + node.operation.length,
1500
- line: node.loc.start.line,
1781
+ column: start.column - 1 + node.operation.length,
1782
+ line: start.line,
1501
1783
  },
1502
1784
  },
1503
1785
  data: {
@@ -1512,7 +1794,7 @@ const rule$8 = {
1512
1794
  };
1513
1795
 
1514
1796
  const ERROR_MESSAGE_ID = 'NO_CASE_INSENSITIVE_ENUM_VALUES_DUPLICATES';
1515
- const rule$9 = {
1797
+ const rule$a = {
1516
1798
  meta: {
1517
1799
  type: 'suggestion',
1518
1800
  docs: {
@@ -1547,6 +1829,7 @@ const rule$9 = {
1547
1829
  messages: {
1548
1830
  [ERROR_MESSAGE_ID]: `Case-insensitive enum values duplicates are not allowed! Found: "{{ found }}"`,
1549
1831
  },
1832
+ schema: [],
1550
1833
  },
1551
1834
  create(context) {
1552
1835
  return {
@@ -1567,7 +1850,7 @@ const rule$9 = {
1567
1850
  };
1568
1851
 
1569
1852
  const NO_DEPRECATED = 'NO_DEPRECATED';
1570
- const rule$a = {
1853
+ const rule$b = {
1571
1854
  meta: {
1572
1855
  type: 'suggestion',
1573
1856
  docs: {
@@ -1641,6 +1924,7 @@ const rule$a = {
1641
1924
  messages: {
1642
1925
  [NO_DEPRECATED]: `This {{ type }} is marked as deprecated in your GraphQL schema {{ reason }}`,
1643
1926
  },
1927
+ schema: [],
1644
1928
  },
1645
1929
  create(context) {
1646
1930
  return {
@@ -1681,7 +1965,7 @@ const rule$a = {
1681
1965
  };
1682
1966
 
1683
1967
  const HASHTAG_COMMENT = 'HASHTAG_COMMENT';
1684
- const rule$b = {
1968
+ const rule$c = {
1685
1969
  meta: {
1686
1970
  messages: {
1687
1971
  [HASHTAG_COMMENT]: 'Using hashtag (#) for adding GraphQL descriptions is not allowed. Prefer using """ for multiline, or " for a single line description.',
@@ -1727,6 +2011,7 @@ const rule$b = {
1727
2011
  ],
1728
2012
  },
1729
2013
  type: 'suggestion',
2014
+ schema: [],
1730
2015
  },
1731
2016
  create(context) {
1732
2017
  return {
@@ -1756,7 +2041,7 @@ const rule$b = {
1756
2041
  };
1757
2042
 
1758
2043
  const NO_OPERATION_NAME_SUFFIX = 'NO_OPERATION_NAME_SUFFIX';
1759
- const rule$c = {
2044
+ const rule$d = {
1760
2045
  meta: {
1761
2046
  fixable: 'code',
1762
2047
  type: 'suggestion',
@@ -1787,15 +2072,28 @@ const rule$c = {
1787
2072
  messages: {
1788
2073
  [NO_OPERATION_NAME_SUFFIX]: `Unnecessary "{{ invalidSuffix }}" suffix in your operation name!`,
1789
2074
  },
2075
+ schema: [],
1790
2076
  },
1791
2077
  create(context) {
1792
2078
  return {
1793
2079
  'OperationDefinition, FragmentDefinition'(node) {
1794
- if (node && node.name && node.name.value !== '') {
1795
- const invalidSuffix = (node.type === 'OperationDefinition' ? node.operation : 'fragment').toLowerCase();
1796
- if (node.name.value.toLowerCase().endsWith(invalidSuffix)) {
2080
+ var _a;
2081
+ const name = ((_a = node.name) === null || _a === void 0 ? void 0 : _a.value) || '';
2082
+ if (name.length > 0) {
2083
+ const invalidSuffix = 'operation' in node ? node.operation : 'fragment';
2084
+ if (name.toLowerCase().endsWith(invalidSuffix)) {
2085
+ const { start, end } = node.name.loc;
1797
2086
  context.report({
1798
- node: node.name,
2087
+ loc: {
2088
+ start: {
2089
+ column: start.column - 1 + name.length - invalidSuffix.length,
2090
+ line: start.line,
2091
+ },
2092
+ end: {
2093
+ column: end.column - 1 + name.length,
2094
+ line: end.line,
2095
+ },
2096
+ },
1799
2097
  data: {
1800
2098
  invalidSuffix,
1801
2099
  },
@@ -1811,7 +2109,7 @@ const rule$c = {
1811
2109
 
1812
2110
  const UNREACHABLE_TYPE = 'UNREACHABLE_TYPE';
1813
2111
  const RULE_NAME = 'no-unreachable-types';
1814
- const rule$d = {
2112
+ const rule$e = {
1815
2113
  meta: {
1816
2114
  messages: {
1817
2115
  [UNREACHABLE_TYPE]: `Type "{{ typeName }}" is unreachable`,
@@ -1852,6 +2150,7 @@ const rule$d = {
1852
2150
  },
1853
2151
  fixable: 'code',
1854
2152
  type: 'suggestion',
2153
+ schema: [],
1855
2154
  },
1856
2155
  create(context) {
1857
2156
  const reachableTypes = requireReachableTypesFromContext(RULE_NAME, context);
@@ -1886,7 +2185,7 @@ const rule$d = {
1886
2185
 
1887
2186
  const UNUSED_FIELD = 'UNUSED_FIELD';
1888
2187
  const RULE_NAME$1 = 'no-unused-fields';
1889
- const rule$e = {
2188
+ const rule$f = {
1890
2189
  meta: {
1891
2190
  messages: {
1892
2191
  [UNUSED_FIELD]: `Field "{{fieldName}}" is unused`,
@@ -1943,6 +2242,7 @@ const rule$e = {
1943
2242
  },
1944
2243
  fixable: 'code',
1945
2244
  type: 'suggestion',
2245
+ schema: [],
1946
2246
  },
1947
2247
  create(context) {
1948
2248
  const usedFields = requireUsedFieldsFromContext(RULE_NAME$1, context);
@@ -2073,7 +2373,7 @@ const MESSAGE_REQUIRE_DATE = 'MESSAGE_REQUIRE_DATE';
2073
2373
  const MESSAGE_INVALID_FORMAT = 'MESSAGE_INVALID_FORMAT';
2074
2374
  const MESSAGE_INVALID_DATE = 'MESSAGE_INVALID_DATE';
2075
2375
  const MESSAGE_CAN_BE_REMOVED = 'MESSAGE_CAN_BE_REMOVED';
2076
- const rule$f = {
2376
+ const rule$g = {
2077
2377
  meta: {
2078
2378
  type: 'suggestion',
2079
2379
  docs: {
@@ -2173,7 +2473,7 @@ const rule$f = {
2173
2473
  },
2174
2474
  };
2175
2475
 
2176
- const rule$g = {
2476
+ const rule$h = {
2177
2477
  meta: {
2178
2478
  docs: {
2179
2479
  description: `Require all deprecation directives to specify a reason.`,
@@ -2208,6 +2508,7 @@ const rule$g = {
2208
2508
  ],
2209
2509
  },
2210
2510
  type: 'suggestion',
2511
+ schema: [],
2211
2512
  },
2212
2513
  create(context) {
2213
2514
  return {
@@ -2244,15 +2545,18 @@ const DESCRIBABLE_NODES = [
2244
2545
  function verifyRule(context, node) {
2245
2546
  if (node) {
2246
2547
  if (!node.description || !node.description.value || node.description.value.trim().length === 0) {
2548
+ const { start, end } = ('name' in node ? node.name : node).loc;
2247
2549
  context.report({
2248
2550
  loc: {
2249
2551
  start: {
2250
- line: node.loc.start.line,
2251
- column: node.loc.start.column - 1,
2552
+ line: start.line,
2553
+ column: start.column - 1,
2252
2554
  },
2253
2555
  end: {
2254
- line: node.loc.end.line,
2255
- column: node.loc.end.column,
2556
+ line: end.line,
2557
+ column:
2558
+ // node.name don't exist on SchemaDefinition
2559
+ 'name' in node ? end.column - 1 + node.name.value.length : end.column,
2256
2560
  },
2257
2561
  },
2258
2562
  messageId: REQUIRE_DESCRIPTION_ERROR,
@@ -2263,7 +2567,7 @@ function verifyRule(context, node) {
2263
2567
  }
2264
2568
  }
2265
2569
  }
2266
- const rule$h = {
2570
+ const rule$i = {
2267
2571
  meta: {
2268
2572
  docs: {
2269
2573
  category: 'Best Practices',
@@ -2328,7 +2632,7 @@ const rule$h = {
2328
2632
  };
2329
2633
 
2330
2634
  const RULE_NAME$2 = 'require-field-of-type-query-in-mutation-result';
2331
- const rule$i = {
2635
+ const rule$j = {
2332
2636
  meta: {
2333
2637
  type: 'suggestion',
2334
2638
  docs: {
@@ -2366,6 +2670,7 @@ const rule$i = {
2366
2670
  },
2367
2671
  ],
2368
2672
  },
2673
+ schema: [],
2369
2674
  },
2370
2675
  create(context) {
2371
2676
  const schema = requireGraphQLSchemaFromContext(RULE_NAME$2, context);
@@ -2491,7 +2796,7 @@ const convertNode = (typeInfo) => (node, key, parent) => {
2491
2796
 
2492
2797
  const REQUIRE_ID_WHEN_AVAILABLE = 'REQUIRE_ID_WHEN_AVAILABLE';
2493
2798
  const DEFAULT_ID_FIELD_NAME = 'id';
2494
- const rule$j = {
2799
+ const rule$k = {
2495
2800
  meta: {
2496
2801
  type: 'suggestion',
2497
2802
  docs: {
@@ -2627,7 +2932,7 @@ const rule$j = {
2627
2932
  },
2628
2933
  };
2629
2934
 
2630
- const rule$k = {
2935
+ const rule$l = {
2631
2936
  meta: {
2632
2937
  docs: {
2633
2938
  category: 'Best Practices',
@@ -2749,7 +3054,7 @@ const shouldIgnoreNode = ({ node, exceptions }) => {
2749
3054
  }
2750
3055
  return false;
2751
3056
  };
2752
- const rule$l = {
3057
+ const rule$m = {
2753
3058
  meta: {
2754
3059
  type: 'suggestion',
2755
3060
  docs: {
@@ -2902,11 +3207,7 @@ const rule$l = {
2902
3207
  const RULE_NAME$3 = 'unique-fragment-name';
2903
3208
  const UNIQUE_FRAGMENT_NAME = 'UNIQUE_FRAGMENT_NAME';
2904
3209
  const checkNode = (context, node, ruleName, messageId) => {
2905
- var _a;
2906
- const documentName = (_a = node.name) === null || _a === void 0 ? void 0 : _a.value;
2907
- if (!documentName) {
2908
- return;
2909
- }
3210
+ const documentName = node.name.value;
2910
3211
  const siblings = requireSiblingsOperations(ruleName, context);
2911
3212
  const siblingDocuments = node.kind === graphql.Kind.FRAGMENT_DEFINITION ? siblings.getFragment(documentName) : siblings.getOperation(documentName);
2912
3213
  const filepath = context.getFilename();
@@ -2917,7 +3218,6 @@ const checkNode = (context, node, ruleName, messageId) => {
2917
3218
  return isSameName && !isSamePath;
2918
3219
  });
2919
3220
  if (conflictingDocuments.length > 0) {
2920
- const { start, end } = node.name.loc;
2921
3221
  context.report({
2922
3222
  messageId,
2923
3223
  data: {
@@ -2926,20 +3226,11 @@ const checkNode = (context, node, ruleName, messageId) => {
2926
3226
  .map(f => `\t${path.relative(process.cwd(), getOnDiskFilepath(f.filePath))}`)
2927
3227
  .join('\n'),
2928
3228
  },
2929
- loc: {
2930
- start: {
2931
- line: start.line,
2932
- column: start.column - 1,
2933
- },
2934
- end: {
2935
- line: end.line,
2936
- column: end.column - 1,
2937
- },
2938
- },
3229
+ loc: getLocation(node.name.loc, documentName),
2939
3230
  });
2940
3231
  }
2941
3232
  };
2942
- const rule$m = {
3233
+ const rule$n = {
2943
3234
  meta: {
2944
3235
  type: 'suggestion',
2945
3236
  docs: {
@@ -2985,6 +3276,7 @@ const rule$m = {
2985
3276
  messages: {
2986
3277
  [UNIQUE_FRAGMENT_NAME]: 'Fragment named "{{ documentName }}" already defined in:\n{{ summary }}',
2987
3278
  },
3279
+ schema: [],
2988
3280
  },
2989
3281
  create(context) {
2990
3282
  return {
@@ -2997,7 +3289,7 @@ const rule$m = {
2997
3289
 
2998
3290
  const RULE_NAME$4 = 'unique-operation-name';
2999
3291
  const UNIQUE_OPERATION_NAME = 'UNIQUE_OPERATION_NAME';
3000
- const rule$n = {
3292
+ const rule$o = {
3001
3293
  meta: {
3002
3294
  type: 'suggestion',
3003
3295
  docs: {
@@ -3047,10 +3339,11 @@ const rule$n = {
3047
3339
  messages: {
3048
3340
  [UNIQUE_OPERATION_NAME]: 'Operation named "{{ documentName }}" already defined in:\n{{ summary }}',
3049
3341
  },
3342
+ schema: [],
3050
3343
  },
3051
3344
  create(context) {
3052
3345
  return {
3053
- OperationDefinition(node) {
3346
+ 'OperationDefinition[name!=undefined]'(node) {
3054
3347
  checkNode(context, node, RULE_NAME$4, UNIQUE_OPERATION_NAME);
3055
3348
  },
3056
3349
  };
@@ -3062,30 +3355,31 @@ const rule$n = {
3062
3355
  */
3063
3356
  const rules = {
3064
3357
  ...GRAPHQL_JS_VALIDATIONS,
3065
- 'avoid-duplicate-fields': rule,
3066
- 'avoid-operation-name-prefix': rule$1,
3067
- 'avoid-scalar-result-type-on-mutation': rule$2,
3068
- 'avoid-typename-prefix': rule$3,
3069
- 'description-style': rule$4,
3070
- 'input-name': rule$5,
3071
- 'match-document-filename': rule$6,
3072
- 'naming-convention': rule$7,
3073
- 'no-anonymous-operations': rule$8,
3074
- 'no-case-insensitive-enum-values-duplicates': rule$9,
3075
- 'no-deprecated': rule$a,
3076
- 'no-hashtag-description': rule$b,
3077
- 'no-operation-name-suffix': rule$c,
3078
- 'no-unreachable-types': rule$d,
3079
- 'no-unused-fields': rule$e,
3080
- 'require-deprecation-date': rule$f,
3081
- 'require-deprecation-reason': rule$g,
3082
- 'require-description': rule$h,
3083
- 'require-field-of-type-query-in-mutation-result': rule$i,
3084
- 'require-id-when-available': rule$j,
3085
- 'selection-set-depth': rule$k,
3086
- 'strict-id-in-types': rule$l,
3087
- 'unique-fragment-name': rule$m,
3088
- 'unique-operation-name': rule$n,
3358
+ alphabetize: rule,
3359
+ 'avoid-duplicate-fields': rule$1,
3360
+ 'avoid-operation-name-prefix': rule$2,
3361
+ 'avoid-scalar-result-type-on-mutation': rule$3,
3362
+ 'avoid-typename-prefix': rule$4,
3363
+ 'description-style': rule$5,
3364
+ 'input-name': rule$6,
3365
+ 'match-document-filename': rule$7,
3366
+ 'naming-convention': rule$8,
3367
+ 'no-anonymous-operations': rule$9,
3368
+ 'no-case-insensitive-enum-values-duplicates': rule$a,
3369
+ 'no-deprecated': rule$b,
3370
+ 'no-hashtag-description': rule$c,
3371
+ 'no-operation-name-suffix': rule$d,
3372
+ 'no-unreachable-types': rule$e,
3373
+ 'no-unused-fields': rule$f,
3374
+ 'require-deprecation-date': rule$g,
3375
+ 'require-deprecation-reason': rule$h,
3376
+ 'require-description': rule$i,
3377
+ 'require-field-of-type-query-in-mutation-result': rule$j,
3378
+ 'require-id-when-available': rule$k,
3379
+ 'selection-set-depth': rule$l,
3380
+ 'strict-id-in-types': rule$m,
3381
+ 'unique-fragment-name': rule$n,
3382
+ 'unique-operation-name': rule$o,
3089
3383
  };
3090
3384
 
3091
3385
  const RELEVANT_KEYWORDS = ['gql', 'graphql', '/* GraphQL */'];
@@ -3479,23 +3773,93 @@ function parseForESLint(code, options = {}) {
3479
3773
  }
3480
3774
  }
3481
3775
 
3482
- class GraphQLRuleTester extends require('eslint').RuleTester {
3776
+ class GraphQLRuleTester extends eslint.RuleTester {
3483
3777
  constructor(parserOptions = {}) {
3484
- super({
3778
+ const config = {
3485
3779
  parser: require.resolve('@graphql-eslint/eslint-plugin'),
3486
3780
  parserOptions: {
3487
3781
  ...parserOptions,
3488
3782
  skipGraphQLConfig: true,
3489
3783
  },
3490
- });
3784
+ };
3785
+ super(config);
3786
+ this.config = config;
3491
3787
  }
3492
3788
  fromMockFile(path$1) {
3493
3789
  return fs.readFileSync(path.resolve(__dirname, `../tests/mocks/${path$1}`), 'utf-8');
3494
3790
  }
3495
3791
  runGraphQLTests(name, rule, tests) {
3496
3792
  super.run(name, rule, tests);
3793
+ // Skip snapshot testing if `expect` variable is not defined
3794
+ if (typeof expect === 'undefined') {
3795
+ return;
3796
+ }
3797
+ const linter = new eslint.Linter();
3798
+ linter.defineRule(name, rule);
3799
+ for (const testCase of tests.invalid) {
3800
+ const verifyConfig = getVerifyConfig(name, this.config, testCase);
3801
+ defineParser(linter, verifyConfig.parser);
3802
+ const { code, filename } = testCase;
3803
+ const messages = linter.verify(code, verifyConfig, { filename });
3804
+ for (const message of messages) {
3805
+ if (message.fatal) {
3806
+ throw new Error(message.message);
3807
+ }
3808
+ const messageForSnapshot = visualizeEslintMessage(code, message);
3809
+ // eslint-disable-next-line no-undef
3810
+ expect(messageForSnapshot).toMatchSnapshot();
3811
+ }
3812
+ }
3497
3813
  }
3498
3814
  }
3815
+ function getVerifyConfig(ruleId, testerConfig, testCase) {
3816
+ const { options, parserOptions, parser = testerConfig.parser } = testCase;
3817
+ return {
3818
+ ...testerConfig,
3819
+ parser,
3820
+ parserOptions: {
3821
+ ...testerConfig.parserOptions,
3822
+ ...parserOptions,
3823
+ },
3824
+ rules: {
3825
+ [ruleId]: ['error', ...(Array.isArray(options) ? options : [])],
3826
+ },
3827
+ };
3828
+ }
3829
+ const parsers = new WeakMap();
3830
+ function defineParser(linter, parser) {
3831
+ if (!parser) {
3832
+ return;
3833
+ }
3834
+ if (!parsers.has(linter)) {
3835
+ parsers.set(linter, new Set());
3836
+ }
3837
+ const defined = parsers.get(linter);
3838
+ if (!defined.has(parser)) {
3839
+ defined.add(parser);
3840
+ linter.defineParser(parser, require(parser));
3841
+ }
3842
+ }
3843
+ function visualizeEslintMessage(text, result) {
3844
+ const { line, column, endLine, endColumn, message } = result;
3845
+ const location = {
3846
+ start: {
3847
+ line,
3848
+ column,
3849
+ },
3850
+ };
3851
+ if (typeof endLine === 'number' && typeof endColumn === 'number') {
3852
+ location.end = {
3853
+ line: endLine,
3854
+ column: endColumn,
3855
+ };
3856
+ }
3857
+ return codeFrame.codeFrameColumns(text, location, {
3858
+ linesAbove: Number.POSITIVE_INFINITY,
3859
+ linesBelow: Number.POSITIVE_INFINITY,
3860
+ message,
3861
+ });
3862
+ }
3499
3863
 
3500
3864
  exports.GraphQLRuleTester = GraphQLRuleTester;
3501
3865
  exports.configs = configs;