@graphql-eslint/eslint-plugin 3.13.2-alpha-20221109140613-1815aa1 → 3.14.0-alpha-20221220004017-f1f0904

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/documents.d.ts CHANGED
@@ -1,14 +1,14 @@
1
1
  import { FragmentDefinitionNode, OperationDefinitionNode, SelectionSetNode, OperationTypeNode } from 'graphql';
2
2
  import { GraphQLProjectConfig } from 'graphql-config';
3
- export declare type FragmentSource = {
3
+ export type FragmentSource = {
4
4
  filePath: string;
5
5
  document: FragmentDefinitionNode;
6
6
  };
7
- export declare type OperationSource = {
7
+ export type OperationSource = {
8
8
  filePath: string;
9
9
  document: OperationDefinitionNode;
10
10
  };
11
- export declare type SiblingOperations = {
11
+ export type SiblingOperations = {
12
12
  available: boolean;
13
13
  getFragment(fragmentName: string): FragmentSource[];
14
14
  getFragments(): FragmentSource[];
@@ -1,15 +1,15 @@
1
1
  import type { ASTNode, TypeInfo, TypeNode, DocumentNode, ExecutableDefinitionNode, NameNode, TypeDefinitionNode, FieldDefinitionNode, ObjectTypeExtensionNode, ObjectTypeDefinitionNode, InterfaceTypeDefinitionNode, InterfaceTypeExtensionNode, SelectionSetNode, SelectionNode, DefinitionNode, TypeExtensionNode, DirectiveDefinitionNode, VariableNode, FieldNode, FragmentSpreadNode, EnumValueDefinitionNode, ArgumentNode, NamedTypeNode, EnumTypeDefinitionNode, EnumTypeExtensionNode, InputValueDefinitionNode, InputObjectTypeDefinitionNode, InputObjectTypeExtensionNode, InlineFragmentNode, VariableDefinitionNode, ListTypeNode, NonNullTypeNode, OperationTypeDefinitionNode } from 'graphql';
2
2
  import type { SourceLocation, Comment } from 'estree';
3
3
  import type { AST } from 'eslint';
4
- declare type SafeGraphQLType<T extends ASTNode> = T extends {
4
+ type SafeGraphQLType<T extends ASTNode> = T extends {
5
5
  type: TypeNode;
6
6
  } ? Omit<T, 'loc' | 'type'> & {
7
7
  gqlType: T['type'];
8
8
  } : Omit<T, 'loc'>;
9
- declare type Writeable<T> = {
9
+ type Writeable<T> = {
10
10
  -readonly [K in keyof T]: T[K];
11
11
  };
12
- export declare type TypeInformation = {
12
+ export type TypeInformation = {
13
13
  argument: ReturnType<TypeInfo['getArgument']>;
14
14
  defaultValue: ReturnType<TypeInfo['getDefaultValue']>;
15
15
  directive: ReturnType<TypeInfo['getDirective']>;
@@ -20,10 +20,10 @@ export declare type TypeInformation = {
20
20
  parentType: ReturnType<TypeInfo['getParentType']>;
21
21
  gqlType: ReturnType<TypeInfo['getType']>;
22
22
  };
23
- declare type NodeWithName = TypeDefinitionNode | TypeExtensionNode | ExecutableDefinitionNode | DirectiveDefinitionNode | FieldDefinitionNode | EnumValueDefinitionNode | FieldNode | FragmentSpreadNode | VariableNode | ArgumentNode | NamedTypeNode;
24
- declare type NodeWithType = FieldDefinitionNode | InputValueDefinitionNode | OperationTypeDefinitionNode | NonNullTypeNode | ListTypeNode | VariableDefinitionNode;
25
- declare type ParentNode<T> = T extends DocumentNode ? AST.Program : T extends DefinitionNode ? DocumentNode : T extends EnumValueDefinitionNode ? EnumTypeDefinitionNode | EnumTypeExtensionNode : T extends InputValueDefinitionNode ? InputObjectTypeDefinitionNode | InputObjectTypeExtensionNode | FieldDefinitionNode | DirectiveDefinitionNode : T extends FieldDefinitionNode ? ObjectTypeDefinitionNode | ObjectTypeExtensionNode | InterfaceTypeDefinitionNode | InterfaceTypeExtensionNode : T extends SelectionSetNode ? ExecutableDefinitionNode | FieldNode | InlineFragmentNode : T extends SelectionNode ? SelectionSetNode : T extends TypeNode ? NodeWithType : T extends NameNode ? NodeWithName : unknown;
26
- declare type Node<T extends ASTNode, WithTypeInfo extends boolean> = Writeable<SafeGraphQLType<T>> & {
23
+ type NodeWithName = TypeDefinitionNode | TypeExtensionNode | ExecutableDefinitionNode | DirectiveDefinitionNode | FieldDefinitionNode | EnumValueDefinitionNode | FieldNode | FragmentSpreadNode | VariableNode | ArgumentNode | NamedTypeNode;
24
+ type NodeWithType = FieldDefinitionNode | InputValueDefinitionNode | OperationTypeDefinitionNode | NonNullTypeNode | ListTypeNode | VariableDefinitionNode;
25
+ type ParentNode<T> = T extends DocumentNode ? AST.Program : T extends DefinitionNode ? DocumentNode : T extends EnumValueDefinitionNode ? EnumTypeDefinitionNode | EnumTypeExtensionNode : T extends InputValueDefinitionNode ? InputObjectTypeDefinitionNode | InputObjectTypeExtensionNode | FieldDefinitionNode | DirectiveDefinitionNode : T extends FieldDefinitionNode ? ObjectTypeDefinitionNode | ObjectTypeExtensionNode | InterfaceTypeDefinitionNode | InterfaceTypeExtensionNode : T extends SelectionSetNode ? ExecutableDefinitionNode | FieldNode | InlineFragmentNode : T extends SelectionNode ? SelectionSetNode : T extends TypeNode ? NodeWithType : T extends NameNode ? NodeWithName : unknown;
26
+ type Node<T extends ASTNode, WithTypeInfo extends boolean> = Writeable<SafeGraphQLType<T>> & {
27
27
  type: T['kind'];
28
28
  loc: SourceLocation;
29
29
  range: AST.Range;
@@ -32,7 +32,7 @@ declare type Node<T extends ASTNode, WithTypeInfo extends boolean> = Writeable<S
32
32
  rawNode: () => T;
33
33
  parent: ParentNode<T>;
34
34
  };
35
- export declare type GraphQLESTreeNode<T, W extends boolean = false> = T extends ASTNode ? {
35
+ export type GraphQLESTreeNode<T, W extends boolean = false> = T extends ASTNode ? {
36
36
  [K in keyof Node<T, W>]: Node<T, W>[K] extends ReadonlyArray<infer ArrayItem> ? GraphQLESTreeNode<ArrayItem, W>[] : GraphQLESTreeNode<Node<T, W>[K], W>;
37
37
  } : T extends AST.Program ? T & {
38
38
  parent: null;
@@ -3,7 +3,7 @@ import type { Comment, SourceLocation } from 'estree';
3
3
  import type { AST } from 'eslint';
4
4
  export declare const valueFromNode: (valueNode: import("graphql").ValueNode, variables?: import("graphql/jsutils/ObjMap").ObjMap<unknown>) => any;
5
5
  export declare function getBaseType(type: GraphQLOutputType): GraphQLNamedType;
6
- declare type TokenKindValue = '<SOF>' | '!' | '$' | '&' | '(' | ')' | '...' | ':' | '=' | '@' | '[' | ']' | '{' | '|' | '}' | 'Name' | 'Int' | 'Float' | 'String' | 'BlockString' | 'Comment';
6
+ type TokenKindValue = '<SOF>' | '!' | '$' | '&' | '(' | ')' | '...' | ':' | '=' | '@' | '[' | ']' | '{' | '|' | '}' | 'Name' | 'Int' | 'Float' | 'String' | 'BlockString' | 'Comment';
7
7
  export declare function convertToken<T extends 'Line' | 'Block' | TokenKindValue>(token: Token, type: T): Omit<AST.Token, 'type'> & {
8
8
  type: T;
9
9
  };
package/index.js CHANGED
@@ -221,7 +221,8 @@ const ARRAY_DEFAULT_OPTIONS = {
221
221
  };
222
222
  const englishJoinWords = words => new Intl.ListFormat('en-US', { type: 'disjunction' }).format(words);
223
223
 
224
- function validateDocument(context, schema = null, documentNode, rule) {
224
+ function validateDocument({ context, schema = null, documentNode, rule, hasDidYouMeanSuggestions, }) {
225
+ var _a;
225
226
  if (documentNode.definitions.length === 0) {
226
227
  return;
227
228
  }
@@ -245,9 +246,20 @@ function validateDocument(context, schema = null, documentNode, rule) {
245
246
  ? sourceCode.getNodeByRangeIndex(token.range[1] + 1).loc
246
247
  : token.loc;
247
248
  }
249
+ const didYouMeanContent = (_a = error.message.match(/Did you mean (?<content>.*)\?$/)) === null || _a === void 0 ? void 0 : _a.groups.content;
250
+ const matches = didYouMeanContent ? [...didYouMeanContent.matchAll(/"(?<name>[^"]*)"/g)] : [];
248
251
  context.report({
249
252
  loc,
250
253
  message: error.message,
254
+ suggest: hasDidYouMeanSuggestions
255
+ ? matches.map(match => {
256
+ const { name } = match.groups;
257
+ return {
258
+ desc: `Rename to \`${name}\``,
259
+ fix: fixer => fixer.replaceText(token, name),
260
+ };
261
+ })
262
+ : [],
251
263
  });
252
264
  }
253
265
  }
@@ -301,7 +313,7 @@ const handleMissingFragments = ({ ruleId, context, node }) => {
301
313
  }
302
314
  return node;
303
315
  };
304
- const validationToRule = (ruleId, ruleName, docs, getDocumentNode, schema = []) => {
316
+ const validationToRule = ({ ruleId, ruleName, getDocumentNode, schema = [], hasDidYouMeanSuggestions, }, docs) => {
305
317
  let ruleFn = null;
306
318
  try {
307
319
  ruleFn = require(`graphql/validation/rules/${ruleName}Rule`)[`${ruleName}Rule`];
@@ -325,6 +337,7 @@ const validationToRule = (ruleId, ruleName, docs, getDocumentNode, schema = [])
325
337
  description: `${docs.description}\n\n> This rule is a wrapper around a \`graphql-js\` validation function.`,
326
338
  },
327
339
  schema,
340
+ hasSuggestions: hasDidYouMeanSuggestions,
328
341
  },
329
342
  create(context) {
330
343
  if (!ruleFn) {
@@ -339,30 +352,79 @@ const validationToRule = (ruleId, ruleName, docs, getDocumentNode, schema = [])
339
352
  const documentNode = getDocumentNode
340
353
  ? getDocumentNode({ ruleId, context, node: node.rawNode() })
341
354
  : node.rawNode();
342
- validateDocument(context, schema, documentNode, ruleFn);
355
+ validateDocument({
356
+ context,
357
+ schema,
358
+ documentNode,
359
+ rule: ruleFn,
360
+ hasDidYouMeanSuggestions,
361
+ });
343
362
  },
344
363
  };
345
364
  },
346
365
  },
347
366
  };
348
367
  };
349
- const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-definitions', 'ExecutableDefinitions', {
368
+ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule({
369
+ ruleId: 'executable-definitions',
370
+ ruleName: 'ExecutableDefinitions',
371
+ }, {
350
372
  category: 'Operations',
351
373
  description: 'A GraphQL document is only valid for execution if all definitions are either operation or fragment definitions.',
352
374
  requiresSchema: true,
353
- }), validationToRule('fields-on-correct-type', 'FieldsOnCorrectType', {
375
+ }), validationToRule({
376
+ ruleId: 'fields-on-correct-type',
377
+ ruleName: 'FieldsOnCorrectType',
378
+ hasDidYouMeanSuggestions: true,
379
+ }, {
354
380
  category: 'Operations',
355
381
  description: 'A GraphQL document is only valid if all fields selected are defined by the parent type, or are an allowed meta field such as `__typename`.',
356
382
  requiresSchema: true,
357
- }), validationToRule('fragments-on-composite-type', 'FragmentsOnCompositeTypes', {
383
+ }), validationToRule({
384
+ ruleId: 'fragments-on-composite-type',
385
+ ruleName: 'FragmentsOnCompositeTypes',
386
+ }, {
358
387
  category: 'Operations',
359
388
  description: 'Fragments use a type condition to determine if they apply, since fragments can only be spread into a composite type (object, interface, or union), the type condition must also be a composite type.',
360
389
  requiresSchema: true,
361
- }), validationToRule('known-argument-names', 'KnownArgumentNames', {
390
+ }), validationToRule({
391
+ ruleId: 'known-argument-names',
392
+ ruleName: 'KnownArgumentNames',
393
+ hasDidYouMeanSuggestions: true,
394
+ }, {
362
395
  category: ['Schema', 'Operations'],
363
396
  description: 'A GraphQL field is only valid if all supplied arguments are defined by that field.',
364
397
  requiresSchema: true,
365
- }), validationToRule('known-directives', 'KnownDirectives', {
398
+ }), validationToRule({
399
+ ruleId: 'known-directives',
400
+ ruleName: 'KnownDirectives',
401
+ getDocumentNode({ context, node: documentNode }) {
402
+ const { ignoreClientDirectives = [] } = context.options[0] || {};
403
+ if (ignoreClientDirectives.length === 0) {
404
+ return documentNode;
405
+ }
406
+ const filterDirectives = (node) => ({
407
+ ...node,
408
+ directives: node.directives.filter(directive => !ignoreClientDirectives.includes(directive.name.value)),
409
+ });
410
+ return graphql.visit(documentNode, {
411
+ Field: filterDirectives,
412
+ OperationDefinition: filterDirectives,
413
+ });
414
+ },
415
+ schema: {
416
+ type: 'array',
417
+ maxItems: 1,
418
+ items: {
419
+ type: 'object',
420
+ additionalProperties: false,
421
+ required: ['ignoreClientDirectives'],
422
+ properties: {
423
+ ignoreClientDirectives: ARRAY_DEFAULT_OPTIONS,
424
+ },
425
+ },
426
+ },
427
+ }, {
366
428
  category: ['Schema', 'Operations'],
367
429
  description: 'A GraphQL document is only valid if all `@directive`s are known by the schema and legally positioned.',
368
430
  requiresSchema: true,
@@ -379,31 +441,11 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
379
441
  `,
380
442
  },
381
443
  ],
382
- }, ({ context, node: documentNode }) => {
383
- const { ignoreClientDirectives = [] } = context.options[0] || {};
384
- if (ignoreClientDirectives.length === 0) {
385
- return documentNode;
386
- }
387
- const filterDirectives = (node) => ({
388
- ...node,
389
- directives: node.directives.filter(directive => !ignoreClientDirectives.includes(directive.name.value)),
390
- });
391
- return graphql.visit(documentNode, {
392
- Field: filterDirectives,
393
- OperationDefinition: filterDirectives,
394
- });
444
+ }), validationToRule({
445
+ ruleId: 'known-fragment-names',
446
+ ruleName: 'KnownFragmentNames',
447
+ getDocumentNode: handleMissingFragments,
395
448
  }, {
396
- type: 'array',
397
- maxItems: 1,
398
- items: {
399
- type: 'object',
400
- additionalProperties: false,
401
- required: ['ignoreClientDirectives'],
402
- properties: {
403
- ignoreClientDirectives: ARRAY_DEFAULT_OPTIONS,
404
- },
405
- },
406
- }), validationToRule('known-fragment-names', 'KnownFragmentNames', {
407
449
  category: 'Operations',
408
450
  description: 'A GraphQL document is only valid if all `...Fragment` fragment spreads refer to fragments defined in the same document.',
409
451
  requiresSchema: true,
@@ -454,138 +496,220 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
454
496
  `,
455
497
  },
456
498
  ],
457
- }, handleMissingFragments), validationToRule('known-type-names', 'KnownTypeNames', {
499
+ }), validationToRule({
500
+ ruleId: 'known-type-names',
501
+ ruleName: 'KnownTypeNames',
502
+ hasDidYouMeanSuggestions: true,
503
+ }, {
458
504
  category: ['Schema', 'Operations'],
459
505
  description: 'A GraphQL document is only valid if referenced types (specifically variable definitions and fragment conditions) are defined by the type schema.',
460
506
  requiresSchema: true,
461
- }), validationToRule('lone-anonymous-operation', 'LoneAnonymousOperation', {
507
+ }), validationToRule({
508
+ ruleId: 'lone-anonymous-operation',
509
+ ruleName: 'LoneAnonymousOperation',
510
+ }, {
462
511
  category: 'Operations',
463
512
  description: 'A GraphQL document is only valid if when it contains an anonymous operation (the query short-hand) that it contains only that one operation definition.',
464
513
  requiresSchema: true,
465
- }), validationToRule('lone-schema-definition', 'LoneSchemaDefinition', {
514
+ }), validationToRule({
515
+ ruleId: 'lone-schema-definition',
516
+ ruleName: 'LoneSchemaDefinition',
517
+ }, {
466
518
  category: 'Schema',
467
519
  description: 'A GraphQL document is only valid if it contains only one schema definition.',
468
- }), validationToRule('no-fragment-cycles', 'NoFragmentCycles', {
520
+ }), validationToRule({
521
+ ruleId: 'no-fragment-cycles',
522
+ ruleName: 'NoFragmentCycles',
523
+ }, {
469
524
  category: 'Operations',
470
525
  description: 'A GraphQL fragment is only valid when it does not have cycles in fragments usage.',
471
526
  requiresSchema: true,
472
- }), validationToRule('no-undefined-variables', 'NoUndefinedVariables', {
527
+ }), validationToRule({
528
+ ruleId: 'no-undefined-variables',
529
+ ruleName: 'NoUndefinedVariables',
530
+ getDocumentNode: handleMissingFragments,
531
+ }, {
473
532
  category: 'Operations',
474
533
  description: 'A GraphQL operation is only valid if all variables encountered, both directly and via fragment spreads, are defined by that operation.',
475
534
  requiresSchema: true,
476
535
  requiresSiblings: true,
477
- }, handleMissingFragments), validationToRule('no-unused-fragments', 'NoUnusedFragments', {
536
+ }), validationToRule({
537
+ ruleId: 'no-unused-fragments',
538
+ ruleName: 'NoUnusedFragments',
539
+ getDocumentNode: ({ ruleId, context, node }) => {
540
+ const siblings = requireSiblingsOperations(ruleId, context);
541
+ const FilePathToDocumentsMap = [
542
+ ...siblings.getOperations(),
543
+ ...siblings.getFragments(),
544
+ ].reduce((map, { filePath, document }) => {
545
+ var _a;
546
+ (_a = map[filePath]) !== null && _a !== void 0 ? _a : (map[filePath] = []);
547
+ map[filePath].push(document);
548
+ return map;
549
+ }, Object.create(null));
550
+ const getParentNode = (currentFilePath, node) => {
551
+ const { fragmentDefs } = getFragmentDefsAndFragmentSpreads(node);
552
+ if (fragmentDefs.size === 0) {
553
+ return node;
554
+ }
555
+ // skip iteration over documents for current filepath
556
+ delete FilePathToDocumentsMap[currentFilePath];
557
+ for (const [filePath, documents] of Object.entries(FilePathToDocumentsMap)) {
558
+ const missingFragments = getMissingFragments({
559
+ kind: graphql.Kind.DOCUMENT,
560
+ definitions: documents,
561
+ });
562
+ const isCurrentFileImportFragment = missingFragments.some(fragment => fragmentDefs.has(fragment));
563
+ if (isCurrentFileImportFragment) {
564
+ return getParentNode(filePath, {
565
+ kind: graphql.Kind.DOCUMENT,
566
+ definitions: [...node.definitions, ...documents],
567
+ });
568
+ }
569
+ }
570
+ return node;
571
+ };
572
+ return getParentNode(context.getFilename(), node);
573
+ },
574
+ }, {
478
575
  category: 'Operations',
479
576
  description: 'A GraphQL document is only valid if all fragment definitions are spread within operations, or spread within other fragments spread within operations.',
480
577
  requiresSchema: true,
481
578
  requiresSiblings: true,
482
- }, ({ ruleId, context, node }) => {
483
- const siblings = requireSiblingsOperations(ruleId, context);
484
- const FilePathToDocumentsMap = [
485
- ...siblings.getOperations(),
486
- ...siblings.getFragments(),
487
- ].reduce((map, { filePath, document }) => {
488
- var _a;
489
- (_a = map[filePath]) !== null && _a !== void 0 ? _a : (map[filePath] = []);
490
- map[filePath].push(document);
491
- return map;
492
- }, Object.create(null));
493
- const getParentNode = (currentFilePath, node) => {
494
- const { fragmentDefs } = getFragmentDefsAndFragmentSpreads(node);
495
- if (fragmentDefs.size === 0) {
496
- return node;
497
- }
498
- // skip iteration over documents for current filepath
499
- delete FilePathToDocumentsMap[currentFilePath];
500
- for (const [filePath, documents] of Object.entries(FilePathToDocumentsMap)) {
501
- const missingFragments = getMissingFragments({
502
- kind: graphql.Kind.DOCUMENT,
503
- definitions: documents,
504
- });
505
- const isCurrentFileImportFragment = missingFragments.some(fragment => fragmentDefs.has(fragment));
506
- if (isCurrentFileImportFragment) {
507
- return getParentNode(filePath, {
508
- kind: graphql.Kind.DOCUMENT,
509
- definitions: [...node.definitions, ...documents],
510
- });
511
- }
512
- }
513
- return node;
514
- };
515
- return getParentNode(context.getFilename(), node);
516
- }), validationToRule('no-unused-variables', 'NoUnusedVariables', {
579
+ }), validationToRule({
580
+ ruleId: 'no-unused-variables',
581
+ ruleName: 'NoUnusedVariables',
582
+ getDocumentNode: handleMissingFragments,
583
+ }, {
517
584
  category: 'Operations',
518
585
  description: 'A GraphQL operation is only valid if all variables defined by an operation are used, either directly or within a spread fragment.',
519
586
  requiresSchema: true,
520
587
  requiresSiblings: true,
521
- }, handleMissingFragments), validationToRule('overlapping-fields-can-be-merged', 'OverlappingFieldsCanBeMerged', {
588
+ }), validationToRule({
589
+ ruleId: 'overlapping-fields-can-be-merged',
590
+ ruleName: 'OverlappingFieldsCanBeMerged',
591
+ }, {
522
592
  category: 'Operations',
523
593
  description: 'A selection set is only valid if all fields (including spreading any fragments) either correspond to distinct response names or can be merged without ambiguity.',
524
594
  requiresSchema: true,
525
- }), validationToRule('possible-fragment-spread', 'PossibleFragmentSpreads', {
595
+ }), validationToRule({
596
+ ruleId: 'possible-fragment-spread',
597
+ ruleName: 'PossibleFragmentSpreads',
598
+ }, {
526
599
  category: 'Operations',
527
600
  description: 'A fragment spread is only valid if the type condition could ever possibly be true: if there is a non-empty intersection of the possible parent types, and possible types which pass the type condition.',
528
601
  requiresSchema: true,
529
- }), validationToRule('possible-type-extension', 'PossibleTypeExtensions', {
602
+ }), validationToRule({
603
+ ruleId: 'possible-type-extension',
604
+ ruleName: 'PossibleTypeExtensions',
605
+ hasDidYouMeanSuggestions: true,
606
+ }, {
530
607
  category: 'Schema',
531
608
  description: 'A type extension is only valid if the type is defined and has the same kind.',
532
609
  // TODO: add in graphql-eslint v4
533
610
  recommended: false,
534
611
  requiresSchema: true,
535
612
  isDisabledForAllConfig: true,
536
- }), validationToRule('provided-required-arguments', 'ProvidedRequiredArguments', {
613
+ }), validationToRule({
614
+ ruleId: 'provided-required-arguments',
615
+ ruleName: 'ProvidedRequiredArguments',
616
+ }, {
537
617
  category: ['Schema', 'Operations'],
538
618
  description: 'A field or directive is only valid if all required (non-null without a default value) field arguments have been provided.',
539
619
  requiresSchema: true,
540
- }), validationToRule('scalar-leafs', 'ScalarLeafs', {
620
+ }), validationToRule({
621
+ ruleId: 'scalar-leafs',
622
+ ruleName: 'ScalarLeafs',
623
+ hasDidYouMeanSuggestions: true,
624
+ }, {
541
625
  category: 'Operations',
542
626
  description: 'A GraphQL document is valid only if all leaf fields (fields without sub selections) are of scalar or enum types.',
543
627
  requiresSchema: true,
544
- }), validationToRule('one-field-subscriptions', 'SingleFieldSubscriptions', {
628
+ }), validationToRule({
629
+ ruleId: 'one-field-subscriptions',
630
+ ruleName: 'SingleFieldSubscriptions',
631
+ }, {
545
632
  category: 'Operations',
546
633
  description: 'A GraphQL subscription is valid only if it contains a single root field.',
547
634
  requiresSchema: true,
548
- }), validationToRule('unique-argument-names', 'UniqueArgumentNames', {
635
+ }), validationToRule({
636
+ ruleId: 'unique-argument-names',
637
+ ruleName: 'UniqueArgumentNames',
638
+ }, {
549
639
  category: 'Operations',
550
640
  description: 'A GraphQL field or directive is only valid if all supplied arguments are uniquely named.',
551
641
  requiresSchema: true,
552
- }), validationToRule('unique-directive-names', 'UniqueDirectiveNames', {
642
+ }), validationToRule({
643
+ ruleId: 'unique-directive-names',
644
+ ruleName: 'UniqueDirectiveNames',
645
+ }, {
553
646
  category: 'Schema',
554
647
  description: 'A GraphQL document is only valid if all defined directives have unique names.',
555
- }), validationToRule('unique-directive-names-per-location', 'UniqueDirectivesPerLocation', {
648
+ }), validationToRule({
649
+ ruleId: 'unique-directive-names-per-location',
650
+ ruleName: 'UniqueDirectivesPerLocation',
651
+ }, {
556
652
  category: ['Schema', 'Operations'],
557
653
  description: 'A GraphQL document is only valid if all non-repeatable directives at a given location are uniquely named.',
558
654
  requiresSchema: true,
559
- }), validationToRule('unique-enum-value-names', 'UniqueEnumValueNames', {
655
+ }), validationToRule({
656
+ ruleId: 'unique-enum-value-names',
657
+ ruleName: 'UniqueEnumValueNames',
658
+ }, {
560
659
  category: 'Schema',
561
660
  description: 'A GraphQL enum type is only valid if all its values are uniquely named.',
562
661
  recommended: false,
563
662
  isDisabledForAllConfig: true,
564
- }), validationToRule('unique-field-definition-names', 'UniqueFieldDefinitionNames', {
663
+ }), validationToRule({
664
+ ruleId: 'unique-field-definition-names',
665
+ ruleName: 'UniqueFieldDefinitionNames',
666
+ }, {
565
667
  category: 'Schema',
566
668
  description: 'A GraphQL complex type is only valid if all its fields are uniquely named.',
567
- }), validationToRule('unique-input-field-names', 'UniqueInputFieldNames', {
669
+ }), validationToRule({
670
+ ruleId: 'unique-input-field-names',
671
+ ruleName: 'UniqueInputFieldNames',
672
+ }, {
568
673
  category: 'Operations',
569
674
  description: 'A GraphQL input object value is only valid if all supplied fields are uniquely named.',
570
- }), validationToRule('unique-operation-types', 'UniqueOperationTypes', {
675
+ }), validationToRule({
676
+ ruleId: 'unique-operation-types',
677
+ ruleName: 'UniqueOperationTypes',
678
+ }, {
571
679
  category: 'Schema',
572
680
  description: 'A GraphQL document is only valid if it has only one type per operation.',
573
- }), validationToRule('unique-type-names', 'UniqueTypeNames', {
681
+ }), validationToRule({
682
+ ruleId: 'unique-type-names',
683
+ ruleName: 'UniqueTypeNames',
684
+ }, {
574
685
  category: 'Schema',
575
686
  description: 'A GraphQL document is only valid if all defined types have unique names.',
576
- }), validationToRule('unique-variable-names', 'UniqueVariableNames', {
687
+ }), validationToRule({
688
+ ruleId: 'unique-variable-names',
689
+ ruleName: 'UniqueVariableNames',
690
+ }, {
577
691
  category: 'Operations',
578
692
  description: 'A GraphQL operation is only valid if all its variables are uniquely named.',
579
693
  requiresSchema: true,
580
- }), validationToRule('value-literals-of-correct-type', 'ValuesOfCorrectType', {
694
+ }), validationToRule({
695
+ ruleId: 'value-literals-of-correct-type',
696
+ ruleName: 'ValuesOfCorrectType',
697
+ hasDidYouMeanSuggestions: true,
698
+ }, {
581
699
  category: 'Operations',
582
700
  description: 'A GraphQL document is only valid if all value literals are of the type expected at their position.',
583
701
  requiresSchema: true,
584
- }), validationToRule('variables-are-input-types', 'VariablesAreInputTypes', {
702
+ }), validationToRule({
703
+ ruleId: 'variables-are-input-types',
704
+ ruleName: 'VariablesAreInputTypes',
705
+ }, {
585
706
  category: 'Operations',
586
707
  description: 'A GraphQL operation is only valid if all the variables it defines are of input types (scalar, enum, or input object).',
587
708
  requiresSchema: true,
588
- }), validationToRule('variables-in-allowed-position', 'VariablesInAllowedPosition', {
709
+ }), validationToRule({
710
+ ruleId: 'variables-in-allowed-position',
711
+ ruleName: 'VariablesInAllowedPosition',
712
+ }, {
589
713
  category: 'Operations',
590
714
  description: 'Variables passed to field arguments conform to type.',
591
715
  requiresSchema: true,
package/index.mjs CHANGED
@@ -215,7 +215,8 @@ const ARRAY_DEFAULT_OPTIONS = {
215
215
  };
216
216
  const englishJoinWords = words => new Intl.ListFormat('en-US', { type: 'disjunction' }).format(words);
217
217
 
218
- function validateDocument(context, schema = null, documentNode, rule) {
218
+ function validateDocument({ context, schema = null, documentNode, rule, hasDidYouMeanSuggestions, }) {
219
+ var _a;
219
220
  if (documentNode.definitions.length === 0) {
220
221
  return;
221
222
  }
@@ -239,9 +240,20 @@ function validateDocument(context, schema = null, documentNode, rule) {
239
240
  ? sourceCode.getNodeByRangeIndex(token.range[1] + 1).loc
240
241
  : token.loc;
241
242
  }
243
+ const didYouMeanContent = (_a = error.message.match(/Did you mean (?<content>.*)\?$/)) === null || _a === void 0 ? void 0 : _a.groups.content;
244
+ const matches = didYouMeanContent ? [...didYouMeanContent.matchAll(/"(?<name>[^"]*)"/g)] : [];
242
245
  context.report({
243
246
  loc,
244
247
  message: error.message,
248
+ suggest: hasDidYouMeanSuggestions
249
+ ? matches.map(match => {
250
+ const { name } = match.groups;
251
+ return {
252
+ desc: `Rename to \`${name}\``,
253
+ fix: fixer => fixer.replaceText(token, name),
254
+ };
255
+ })
256
+ : [],
245
257
  });
246
258
  }
247
259
  }
@@ -295,7 +307,7 @@ const handleMissingFragments = ({ ruleId, context, node }) => {
295
307
  }
296
308
  return node;
297
309
  };
298
- const validationToRule = (ruleId, ruleName, docs, getDocumentNode, schema = []) => {
310
+ const validationToRule = ({ ruleId, ruleName, getDocumentNode, schema = [], hasDidYouMeanSuggestions, }, docs) => {
299
311
  let ruleFn = null;
300
312
  try {
301
313
  ruleFn = require(`graphql/validation/rules/${ruleName}Rule`)[`${ruleName}Rule`];
@@ -319,6 +331,7 @@ const validationToRule = (ruleId, ruleName, docs, getDocumentNode, schema = [])
319
331
  description: `${docs.description}\n\n> This rule is a wrapper around a \`graphql-js\` validation function.`,
320
332
  },
321
333
  schema,
334
+ hasSuggestions: hasDidYouMeanSuggestions,
322
335
  },
323
336
  create(context) {
324
337
  if (!ruleFn) {
@@ -333,30 +346,79 @@ const validationToRule = (ruleId, ruleName, docs, getDocumentNode, schema = [])
333
346
  const documentNode = getDocumentNode
334
347
  ? getDocumentNode({ ruleId, context, node: node.rawNode() })
335
348
  : node.rawNode();
336
- validateDocument(context, schema, documentNode, ruleFn);
349
+ validateDocument({
350
+ context,
351
+ schema,
352
+ documentNode,
353
+ rule: ruleFn,
354
+ hasDidYouMeanSuggestions,
355
+ });
337
356
  },
338
357
  };
339
358
  },
340
359
  },
341
360
  };
342
361
  };
343
- const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-definitions', 'ExecutableDefinitions', {
362
+ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule({
363
+ ruleId: 'executable-definitions',
364
+ ruleName: 'ExecutableDefinitions',
365
+ }, {
344
366
  category: 'Operations',
345
367
  description: 'A GraphQL document is only valid for execution if all definitions are either operation or fragment definitions.',
346
368
  requiresSchema: true,
347
- }), validationToRule('fields-on-correct-type', 'FieldsOnCorrectType', {
369
+ }), validationToRule({
370
+ ruleId: 'fields-on-correct-type',
371
+ ruleName: 'FieldsOnCorrectType',
372
+ hasDidYouMeanSuggestions: true,
373
+ }, {
348
374
  category: 'Operations',
349
375
  description: 'A GraphQL document is only valid if all fields selected are defined by the parent type, or are an allowed meta field such as `__typename`.',
350
376
  requiresSchema: true,
351
- }), validationToRule('fragments-on-composite-type', 'FragmentsOnCompositeTypes', {
377
+ }), validationToRule({
378
+ ruleId: 'fragments-on-composite-type',
379
+ ruleName: 'FragmentsOnCompositeTypes',
380
+ }, {
352
381
  category: 'Operations',
353
382
  description: 'Fragments use a type condition to determine if they apply, since fragments can only be spread into a composite type (object, interface, or union), the type condition must also be a composite type.',
354
383
  requiresSchema: true,
355
- }), validationToRule('known-argument-names', 'KnownArgumentNames', {
384
+ }), validationToRule({
385
+ ruleId: 'known-argument-names',
386
+ ruleName: 'KnownArgumentNames',
387
+ hasDidYouMeanSuggestions: true,
388
+ }, {
356
389
  category: ['Schema', 'Operations'],
357
390
  description: 'A GraphQL field is only valid if all supplied arguments are defined by that field.',
358
391
  requiresSchema: true,
359
- }), validationToRule('known-directives', 'KnownDirectives', {
392
+ }), validationToRule({
393
+ ruleId: 'known-directives',
394
+ ruleName: 'KnownDirectives',
395
+ getDocumentNode({ context, node: documentNode }) {
396
+ const { ignoreClientDirectives = [] } = context.options[0] || {};
397
+ if (ignoreClientDirectives.length === 0) {
398
+ return documentNode;
399
+ }
400
+ const filterDirectives = (node) => ({
401
+ ...node,
402
+ directives: node.directives.filter(directive => !ignoreClientDirectives.includes(directive.name.value)),
403
+ });
404
+ return visit(documentNode, {
405
+ Field: filterDirectives,
406
+ OperationDefinition: filterDirectives,
407
+ });
408
+ },
409
+ schema: {
410
+ type: 'array',
411
+ maxItems: 1,
412
+ items: {
413
+ type: 'object',
414
+ additionalProperties: false,
415
+ required: ['ignoreClientDirectives'],
416
+ properties: {
417
+ ignoreClientDirectives: ARRAY_DEFAULT_OPTIONS,
418
+ },
419
+ },
420
+ },
421
+ }, {
360
422
  category: ['Schema', 'Operations'],
361
423
  description: 'A GraphQL document is only valid if all `@directive`s are known by the schema and legally positioned.',
362
424
  requiresSchema: true,
@@ -373,31 +435,11 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
373
435
  `,
374
436
  },
375
437
  ],
376
- }, ({ context, node: documentNode }) => {
377
- const { ignoreClientDirectives = [] } = context.options[0] || {};
378
- if (ignoreClientDirectives.length === 0) {
379
- return documentNode;
380
- }
381
- const filterDirectives = (node) => ({
382
- ...node,
383
- directives: node.directives.filter(directive => !ignoreClientDirectives.includes(directive.name.value)),
384
- });
385
- return visit(documentNode, {
386
- Field: filterDirectives,
387
- OperationDefinition: filterDirectives,
388
- });
438
+ }), validationToRule({
439
+ ruleId: 'known-fragment-names',
440
+ ruleName: 'KnownFragmentNames',
441
+ getDocumentNode: handleMissingFragments,
389
442
  }, {
390
- type: 'array',
391
- maxItems: 1,
392
- items: {
393
- type: 'object',
394
- additionalProperties: false,
395
- required: ['ignoreClientDirectives'],
396
- properties: {
397
- ignoreClientDirectives: ARRAY_DEFAULT_OPTIONS,
398
- },
399
- },
400
- }), validationToRule('known-fragment-names', 'KnownFragmentNames', {
401
443
  category: 'Operations',
402
444
  description: 'A GraphQL document is only valid if all `...Fragment` fragment spreads refer to fragments defined in the same document.',
403
445
  requiresSchema: true,
@@ -448,138 +490,220 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
448
490
  `,
449
491
  },
450
492
  ],
451
- }, handleMissingFragments), validationToRule('known-type-names', 'KnownTypeNames', {
493
+ }), validationToRule({
494
+ ruleId: 'known-type-names',
495
+ ruleName: 'KnownTypeNames',
496
+ hasDidYouMeanSuggestions: true,
497
+ }, {
452
498
  category: ['Schema', 'Operations'],
453
499
  description: 'A GraphQL document is only valid if referenced types (specifically variable definitions and fragment conditions) are defined by the type schema.',
454
500
  requiresSchema: true,
455
- }), validationToRule('lone-anonymous-operation', 'LoneAnonymousOperation', {
501
+ }), validationToRule({
502
+ ruleId: 'lone-anonymous-operation',
503
+ ruleName: 'LoneAnonymousOperation',
504
+ }, {
456
505
  category: 'Operations',
457
506
  description: 'A GraphQL document is only valid if when it contains an anonymous operation (the query short-hand) that it contains only that one operation definition.',
458
507
  requiresSchema: true,
459
- }), validationToRule('lone-schema-definition', 'LoneSchemaDefinition', {
508
+ }), validationToRule({
509
+ ruleId: 'lone-schema-definition',
510
+ ruleName: 'LoneSchemaDefinition',
511
+ }, {
460
512
  category: 'Schema',
461
513
  description: 'A GraphQL document is only valid if it contains only one schema definition.',
462
- }), validationToRule('no-fragment-cycles', 'NoFragmentCycles', {
514
+ }), validationToRule({
515
+ ruleId: 'no-fragment-cycles',
516
+ ruleName: 'NoFragmentCycles',
517
+ }, {
463
518
  category: 'Operations',
464
519
  description: 'A GraphQL fragment is only valid when it does not have cycles in fragments usage.',
465
520
  requiresSchema: true,
466
- }), validationToRule('no-undefined-variables', 'NoUndefinedVariables', {
521
+ }), validationToRule({
522
+ ruleId: 'no-undefined-variables',
523
+ ruleName: 'NoUndefinedVariables',
524
+ getDocumentNode: handleMissingFragments,
525
+ }, {
467
526
  category: 'Operations',
468
527
  description: 'A GraphQL operation is only valid if all variables encountered, both directly and via fragment spreads, are defined by that operation.',
469
528
  requiresSchema: true,
470
529
  requiresSiblings: true,
471
- }, handleMissingFragments), validationToRule('no-unused-fragments', 'NoUnusedFragments', {
530
+ }), validationToRule({
531
+ ruleId: 'no-unused-fragments',
532
+ ruleName: 'NoUnusedFragments',
533
+ getDocumentNode: ({ ruleId, context, node }) => {
534
+ const siblings = requireSiblingsOperations(ruleId, context);
535
+ const FilePathToDocumentsMap = [
536
+ ...siblings.getOperations(),
537
+ ...siblings.getFragments(),
538
+ ].reduce((map, { filePath, document }) => {
539
+ var _a;
540
+ (_a = map[filePath]) !== null && _a !== void 0 ? _a : (map[filePath] = []);
541
+ map[filePath].push(document);
542
+ return map;
543
+ }, Object.create(null));
544
+ const getParentNode = (currentFilePath, node) => {
545
+ const { fragmentDefs } = getFragmentDefsAndFragmentSpreads(node);
546
+ if (fragmentDefs.size === 0) {
547
+ return node;
548
+ }
549
+ // skip iteration over documents for current filepath
550
+ delete FilePathToDocumentsMap[currentFilePath];
551
+ for (const [filePath, documents] of Object.entries(FilePathToDocumentsMap)) {
552
+ const missingFragments = getMissingFragments({
553
+ kind: Kind.DOCUMENT,
554
+ definitions: documents,
555
+ });
556
+ const isCurrentFileImportFragment = missingFragments.some(fragment => fragmentDefs.has(fragment));
557
+ if (isCurrentFileImportFragment) {
558
+ return getParentNode(filePath, {
559
+ kind: Kind.DOCUMENT,
560
+ definitions: [...node.definitions, ...documents],
561
+ });
562
+ }
563
+ }
564
+ return node;
565
+ };
566
+ return getParentNode(context.getFilename(), node);
567
+ },
568
+ }, {
472
569
  category: 'Operations',
473
570
  description: 'A GraphQL document is only valid if all fragment definitions are spread within operations, or spread within other fragments spread within operations.',
474
571
  requiresSchema: true,
475
572
  requiresSiblings: true,
476
- }, ({ ruleId, context, node }) => {
477
- const siblings = requireSiblingsOperations(ruleId, context);
478
- const FilePathToDocumentsMap = [
479
- ...siblings.getOperations(),
480
- ...siblings.getFragments(),
481
- ].reduce((map, { filePath, document }) => {
482
- var _a;
483
- (_a = map[filePath]) !== null && _a !== void 0 ? _a : (map[filePath] = []);
484
- map[filePath].push(document);
485
- return map;
486
- }, Object.create(null));
487
- const getParentNode = (currentFilePath, node) => {
488
- const { fragmentDefs } = getFragmentDefsAndFragmentSpreads(node);
489
- if (fragmentDefs.size === 0) {
490
- return node;
491
- }
492
- // skip iteration over documents for current filepath
493
- delete FilePathToDocumentsMap[currentFilePath];
494
- for (const [filePath, documents] of Object.entries(FilePathToDocumentsMap)) {
495
- const missingFragments = getMissingFragments({
496
- kind: Kind.DOCUMENT,
497
- definitions: documents,
498
- });
499
- const isCurrentFileImportFragment = missingFragments.some(fragment => fragmentDefs.has(fragment));
500
- if (isCurrentFileImportFragment) {
501
- return getParentNode(filePath, {
502
- kind: Kind.DOCUMENT,
503
- definitions: [...node.definitions, ...documents],
504
- });
505
- }
506
- }
507
- return node;
508
- };
509
- return getParentNode(context.getFilename(), node);
510
- }), validationToRule('no-unused-variables', 'NoUnusedVariables', {
573
+ }), validationToRule({
574
+ ruleId: 'no-unused-variables',
575
+ ruleName: 'NoUnusedVariables',
576
+ getDocumentNode: handleMissingFragments,
577
+ }, {
511
578
  category: 'Operations',
512
579
  description: 'A GraphQL operation is only valid if all variables defined by an operation are used, either directly or within a spread fragment.',
513
580
  requiresSchema: true,
514
581
  requiresSiblings: true,
515
- }, handleMissingFragments), validationToRule('overlapping-fields-can-be-merged', 'OverlappingFieldsCanBeMerged', {
582
+ }), validationToRule({
583
+ ruleId: 'overlapping-fields-can-be-merged',
584
+ ruleName: 'OverlappingFieldsCanBeMerged',
585
+ }, {
516
586
  category: 'Operations',
517
587
  description: 'A selection set is only valid if all fields (including spreading any fragments) either correspond to distinct response names or can be merged without ambiguity.',
518
588
  requiresSchema: true,
519
- }), validationToRule('possible-fragment-spread', 'PossibleFragmentSpreads', {
589
+ }), validationToRule({
590
+ ruleId: 'possible-fragment-spread',
591
+ ruleName: 'PossibleFragmentSpreads',
592
+ }, {
520
593
  category: 'Operations',
521
594
  description: 'A fragment spread is only valid if the type condition could ever possibly be true: if there is a non-empty intersection of the possible parent types, and possible types which pass the type condition.',
522
595
  requiresSchema: true,
523
- }), validationToRule('possible-type-extension', 'PossibleTypeExtensions', {
596
+ }), validationToRule({
597
+ ruleId: 'possible-type-extension',
598
+ ruleName: 'PossibleTypeExtensions',
599
+ hasDidYouMeanSuggestions: true,
600
+ }, {
524
601
  category: 'Schema',
525
602
  description: 'A type extension is only valid if the type is defined and has the same kind.',
526
603
  // TODO: add in graphql-eslint v4
527
604
  recommended: false,
528
605
  requiresSchema: true,
529
606
  isDisabledForAllConfig: true,
530
- }), validationToRule('provided-required-arguments', 'ProvidedRequiredArguments', {
607
+ }), validationToRule({
608
+ ruleId: 'provided-required-arguments',
609
+ ruleName: 'ProvidedRequiredArguments',
610
+ }, {
531
611
  category: ['Schema', 'Operations'],
532
612
  description: 'A field or directive is only valid if all required (non-null without a default value) field arguments have been provided.',
533
613
  requiresSchema: true,
534
- }), validationToRule('scalar-leafs', 'ScalarLeafs', {
614
+ }), validationToRule({
615
+ ruleId: 'scalar-leafs',
616
+ ruleName: 'ScalarLeafs',
617
+ hasDidYouMeanSuggestions: true,
618
+ }, {
535
619
  category: 'Operations',
536
620
  description: 'A GraphQL document is valid only if all leaf fields (fields without sub selections) are of scalar or enum types.',
537
621
  requiresSchema: true,
538
- }), validationToRule('one-field-subscriptions', 'SingleFieldSubscriptions', {
622
+ }), validationToRule({
623
+ ruleId: 'one-field-subscriptions',
624
+ ruleName: 'SingleFieldSubscriptions',
625
+ }, {
539
626
  category: 'Operations',
540
627
  description: 'A GraphQL subscription is valid only if it contains a single root field.',
541
628
  requiresSchema: true,
542
- }), validationToRule('unique-argument-names', 'UniqueArgumentNames', {
629
+ }), validationToRule({
630
+ ruleId: 'unique-argument-names',
631
+ ruleName: 'UniqueArgumentNames',
632
+ }, {
543
633
  category: 'Operations',
544
634
  description: 'A GraphQL field or directive is only valid if all supplied arguments are uniquely named.',
545
635
  requiresSchema: true,
546
- }), validationToRule('unique-directive-names', 'UniqueDirectiveNames', {
636
+ }), validationToRule({
637
+ ruleId: 'unique-directive-names',
638
+ ruleName: 'UniqueDirectiveNames',
639
+ }, {
547
640
  category: 'Schema',
548
641
  description: 'A GraphQL document is only valid if all defined directives have unique names.',
549
- }), validationToRule('unique-directive-names-per-location', 'UniqueDirectivesPerLocation', {
642
+ }), validationToRule({
643
+ ruleId: 'unique-directive-names-per-location',
644
+ ruleName: 'UniqueDirectivesPerLocation',
645
+ }, {
550
646
  category: ['Schema', 'Operations'],
551
647
  description: 'A GraphQL document is only valid if all non-repeatable directives at a given location are uniquely named.',
552
648
  requiresSchema: true,
553
- }), validationToRule('unique-enum-value-names', 'UniqueEnumValueNames', {
649
+ }), validationToRule({
650
+ ruleId: 'unique-enum-value-names',
651
+ ruleName: 'UniqueEnumValueNames',
652
+ }, {
554
653
  category: 'Schema',
555
654
  description: 'A GraphQL enum type is only valid if all its values are uniquely named.',
556
655
  recommended: false,
557
656
  isDisabledForAllConfig: true,
558
- }), validationToRule('unique-field-definition-names', 'UniqueFieldDefinitionNames', {
657
+ }), validationToRule({
658
+ ruleId: 'unique-field-definition-names',
659
+ ruleName: 'UniqueFieldDefinitionNames',
660
+ }, {
559
661
  category: 'Schema',
560
662
  description: 'A GraphQL complex type is only valid if all its fields are uniquely named.',
561
- }), validationToRule('unique-input-field-names', 'UniqueInputFieldNames', {
663
+ }), validationToRule({
664
+ ruleId: 'unique-input-field-names',
665
+ ruleName: 'UniqueInputFieldNames',
666
+ }, {
562
667
  category: 'Operations',
563
668
  description: 'A GraphQL input object value is only valid if all supplied fields are uniquely named.',
564
- }), validationToRule('unique-operation-types', 'UniqueOperationTypes', {
669
+ }), validationToRule({
670
+ ruleId: 'unique-operation-types',
671
+ ruleName: 'UniqueOperationTypes',
672
+ }, {
565
673
  category: 'Schema',
566
674
  description: 'A GraphQL document is only valid if it has only one type per operation.',
567
- }), validationToRule('unique-type-names', 'UniqueTypeNames', {
675
+ }), validationToRule({
676
+ ruleId: 'unique-type-names',
677
+ ruleName: 'UniqueTypeNames',
678
+ }, {
568
679
  category: 'Schema',
569
680
  description: 'A GraphQL document is only valid if all defined types have unique names.',
570
- }), validationToRule('unique-variable-names', 'UniqueVariableNames', {
681
+ }), validationToRule({
682
+ ruleId: 'unique-variable-names',
683
+ ruleName: 'UniqueVariableNames',
684
+ }, {
571
685
  category: 'Operations',
572
686
  description: 'A GraphQL operation is only valid if all its variables are uniquely named.',
573
687
  requiresSchema: true,
574
- }), validationToRule('value-literals-of-correct-type', 'ValuesOfCorrectType', {
688
+ }), validationToRule({
689
+ ruleId: 'value-literals-of-correct-type',
690
+ ruleName: 'ValuesOfCorrectType',
691
+ hasDidYouMeanSuggestions: true,
692
+ }, {
575
693
  category: 'Operations',
576
694
  description: 'A GraphQL document is only valid if all value literals are of the type expected at their position.',
577
695
  requiresSchema: true,
578
- }), validationToRule('variables-are-input-types', 'VariablesAreInputTypes', {
696
+ }), validationToRule({
697
+ ruleId: 'variables-are-input-types',
698
+ ruleName: 'VariablesAreInputTypes',
699
+ }, {
579
700
  category: 'Operations',
580
701
  description: 'A GraphQL operation is only valid if all the variables it defines are of input types (scalar, enum, or input object).',
581
702
  requiresSchema: true,
582
- }), validationToRule('variables-in-allowed-position', 'VariablesInAllowedPosition', {
703
+ }), validationToRule({
704
+ ruleId: 'variables-in-allowed-position',
705
+ ruleName: 'VariablesInAllowedPosition',
706
+ }, {
583
707
  category: 'Operations',
584
708
  description: 'Variables passed to field arguments conform to type.',
585
709
  requiresSchema: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphql-eslint/eslint-plugin",
3
- "version": "3.13.2-alpha-20221109140613-1815aa1",
3
+ "version": "3.14.0-alpha-20221220004017-f1f0904",
4
4
  "description": "GraphQL plugin for ESLint",
5
5
  "sideEffects": false,
6
6
  "peerDependencies": {
@@ -10,7 +10,7 @@
10
10
  "@babel/code-frame": "^7.18.6",
11
11
  "@graphql-tools/code-file-loader": "^7.3.6",
12
12
  "@graphql-tools/graphql-tag-pluck": "^7.3.6",
13
- "@graphql-tools/utils": "^8.12.0",
13
+ "@graphql-tools/utils": "^9.0.0",
14
14
  "chalk": "^4.1.2",
15
15
  "debug": "^4.3.4",
16
16
  "fast-glob": "^3.2.12",
package/processor.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Linter } from 'eslint';
2
- export declare type Block = Linter.ProcessorFile & {
2
+ export type Block = Linter.ProcessorFile & {
3
3
  lineOffset: number;
4
4
  offset: number;
5
5
  };
@@ -4,7 +4,7 @@ declare const valuesEnum: ['EnumTypeDefinition'];
4
4
  declare const selectionsEnum: ('OperationDefinition' | 'FragmentDefinition')[];
5
5
  declare const variablesEnum: ['OperationDefinition'];
6
6
  declare const argumentsEnum: ('FieldDefinition' | 'Field' | 'DirectiveDefinition' | 'Directive')[];
7
- export declare type AlphabetizeConfig = {
7
+ export type AlphabetizeConfig = {
8
8
  fields?: typeof fieldsEnum;
9
9
  values?: typeof valuesEnum;
10
10
  selections?: typeof selectionsEnum;
@@ -1,5 +1,5 @@
1
1
  import { GraphQLESLintRule } from '../types';
2
- declare type DescriptionStyleRuleConfig = {
2
+ type DescriptionStyleRuleConfig = {
3
3
  style: 'inline' | 'block';
4
4
  };
5
5
  declare const rule: GraphQLESLintRule<[DescriptionStyleRuleConfig]>;
@@ -1,2 +1,2 @@
1
- import type { GraphQLESLintRule } from '../types';
1
+ import { GraphQLESLintRule } from '../types';
2
2
  export declare const GRAPHQL_JS_VALIDATIONS: Record<string, GraphQLESLintRule>;
@@ -1,5 +1,5 @@
1
1
  import { GraphQLESLintRule } from '../types';
2
- declare type InputNameRuleConfig = {
2
+ type InputNameRuleConfig = {
3
3
  checkInputType?: boolean;
4
4
  caseSensitiveInputType?: boolean;
5
5
  checkQueries?: boolean;
@@ -1,12 +1,12 @@
1
1
  import { CaseStyle as _CaseStyle } from '../utils';
2
2
  import { GraphQLESLintRule } from '../types';
3
- declare type CaseStyle = _CaseStyle | 'matchDocumentStyle';
3
+ type CaseStyle = _CaseStyle | 'matchDocumentStyle';
4
4
  declare const ACCEPTED_EXTENSIONS: ['.gql', '.graphql'];
5
- declare type PropertySchema = {
5
+ type PropertySchema = {
6
6
  style?: CaseStyle;
7
7
  suffix?: string;
8
8
  };
9
- export declare type MatchDocumentFilenameRuleConfig = {
9
+ export type MatchDocumentFilenameRuleConfig = {
10
10
  fileExtension?: typeof ACCEPTED_EXTENSIONS[number];
11
11
  query?: CaseStyle | PropertySchema;
12
12
  mutation?: CaseStyle | PropertySchema;
@@ -15,9 +15,9 @@ declare const KindToDisplayName: {
15
15
  FragmentDefinition: string;
16
16
  VariableDefinition: string;
17
17
  };
18
- declare type AllowedKind = keyof typeof KindToDisplayName;
19
- declare type AllowedStyle = 'camelCase' | 'PascalCase' | 'snake_case' | 'UPPER_CASE';
20
- declare type PropertySchema = {
18
+ type AllowedKind = keyof typeof KindToDisplayName;
19
+ type AllowedStyle = 'camelCase' | 'PascalCase' | 'snake_case' | 'UPPER_CASE';
20
+ type PropertySchema = {
21
21
  style?: AllowedStyle;
22
22
  suffix?: string;
23
23
  prefix?: string;
@@ -25,8 +25,8 @@ declare type PropertySchema = {
25
25
  forbiddenSuffixes?: string[];
26
26
  ignorePattern?: string;
27
27
  };
28
- declare type Options = AllowedStyle | PropertySchema;
29
- export declare type NamingConventionRuleConfig = {
28
+ type Options = AllowedStyle | PropertySchema;
29
+ export type NamingConventionRuleConfig = {
30
30
  allowLeadingUnderscore?: boolean;
31
31
  allowTrailingUnderscore?: boolean;
32
32
  types?: Options;
@@ -1,6 +1,6 @@
1
1
  import type { GraphQLESLintRule } from '../types';
2
2
  declare const ROOT_TYPES: ('mutation' | 'subscription')[];
3
- declare type NoRootTypeConfig = {
3
+ type NoRootTypeConfig = {
4
4
  disallow: typeof ROOT_TYPES;
5
5
  };
6
6
  declare const rule: GraphQLESLintRule<[NoRootTypeConfig]>;
@@ -1,5 +1,5 @@
1
1
  import { GraphQLESLintRule } from '../types';
2
- export declare type RelayArgumentsConfig = {
2
+ export type RelayArgumentsConfig = {
3
3
  includeBoth?: boolean;
4
4
  };
5
5
  declare const rule: GraphQLESLintRule<[RelayArgumentsConfig], true>;
@@ -1,5 +1,5 @@
1
1
  import type { GraphQLESLintRule } from '../types';
2
- export declare type EdgeTypesConfig = {
2
+ export type EdgeTypesConfig = {
3
3
  withEdgeSuffix?: boolean;
4
4
  shouldImplementNode?: boolean;
5
5
  listTypeCanWrapOnlyEdgeType?: boolean;
@@ -1,8 +1,8 @@
1
1
  import { Kind } from 'graphql';
2
2
  import type { GraphQLESLintRule } from '../types';
3
3
  declare const ALLOWED_KINDS: readonly [Kind.OBJECT_TYPE_DEFINITION, Kind.INTERFACE_TYPE_DEFINITION, Kind.ENUM_TYPE_DEFINITION, Kind.SCALAR_TYPE_DEFINITION, Kind.INPUT_OBJECT_TYPE_DEFINITION, Kind.UNION_TYPE_DEFINITION, Kind.DIRECTIVE_DEFINITION, Kind.FIELD_DEFINITION, Kind.INPUT_VALUE_DEFINITION, Kind.ENUM_VALUE_DEFINITION, Kind.OPERATION_DEFINITION];
4
- declare type AllowedKind = typeof ALLOWED_KINDS[number];
5
- export declare type RequireDescriptionRuleConfig = {
4
+ type AllowedKind = typeof ALLOWED_KINDS[number];
5
+ export type RequireDescriptionRuleConfig = {
6
6
  types?: boolean;
7
7
  } & {
8
8
  [key in AllowedKind]?: boolean;
@@ -1,5 +1,5 @@
1
1
  import { GraphQLESLintRule } from '../types';
2
- export declare type RequireIdWhenAvailableRuleConfig = {
2
+ export type RequireIdWhenAvailableRuleConfig = {
3
3
  fieldName: string | string[];
4
4
  };
5
5
  declare const rule: GraphQLESLintRule<[RequireIdWhenAvailableRuleConfig], true>;
@@ -1,5 +1,5 @@
1
1
  import { GraphQLESLintRule } from '../types';
2
- export declare type SelectionSetDepthRuleConfig = {
2
+ export type SelectionSetDepthRuleConfig = {
3
3
  maxDepth: number;
4
4
  ignore?: string[];
5
5
  };
@@ -1,5 +1,5 @@
1
1
  import { GraphQLESLintRule } from '../types';
2
- export declare type StrictIdInTypesRuleConfig = {
2
+ export type StrictIdInTypesRuleConfig = {
3
3
  acceptedIdNames?: string[];
4
4
  acceptedIdTypes?: string[];
5
5
  exceptions?: {
package/testkit.d.ts CHANGED
@@ -2,14 +2,14 @@ import { RuleTester } from 'eslint';
2
2
  import type { ASTKindToNode } from 'graphql';
3
3
  import type { GraphQLESTreeNode } from './estree-converter';
4
4
  import type { GraphQLESLintRule, ParserOptions } from './types';
5
- export declare type GraphQLESLintRuleListener<WithTypeInfo extends boolean = false> = {
5
+ export type GraphQLESLintRuleListener<WithTypeInfo extends boolean = false> = {
6
6
  [K in keyof ASTKindToNode]?: (node: GraphQLESTreeNode<ASTKindToNode[K], WithTypeInfo>) => void;
7
7
  } & Record<string, any>;
8
- export declare type GraphQLValidTestCase<Options> = Omit<RuleTester.ValidTestCase, 'options' | 'parserOptions'> & {
8
+ export type GraphQLValidTestCase<Options> = Omit<RuleTester.ValidTestCase, 'options' | 'parserOptions'> & {
9
9
  options?: Options;
10
10
  parserOptions?: ParserOptions;
11
11
  };
12
- export declare type GraphQLInvalidTestCase<T> = GraphQLValidTestCase<T> & {
12
+ export type GraphQLInvalidTestCase<T> = GraphQLValidTestCase<T> & {
13
13
  errors: number | (RuleTester.TestCaseError | string)[];
14
14
  output?: string | null;
15
15
  };
package/types.d.ts CHANGED
@@ -5,8 +5,8 @@ import { IExtensions, IGraphQLProject } from 'graphql-config';
5
5
  import { GraphQLParseOptions } from '@graphql-tools/utils';
6
6
  import { GraphQLESLintRuleListener } from './testkit';
7
7
  import { SiblingOperations } from './documents';
8
- export declare type Schema = GraphQLSchema | Error | null;
9
- export declare type Pointer = string | string[];
8
+ export type Schema = GraphQLSchema | Error | null;
9
+ export type Pointer = string | string[];
10
10
  export interface ParserOptions {
11
11
  schema?: Pointer | Record<string, {
12
12
  headers: Record<string, string>;
@@ -24,29 +24,29 @@ export interface ParserOptions {
24
24
  skipGraphQLConfig?: boolean;
25
25
  filePath: string;
26
26
  }
27
- export declare type ParserServices = {
27
+ export type ParserServices = {
28
28
  schema: Schema;
29
29
  siblingOperations: SiblingOperations;
30
30
  };
31
- export declare type GraphQLESLintParseResult = Linter.ESLintParseResult & {
31
+ export type GraphQLESLintParseResult = Linter.ESLintParseResult & {
32
32
  services: ParserServices;
33
33
  };
34
- declare type Location = AST.SourceLocation | ESTree.Position;
35
- declare type ReportDescriptorLocation = {
34
+ type Location = AST.SourceLocation | ESTree.Position;
35
+ type ReportDescriptorLocation = {
36
36
  node: {
37
37
  loc: Location;
38
38
  };
39
39
  } | {
40
40
  loc: Location;
41
41
  };
42
- export declare type ReportDescriptor = Rule.ReportDescriptorMessage & Rule.ReportDescriptorOptions & ReportDescriptorLocation;
43
- export declare type GraphQLESLintRuleContext<Options = any[]> = Omit<Rule.RuleContext, 'parserServices' | 'report' | 'options'> & {
42
+ export type ReportDescriptor = Rule.ReportDescriptorMessage & Rule.ReportDescriptorOptions & ReportDescriptorLocation;
43
+ export type GraphQLESLintRuleContext<Options = any[]> = Omit<Rule.RuleContext, 'parserServices' | 'report' | 'options'> & {
44
44
  options: Options;
45
45
  report(descriptor: ReportDescriptor): void;
46
46
  parserServices?: ParserServices;
47
47
  };
48
- export declare type CategoryType = 'Schema' | 'Operations';
49
- export declare type RuleDocsInfo<T> = Omit<Rule.RuleMetaData['docs'], 'category' | 'suggestion'> & {
48
+ export type CategoryType = 'Schema' | 'Operations';
49
+ export type RuleDocsInfo<T> = Omit<Rule.RuleMetaData['docs'], 'category' | 'suggestion'> & {
50
50
  category: CategoryType | CategoryType[];
51
51
  requiresSchema?: true;
52
52
  requiresSiblings?: true;
@@ -62,18 +62,18 @@ export declare type RuleDocsInfo<T> = Omit<Rule.RuleMetaData['docs'], 'category'
62
62
  graphQLJSRuleName?: string;
63
63
  isDisabledForAllConfig?: true;
64
64
  };
65
- export declare type GraphQLESLintRule<Options = any[], WithTypeInfo extends boolean = false> = {
65
+ export type GraphQLESLintRule<Options = any[], WithTypeInfo extends boolean = false> = {
66
66
  create(context: GraphQLESLintRuleContext<Options>): GraphQLESLintRuleListener<WithTypeInfo>;
67
67
  meta: Omit<Rule.RuleMetaData, 'docs'> & {
68
68
  docs?: RuleDocsInfo<Options>;
69
69
  };
70
70
  };
71
- export declare type ValueOf<T> = T[keyof T];
72
- declare type Id<T> = {} & {
71
+ export type ValueOf<T> = T[keyof T];
72
+ type Id<T> = {} & {
73
73
  [P in keyof T]: T[P];
74
74
  };
75
- declare type OmitDistributive<T, K extends PropertyKey> = T extends object ? Id<OmitRecursively<T, K>> : T;
76
- export declare type OmitRecursively<T extends object, K extends PropertyKey> = Omit<{
75
+ type OmitDistributive<T, K extends PropertyKey> = T extends object ? Id<OmitRecursively<T, K>> : T;
76
+ export type OmitRecursively<T extends object, K extends PropertyKey> = Omit<{
77
77
  [P in keyof T]: OmitDistributive<T[P], K>;
78
78
  }, K>;
79
79
  export {};
package/utils.d.ts CHANGED
@@ -14,7 +14,7 @@ export declare const VIRTUAL_DOCUMENT_REGEX: RegExp;
14
14
  export declare const CWD: string;
15
15
  export declare const getTypeName: (node: any) => string;
16
16
  export declare const TYPES_KINDS: readonly [Kind.OBJECT_TYPE_DEFINITION, Kind.INTERFACE_TYPE_DEFINITION, Kind.ENUM_TYPE_DEFINITION, Kind.SCALAR_TYPE_DEFINITION, Kind.INPUT_OBJECT_TYPE_DEFINITION, Kind.UNION_TYPE_DEFINITION];
17
- export declare type CaseStyle = 'camelCase' | 'PascalCase' | 'snake_case' | 'UPPER_CASE' | 'kebab-case';
17
+ export type CaseStyle = 'camelCase' | 'PascalCase' | 'snake_case' | 'UPPER_CASE' | 'kebab-case';
18
18
  export declare const camelCase: (str: string) => string;
19
19
  export declare const convertCase: (style: CaseStyle, str: string) => string;
20
20
  export declare function getLocation(start: Position, fieldName?: string): AST.SourceLocation;