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

Sign up to get free protection for your applications and to get access to all the features.
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;