@graphql-eslint/eslint-plugin 3.1.0-alpha-878c908.0 → 3.2.0-alpha-6aa2721.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -6,7 +6,6 @@ function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'defau
6
6
 
7
7
  const graphql = require('graphql');
8
8
  const validate = require('graphql/validation/validate');
9
- const _import = require('@graphql-tools/import');
10
9
  const fs = require('fs');
11
10
  const path = require('path');
12
11
  const utils = require('@graphql-tools/utils');
@@ -335,59 +334,99 @@ function getLocation(loc, fieldName = '', offset) {
335
334
  };
336
335
  }
337
336
 
338
- function extractRuleName(stack) {
339
- const match = (stack || '').match(/validation[/\\]rules[/\\](.*?)\.js:/) || [];
340
- return match[1] || null;
337
+ function validateDocument(sourceNode, context, schema, documentNode, rule) {
338
+ if (documentNode.definitions.length === 0) {
339
+ return;
340
+ }
341
+ try {
342
+ const validationErrors = schema
343
+ ? graphql.validate(schema, documentNode, [rule])
344
+ : validate.validateSDL(documentNode, null, [rule]);
345
+ for (const error of validationErrors) {
346
+ context.report({
347
+ loc: getLocation({ start: error.locations[0] }),
348
+ message: error.message,
349
+ });
350
+ }
351
+ }
352
+ catch (e) {
353
+ context.report({
354
+ node: sourceNode,
355
+ message: e.message,
356
+ });
357
+ }
341
358
  }
342
- function validateDoc(sourceNode, context, schema, documentNode, rules, ruleName = null) {
343
- var _a;
344
- if (((_a = documentNode === null || documentNode === void 0 ? void 0 : documentNode.definitions) === null || _a === void 0 ? void 0 : _a.length) > 0) {
345
- try {
346
- const validationErrors = schema ? graphql.validate(schema, documentNode, rules) : validate.validateSDL(documentNode, null, rules);
347
- for (const error of validationErrors) {
348
- const validateRuleName = ruleName || `[${extractRuleName(error.stack)}]`;
349
- context.report({
350
- loc: getLocation({ start: error.locations[0] }),
351
- message: ruleName ? error.message : `${validateRuleName} ${error.message}`,
352
- });
359
+ const getFragmentDefsAndFragmentSpreads = (schema, node) => {
360
+ const typeInfo = new graphql.TypeInfo(schema);
361
+ const fragmentDefs = new Set();
362
+ const fragmentSpreads = new Set();
363
+ const visitor = graphql.visitWithTypeInfo(typeInfo, {
364
+ FragmentDefinition(node) {
365
+ fragmentDefs.add(`${node.name.value}:${node.typeCondition.name.value}`);
366
+ },
367
+ FragmentSpread(node) {
368
+ const parentType = typeInfo.getParentType();
369
+ if (parentType) {
370
+ fragmentSpreads.add(`${node.name.value}:${parentType.name}`);
353
371
  }
372
+ },
373
+ });
374
+ graphql.visit(node, visitor);
375
+ return { fragmentDefs, fragmentSpreads };
376
+ };
377
+ const getMissingFragments = (schema, node) => {
378
+ const { fragmentDefs, fragmentSpreads } = getFragmentDefsAndFragmentSpreads(schema, node);
379
+ return [...fragmentSpreads].filter(name => !fragmentDefs.has(name));
380
+ };
381
+ const handleMissingFragments = ({ ruleId, context, schema, node }) => {
382
+ const missingFragments = getMissingFragments(schema, node);
383
+ if (missingFragments.length > 0) {
384
+ const siblings = requireSiblingsOperations(ruleId, context);
385
+ const fragmentsToAdd = [];
386
+ for (const missingFragment of missingFragments) {
387
+ const [fragmentName, fragmentTypeName] = missingFragment.split(':');
388
+ const fragments = siblings
389
+ .getFragment(fragmentName)
390
+ .map(source => source.document)
391
+ .filter(fragment => fragment.typeCondition.name.value === fragmentTypeName);
392
+ fragmentsToAdd.push(fragments[0]);
354
393
  }
355
- catch (e) {
356
- context.report({
357
- node: sourceNode,
358
- message: e.message,
394
+ if (fragmentsToAdd.length > 0) {
395
+ // recall fn to make sure to add fragments inside fragments
396
+ return handleMissingFragments({
397
+ ruleId,
398
+ context,
399
+ schema,
400
+ node: {
401
+ kind: graphql.Kind.DOCUMENT,
402
+ definitions: [...node.definitions, ...fragmentsToAdd],
403
+ },
359
404
  });
360
405
  }
361
406
  }
362
- }
363
- const isGraphQLImportFile = rawSDL => {
364
- const trimmedRawSDL = rawSDL.trimLeft();
365
- return trimmedRawSDL.startsWith('# import') || trimmedRawSDL.startsWith('#import');
407
+ return node;
366
408
  };
367
- const validationToRule = (name, ruleName, docs, getDocumentNode) => {
368
- var _a;
409
+ const validationToRule = (ruleId, ruleName, docs, getDocumentNode) => {
369
410
  let ruleFn = null;
370
411
  try {
371
412
  ruleFn = require(`graphql/validation/rules/${ruleName}Rule`)[`${ruleName}Rule`];
372
413
  }
373
- catch (e) {
414
+ catch (_a) {
374
415
  try {
375
416
  ruleFn = require(`graphql/validation/rules/${ruleName}`)[`${ruleName}Rule`];
376
417
  }
377
- catch (e) {
418
+ catch (_b) {
378
419
  ruleFn = require('graphql/validation')[`${ruleName}Rule`];
379
420
  }
380
421
  }
381
- const requiresSchema = (_a = docs.requiresSchema) !== null && _a !== void 0 ? _a : true;
382
422
  return {
383
- [name]: {
423
+ [ruleId]: {
384
424
  meta: {
385
425
  docs: {
386
426
  recommended: true,
387
427
  ...docs,
388
428
  graphQLJSRuleName: ruleName,
389
- requiresSchema,
390
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${name}.md`,
429
+ url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${ruleId}.md`,
391
430
  description: `${docs.description}\n\n> This rule is a wrapper around a \`graphql-js\` validation function. [You can find its source code here](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/${ruleName}Rule.ts).`,
392
431
  },
393
432
  },
@@ -396,56 +435,53 @@ const validationToRule = (name, ruleName, docs, getDocumentNode) => {
396
435
  Document(node) {
397
436
  if (!ruleFn) {
398
437
  // eslint-disable-next-line no-console
399
- console.warn(`You rule "${name}" depends on a GraphQL validation rule ("${ruleName}") but it's not available in the "graphql-js" version you are using. Skipping...`);
438
+ console.warn(`You rule "${ruleId}" depends on a GraphQL validation rule "${ruleName}" but it's not available in the "graphql-js" version you are using. Skipping...`);
400
439
  return;
401
440
  }
402
- const schema = requiresSchema ? requireGraphQLSchemaFromContext(name, context) : null;
403
- let documentNode;
404
- const isRealFile = fs.existsSync(context.getFilename());
405
- if (isRealFile && getDocumentNode) {
406
- documentNode = getDocumentNode(context);
407
- }
408
- validateDoc(node, context, schema, documentNode || node.rawNode(), [ruleFn], ruleName);
441
+ const schema = docs.requiresSchema ? requireGraphQLSchemaFromContext(ruleId, context) : null;
442
+ const documentNode = getDocumentNode
443
+ ? getDocumentNode({ ruleId, context, schema, node: node.rawNode() })
444
+ : node.rawNode();
445
+ validateDocument(node, context, schema, documentNode, ruleFn);
409
446
  },
410
447
  };
411
448
  },
412
449
  },
413
450
  };
414
451
  };
415
- const importFiles = (context) => {
416
- const code = context.getSourceCode().text;
417
- if (!isGraphQLImportFile(code)) {
418
- return null;
419
- }
420
- // Import documents because file contains '#import' comments
421
- return _import.processImport(context.getFilename());
422
- };
423
452
  const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-definitions', 'ExecutableDefinitions', {
424
453
  category: 'Operations',
425
454
  description: `A GraphQL document is only valid for execution if all definitions are either operation or fragment definitions.`,
455
+ requiresSchema: true,
426
456
  }), validationToRule('fields-on-correct-type', 'FieldsOnCorrectType', {
427
457
  category: 'Operations',
428
458
  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`.',
459
+ requiresSchema: true,
429
460
  }), validationToRule('fragments-on-composite-type', 'FragmentsOnCompositeTypes', {
430
461
  category: 'Operations',
431
462
  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.`,
463
+ requiresSchema: true,
432
464
  }), validationToRule('known-argument-names', 'KnownArgumentNames', {
433
465
  category: ['Schema', 'Operations'],
434
466
  description: `A GraphQL field is only valid if all supplied arguments are defined by that field.`,
467
+ requiresSchema: true,
435
468
  }), validationToRule('known-directives', 'KnownDirectives', {
436
469
  category: ['Schema', 'Operations'],
437
470
  description: `A GraphQL document is only valid if all \`@directives\` are known by the schema and legally positioned.`,
471
+ requiresSchema: true,
438
472
  }), validationToRule('known-fragment-names', 'KnownFragmentNames', {
439
473
  category: 'Operations',
440
474
  description: `A GraphQL document is only valid if all \`...Fragment\` fragment spreads refer to fragments defined in the same document.`,
475
+ requiresSchema: true,
476
+ requiresSiblings: true,
441
477
  examples: [
442
478
  {
443
- title: 'Incorrect (fragment not defined in the document)',
479
+ title: 'Incorrect',
444
480
  code: /* GraphQL */ `
445
481
  query {
446
482
  user {
447
483
  id
448
- ...UserFields
484
+ ...UserFields # fragment not defined in the document
449
485
  }
450
486
  }
451
487
  `,
@@ -467,153 +503,151 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
467
503
  `,
468
504
  },
469
505
  {
470
- title: 'Correct (existing import to UserFields fragment)',
506
+ title: 'Correct (`UserFields` fragment located in a separate file)',
471
507
  code: /* GraphQL */ `
472
- #import '../UserFields.gql'
473
-
508
+ # user.gql
474
509
  query {
475
510
  user {
476
511
  id
477
512
  ...UserFields
478
513
  }
479
514
  }
480
- `,
481
- },
482
- {
483
- title: "False positive case\n\nFor extracting documents from code under the hood we use [graphql-tag-pluck](https://graphql-tools.com/docs/graphql-tag-pluck) that [don't support string interpolation](https://stackoverflow.com/questions/62749847/graphql-codegen-dynamic-fields-with-interpolation/62751311#62751311) for this moment.",
484
- code: `
485
- const USER_FIELDS = gql\`
486
- fragment UserFields on User {
487
- id
488
- }
489
- \`
490
-
491
- const GET_USER = /* GraphQL */ \`
492
- # eslint @graphql-eslint/known-fragment-names: 'error'
493
-
494
- query User {
495
- user {
496
- ...UserFields
497
- }
498
- }
499
515
 
500
- # Will give false positive error 'Unknown fragment "UserFields"'
501
- \${USER_FIELDS}
502
- \``,
516
+ # user-fields.gql
517
+ fragment UserFields on User {
518
+ id
519
+ }
520
+ `,
503
521
  },
504
522
  ],
505
- }, importFiles), validationToRule('known-type-names', 'KnownTypeNames', {
523
+ }, handleMissingFragments), validationToRule('known-type-names', 'KnownTypeNames', {
506
524
  category: ['Schema', 'Operations'],
507
525
  description: `A GraphQL document is only valid if referenced types (specifically variable definitions and fragment conditions) are defined by the type schema.`,
526
+ requiresSchema: true,
508
527
  }), validationToRule('lone-anonymous-operation', 'LoneAnonymousOperation', {
509
528
  category: 'Operations',
510
529
  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.`,
530
+ requiresSchema: true,
511
531
  }), validationToRule('lone-schema-definition', 'LoneSchemaDefinition', {
512
532
  category: 'Schema',
513
533
  description: `A GraphQL document is only valid if it contains only one schema definition.`,
514
- requiresSchema: false,
515
534
  }), validationToRule('no-fragment-cycles', 'NoFragmentCycles', {
516
535
  category: 'Operations',
517
536
  description: `A GraphQL fragment is only valid when it does not have cycles in fragments usage.`,
537
+ requiresSchema: true,
518
538
  }), validationToRule('no-undefined-variables', 'NoUndefinedVariables', {
519
539
  category: 'Operations',
520
540
  description: `A GraphQL operation is only valid if all variables encountered, both directly and via fragment spreads, are defined by that operation.`,
521
- }, importFiles), validationToRule('no-unused-fragments', 'NoUnusedFragments', {
541
+ requiresSchema: true,
542
+ requiresSiblings: true,
543
+ }, handleMissingFragments), validationToRule('no-unused-fragments', 'NoUnusedFragments', {
522
544
  category: 'Operations',
523
545
  description: `A GraphQL document is only valid if all fragment definitions are spread within operations, or spread within other fragments spread within operations.`,
546
+ requiresSchema: true,
524
547
  requiresSiblings: true,
525
- }, context => {
526
- const siblings = requireSiblingsOperations('no-unused-fragments', context);
527
- const documents = [...siblings.getOperations(), ...siblings.getFragments()]
528
- .filter(({ document }) => isGraphQLImportFile(document.loc.source.body))
529
- .map(({ filePath, document }) => ({
530
- filePath,
531
- code: document.loc.source.body,
532
- }));
533
- const getParentNode = (filePath) => {
534
- for (const { filePath: docFilePath, code } of documents) {
535
- const isFileImported = code
536
- .split('\n')
537
- .filter(isGraphQLImportFile)
538
- .map(line => _import.parseImportLine(line.replace('#', '')))
539
- .some(o => filePath === path.join(path.dirname(docFilePath), o.from));
540
- if (!isFileImported) {
541
- continue;
542
- }
543
- // Import first file that import this file
544
- const document = _import.processImport(docFilePath);
545
- // Import most top file that import this file
546
- return getParentNode(docFilePath) || document;
548
+ }, ({ ruleId, context, schema, node }) => {
549
+ const siblings = requireSiblingsOperations(ruleId, context);
550
+ const FilePathToDocumentsMap = [...siblings.getOperations(), ...siblings.getFragments()].reduce((map, { filePath, document }) => {
551
+ var _a;
552
+ (_a = map[filePath]) !== null && _a !== void 0 ? _a : (map[filePath] = []);
553
+ map[filePath].push(document);
554
+ return map;
555
+ }, Object.create(null));
556
+ const getParentNode = (currentFilePath, node) => {
557
+ const { fragmentDefs } = getFragmentDefsAndFragmentSpreads(schema, node);
558
+ if (fragmentDefs.size === 0) {
559
+ return node;
547
560
  }
548
- return null;
561
+ // skip iteration over documents for current filepath
562
+ delete FilePathToDocumentsMap[currentFilePath];
563
+ for (const [filePath, documents] of Object.entries(FilePathToDocumentsMap)) {
564
+ const missingFragments = getMissingFragments(schema, {
565
+ kind: graphql.Kind.DOCUMENT,
566
+ definitions: documents,
567
+ });
568
+ const isCurrentFileImportFragment = missingFragments.some(fragment => fragmentDefs.has(fragment));
569
+ if (isCurrentFileImportFragment) {
570
+ return getParentNode(filePath, {
571
+ kind: graphql.Kind.DOCUMENT,
572
+ definitions: [...node.definitions, ...documents],
573
+ });
574
+ }
575
+ }
576
+ return node;
549
577
  };
550
- return getParentNode(context.getFilename());
578
+ return getParentNode(context.getFilename(), node);
551
579
  }), validationToRule('no-unused-variables', 'NoUnusedVariables', {
552
580
  category: 'Operations',
553
581
  description: `A GraphQL operation is only valid if all variables defined by an operation are used, either directly or within a spread fragment.`,
554
- }, importFiles), validationToRule('overlapping-fields-can-be-merged', 'OverlappingFieldsCanBeMerged', {
582
+ requiresSchema: true,
583
+ requiresSiblings: true,
584
+ }, handleMissingFragments), validationToRule('overlapping-fields-can-be-merged', 'OverlappingFieldsCanBeMerged', {
555
585
  category: 'Operations',
556
586
  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.`,
587
+ requiresSchema: true,
557
588
  }), validationToRule('possible-fragment-spread', 'PossibleFragmentSpreads', {
558
589
  category: 'Operations',
559
590
  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.`,
591
+ requiresSchema: true,
560
592
  }), validationToRule('possible-type-extension', 'PossibleTypeExtensions', {
561
593
  category: 'Schema',
562
594
  description: `A type extension is only valid if the type is defined and has the same kind.`,
563
- requiresSchema: false,
564
595
  recommended: false, // TODO: enable after https://github.com/dotansimha/graphql-eslint/issues/787 will be fixed
565
596
  }), validationToRule('provided-required-arguments', 'ProvidedRequiredArguments', {
566
597
  category: ['Schema', 'Operations'],
567
598
  description: `A field or directive is only valid if all required (non-null without a default value) field arguments have been provided.`,
599
+ requiresSchema: true,
568
600
  }), validationToRule('scalar-leafs', 'ScalarLeafs', {
569
601
  category: 'Operations',
570
602
  description: `A GraphQL document is valid only if all leaf fields (fields without sub selections) are of scalar or enum types.`,
603
+ requiresSchema: true,
571
604
  }), validationToRule('one-field-subscriptions', 'SingleFieldSubscriptions', {
572
605
  category: 'Operations',
573
606
  description: `A GraphQL subscription is valid only if it contains a single root field.`,
607
+ requiresSchema: true,
574
608
  }), validationToRule('unique-argument-names', 'UniqueArgumentNames', {
575
609
  category: 'Operations',
576
610
  description: `A GraphQL field or directive is only valid if all supplied arguments are uniquely named.`,
611
+ requiresSchema: true,
577
612
  }), validationToRule('unique-directive-names', 'UniqueDirectiveNames', {
578
613
  category: 'Schema',
579
614
  description: `A GraphQL document is only valid if all defined directives have unique names.`,
580
- requiresSchema: false,
581
615
  }), validationToRule('unique-directive-names-per-location', 'UniqueDirectivesPerLocation', {
582
616
  category: ['Schema', 'Operations'],
583
617
  description: `A GraphQL document is only valid if all non-repeatable directives at a given location are uniquely named.`,
618
+ requiresSchema: true,
584
619
  }), validationToRule('unique-enum-value-names', 'UniqueEnumValueNames', {
585
620
  category: 'Schema',
586
621
  description: `A GraphQL enum type is only valid if all its values are uniquely named.`,
587
- requiresSchema: false,
588
622
  recommended: false,
589
623
  }), validationToRule('unique-field-definition-names', 'UniqueFieldDefinitionNames', {
590
624
  category: 'Schema',
591
625
  description: `A GraphQL complex type is only valid if all its fields are uniquely named.`,
592
- requiresSchema: false,
593
626
  }), validationToRule('unique-input-field-names', 'UniqueInputFieldNames', {
594
627
  category: 'Operations',
595
628
  description: `A GraphQL input object value is only valid if all supplied fields are uniquely named.`,
596
- requiresSchema: false,
597
629
  }), validationToRule('unique-operation-types', 'UniqueOperationTypes', {
598
630
  category: 'Schema',
599
631
  description: `A GraphQL document is only valid if it has only one type per operation.`,
600
- requiresSchema: false,
601
632
  }), validationToRule('unique-type-names', 'UniqueTypeNames', {
602
633
  category: 'Schema',
603
634
  description: `A GraphQL document is only valid if all defined types have unique names.`,
604
- requiresSchema: false,
605
635
  }), validationToRule('unique-variable-names', 'UniqueVariableNames', {
606
636
  category: 'Operations',
607
637
  description: `A GraphQL operation is only valid if all its variables are uniquely named.`,
638
+ requiresSchema: true,
608
639
  }), validationToRule('value-literals-of-correct-type', 'ValuesOfCorrectType', {
609
640
  category: 'Operations',
610
641
  description: `A GraphQL document is only valid if all value literals are of the type expected at their position.`,
642
+ requiresSchema: true,
611
643
  }), validationToRule('variables-are-input-types', 'VariablesAreInputTypes', {
612
644
  category: 'Operations',
613
645
  description: `A GraphQL operation is only valid if all the variables it defines are of input types (scalar, enum, or input object).`,
646
+ requiresSchema: true,
614
647
  }), validationToRule('variables-in-allowed-position', 'VariablesInAllowedPosition', {
615
648
  category: 'Operations',
616
649
  description: `Variables passed to field arguments conform to type.`,
650
+ requiresSchema: true,
617
651
  }));
618
652
 
619
653
  const ALPHABETIZE = 'ALPHABETIZE';
@@ -1845,7 +1879,7 @@ const HASHTAG_COMMENT = 'HASHTAG_COMMENT';
1845
1879
  const rule$9 = {
1846
1880
  meta: {
1847
1881
  messages: {
1848
- [HASHTAG_COMMENT]: 'Using hashtag (#) for adding GraphQL descriptions is not allowed. Prefer using """ for multiline, or " for a single line description.',
1882
+ [HASHTAG_COMMENT]: `Using hashtag (#) for adding GraphQL descriptions is not allowed. Prefer using """ for multiline, or " for a single line description.`,
1849
1883
  },
1850
1884
  docs: {
1851
1885
  description: 'Requires to use `"""` or `"` for adding a GraphQL description instead of `#`.\nAllows to use hashtag for comments, as long as it\'s not attached to an AST definition.',
@@ -1892,14 +1926,15 @@ const rule$9 = {
1892
1926
  schema: [],
1893
1927
  },
1894
1928
  create(context) {
1929
+ const selector = `${graphql.Kind.DOCUMENT}[definitions.0.kind!=/^(${graphql.Kind.OPERATION_DEFINITION}|${graphql.Kind.FRAGMENT_DEFINITION})$/]`;
1895
1930
  return {
1896
- Document(node) {
1931
+ [selector](node) {
1897
1932
  const rawNode = node.rawNode();
1898
1933
  let token = rawNode.loc.startToken;
1899
1934
  while (token !== null) {
1900
1935
  const { kind, prev, next, value, line, column } = token;
1901
1936
  if (kind === graphql.TokenKind.COMMENT && prev && next) {
1902
- const isEslintComment = value.trimLeft().startsWith('eslint');
1937
+ const isEslintComment = value.trimStart().startsWith('eslint');
1903
1938
  const linesAfter = next.line - line;
1904
1939
  if (!isEslintComment && line !== prev.line && next.kind === graphql.TokenKind.NAME && linesAfter < 2) {
1905
1940
  context.report({
@@ -2106,7 +2141,22 @@ const rule$c = {
2106
2141
  };
2107
2142
 
2108
2143
  const UNREACHABLE_TYPE = 'UNREACHABLE_TYPE';
2109
- const RULE_NAME = 'no-unreachable-types';
2144
+ const RULE_ID = 'no-unreachable-types';
2145
+ const KINDS = [
2146
+ graphql.Kind.DIRECTIVE_DEFINITION,
2147
+ graphql.Kind.OBJECT_TYPE_DEFINITION,
2148
+ graphql.Kind.OBJECT_TYPE_EXTENSION,
2149
+ graphql.Kind.INTERFACE_TYPE_DEFINITION,
2150
+ graphql.Kind.INTERFACE_TYPE_EXTENSION,
2151
+ graphql.Kind.SCALAR_TYPE_DEFINITION,
2152
+ graphql.Kind.SCALAR_TYPE_EXTENSION,
2153
+ graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION,
2154
+ graphql.Kind.INPUT_OBJECT_TYPE_EXTENSION,
2155
+ graphql.Kind.UNION_TYPE_DEFINITION,
2156
+ graphql.Kind.UNION_TYPE_EXTENSION,
2157
+ graphql.Kind.ENUM_TYPE_DEFINITION,
2158
+ graphql.Kind.ENUM_TYPE_EXTENSION,
2159
+ ];
2110
2160
  const rule$d = {
2111
2161
  meta: {
2112
2162
  messages: {
@@ -2115,7 +2165,7 @@ const rule$d = {
2115
2165
  docs: {
2116
2166
  description: `Requires all types to be reachable at some level by root level fields.`,
2117
2167
  category: 'Schema',
2118
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME}.md`,
2168
+ url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
2119
2169
  requiresSchema: true,
2120
2170
  examples: [
2121
2171
  {
@@ -2147,43 +2197,36 @@ const rule$d = {
2147
2197
  ],
2148
2198
  recommended: true,
2149
2199
  },
2150
- fixable: 'code',
2151
2200
  type: 'suggestion',
2152
2201
  schema: [],
2202
+ hasSuggestions: true,
2153
2203
  },
2154
2204
  create(context) {
2155
- const reachableTypes = requireReachableTypesFromContext(RULE_NAME, context);
2156
- function ensureReachability(node) {
2157
- const typeName = node.name.value;
2158
- if (!reachableTypes.has(typeName)) {
2159
- context.report({
2160
- loc: getLocation(node.name.loc, typeName, { offsetStart: node.kind === graphql.Kind.DIRECTIVE_DEFINITION ? 2 : 1 }),
2161
- messageId: UNREACHABLE_TYPE,
2162
- data: { typeName },
2163
- fix: fixer => fixer.remove(node),
2164
- });
2165
- }
2166
- }
2205
+ const reachableTypes = requireReachableTypesFromContext(RULE_ID, context);
2206
+ const selector = KINDS.join(',');
2167
2207
  return {
2168
- DirectiveDefinition: ensureReachability,
2169
- ObjectTypeDefinition: ensureReachability,
2170
- ObjectTypeExtension: ensureReachability,
2171
- InterfaceTypeDefinition: ensureReachability,
2172
- InterfaceTypeExtension: ensureReachability,
2173
- ScalarTypeDefinition: ensureReachability,
2174
- ScalarTypeExtension: ensureReachability,
2175
- InputObjectTypeDefinition: ensureReachability,
2176
- InputObjectTypeExtension: ensureReachability,
2177
- UnionTypeDefinition: ensureReachability,
2178
- UnionTypeExtension: ensureReachability,
2179
- EnumTypeDefinition: ensureReachability,
2180
- EnumTypeExtension: ensureReachability,
2208
+ [selector](node) {
2209
+ const typeName = node.name.value;
2210
+ if (!reachableTypes.has(typeName)) {
2211
+ context.report({
2212
+ loc: getLocation(node.name.loc, typeName, { offsetStart: node.kind === graphql.Kind.DIRECTIVE_DEFINITION ? 2 : 1 }),
2213
+ messageId: UNREACHABLE_TYPE,
2214
+ data: { typeName },
2215
+ suggest: [
2216
+ {
2217
+ desc: `Remove ${typeName}`,
2218
+ fix: fixer => fixer.remove(node),
2219
+ },
2220
+ ],
2221
+ });
2222
+ }
2223
+ },
2181
2224
  };
2182
2225
  },
2183
2226
  };
2184
2227
 
2185
2228
  const UNUSED_FIELD = 'UNUSED_FIELD';
2186
- const RULE_NAME$1 = 'no-unused-fields';
2229
+ const RULE_ID$1 = 'no-unused-fields';
2187
2230
  const rule$e = {
2188
2231
  meta: {
2189
2232
  messages: {
@@ -2192,7 +2235,7 @@ const rule$e = {
2192
2235
  docs: {
2193
2236
  description: `Requires all fields to be used at some level by siblings operations.`,
2194
2237
  category: 'Schema',
2195
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$1}.md`,
2238
+ url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$1}.md`,
2196
2239
  requiresSiblings: true,
2197
2240
  requiresSchema: true,
2198
2241
  examples: [
@@ -2239,12 +2282,12 @@ const rule$e = {
2239
2282
  },
2240
2283
  ],
2241
2284
  },
2242
- fixable: 'code',
2243
2285
  type: 'suggestion',
2244
2286
  schema: [],
2287
+ hasSuggestions: true,
2245
2288
  },
2246
2289
  create(context) {
2247
- const usedFields = requireUsedFieldsFromContext(RULE_NAME$1, context);
2290
+ const usedFields = requireUsedFieldsFromContext(RULE_ID$1, context);
2248
2291
  return {
2249
2292
  FieldDefinition(node) {
2250
2293
  var _a;
@@ -2258,22 +2301,18 @@ const rule$e = {
2258
2301
  loc: getLocation(node.loc, fieldName),
2259
2302
  messageId: UNUSED_FIELD,
2260
2303
  data: { fieldName },
2261
- fix(fixer) {
2262
- const sourceCode = context.getSourceCode();
2263
- const tokenBefore = sourceCode.getTokenBefore(node);
2264
- const tokenAfter = sourceCode.getTokenAfter(node);
2265
- const isEmptyType = tokenBefore.type === '{' && tokenAfter.type === '}';
2266
- if (isEmptyType) {
2267
- // Remove type
2268
- const { parent } = node;
2269
- const parentBeforeToken = sourceCode.getTokenBefore(parent);
2270
- return parentBeforeToken
2271
- ? fixer.removeRange([parentBeforeToken.range[1], parent.range[1]])
2272
- : fixer.remove(parent);
2273
- }
2274
- // Remove whitespace before token
2275
- return fixer.removeRange([tokenBefore.range[1], node.range[1]]);
2276
- },
2304
+ suggest: [
2305
+ {
2306
+ desc: `Remove "${fieldName}" field`,
2307
+ fix(fixer) {
2308
+ const sourceCode = context.getSourceCode();
2309
+ const tokenBefore = sourceCode.getTokenBefore(node);
2310
+ const tokenAfter = sourceCode.getTokenAfter(node);
2311
+ const isEmptyType = tokenBefore.type === '{' && tokenAfter.type === '}';
2312
+ return isEmptyType ? fixer.remove(node.parent) : fixer.remove(node);
2313
+ },
2314
+ },
2315
+ ],
2277
2316
  });
2278
2317
  },
2279
2318
  };
@@ -2636,14 +2675,14 @@ const rule$h = {
2636
2675
  },
2637
2676
  };
2638
2677
 
2639
- const RULE_NAME$2 = 'require-field-of-type-query-in-mutation-result';
2678
+ const RULE_NAME = 'require-field-of-type-query-in-mutation-result';
2640
2679
  const rule$i = {
2641
2680
  meta: {
2642
2681
  type: 'suggestion',
2643
2682
  docs: {
2644
2683
  category: 'Schema',
2645
2684
  description: 'Allow the client in one round-trip not only to call mutation but also to get a wagon of data to update their application.\n> Currently, no errors are reported for result type `union`, `interface` and `scalar`.',
2646
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$2}.md`,
2685
+ url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME}.md`,
2647
2686
  requiresSchema: true,
2648
2687
  examples: [
2649
2688
  {
@@ -2678,7 +2717,7 @@ const rule$i = {
2678
2717
  schema: [],
2679
2718
  },
2680
2719
  create(context) {
2681
- const schema = requireGraphQLSchemaFromContext(RULE_NAME$2, context);
2720
+ const schema = requireGraphQLSchemaFromContext(RULE_NAME, context);
2682
2721
  const mutationType = schema.getMutationType();
2683
2722
  const queryType = schema.getQueryType();
2684
2723
  if (!mutationType || !queryType) {
@@ -2852,22 +2891,9 @@ const rule$j = {
2852
2891
  recommended: true,
2853
2892
  },
2854
2893
  messages: {
2855
- [REQUIRE_ID_WHEN_AVAILABLE]: [
2856
- `Field {{ fieldName }} must be selected when it's available on a type. Please make sure to include it in your selection set!`,
2857
- `If you are using fragments, make sure that all used fragments {{ checkedFragments }}specifies the field {{ fieldName }}.`,
2858
- ].join('\n'),
2894
+ [REQUIRE_ID_WHEN_AVAILABLE]: `Field "{{ fieldName }}" must be selected when it's available on a type. Please make sure to include it in your selection set!\nIf you are using fragments, make sure that all used fragments {{ checkedFragments }} specifies the field "{{ fieldName }}".`,
2859
2895
  },
2860
2896
  schema: {
2861
- definitions: {
2862
- asString: {
2863
- type: 'string',
2864
- },
2865
- asArray: {
2866
- type: 'array',
2867
- minItems: 1,
2868
- uniqueItems: true,
2869
- },
2870
- },
2871
2897
  type: 'array',
2872
2898
  maxItems: 1,
2873
2899
  items: {
@@ -2875,7 +2901,7 @@ const rule$j = {
2875
2901
  additionalProperties: false,
2876
2902
  properties: {
2877
2903
  fieldName: {
2878
- oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asArray' }],
2904
+ type: 'string',
2879
2905
  default: DEFAULT_ID_FIELD_NAME,
2880
2906
  },
2881
2907
  },
@@ -2883,64 +2909,69 @@ const rule$j = {
2883
2909
  },
2884
2910
  },
2885
2911
  create(context) {
2886
- requireGraphQLSchemaFromContext('require-id-when-available', context);
2887
- const siblings = requireSiblingsOperations('require-id-when-available', context);
2888
- const { fieldName = DEFAULT_ID_FIELD_NAME } = context.options[0] || {};
2889
- const idNames = Array.isArray(fieldName) ? fieldName : [fieldName];
2890
- const isFound = (s) => s.kind === graphql.Kind.FIELD && idNames.includes(s.name.value);
2891
2912
  return {
2892
2913
  SelectionSet(node) {
2893
2914
  var _a, _b;
2894
- const typeInfo = node.typeInfo();
2895
- if (!typeInfo.gqlType) {
2896
- return;
2897
- }
2898
- const rawType = getBaseType(typeInfo.gqlType);
2899
- const isObjectType = rawType instanceof graphql.GraphQLObjectType;
2900
- const isInterfaceType = rawType instanceof graphql.GraphQLInterfaceType;
2901
- if (!isObjectType && !isInterfaceType) {
2915
+ requireGraphQLSchemaFromContext('require-id-when-available', context);
2916
+ const siblings = requireSiblingsOperations('require-id-when-available', context);
2917
+ const fieldName = (context.options[0] || {}).fieldName || DEFAULT_ID_FIELD_NAME;
2918
+ if (!node.selections || node.selections.length === 0) {
2902
2919
  return;
2903
2920
  }
2904
- const fields = rawType.getFields();
2905
- const hasIdFieldInType = idNames.some(name => fields[name]);
2906
- if (!hasIdFieldInType) {
2907
- return;
2908
- }
2909
- const checkedFragmentSpreads = new Set();
2910
- let found = false;
2911
- for (const selection of node.selections) {
2912
- if (isFound(selection)) {
2913
- found = true;
2914
- }
2915
- else if (selection.kind === graphql.Kind.INLINE_FRAGMENT) {
2916
- found = (_a = selection.selectionSet) === null || _a === void 0 ? void 0 : _a.selections.some(s => isFound(s));
2917
- }
2918
- else if (selection.kind === graphql.Kind.FRAGMENT_SPREAD) {
2919
- const [foundSpread] = siblings.getFragment(selection.name.value);
2920
- if (foundSpread) {
2921
- checkedFragmentSpreads.add(foundSpread.document.name.value);
2922
- found = (_b = foundSpread.document.selectionSet) === null || _b === void 0 ? void 0 : _b.selections.some(s => isFound(s));
2921
+ const typeInfo = node.typeInfo();
2922
+ if (typeInfo && typeInfo.gqlType) {
2923
+ const rawType = getBaseType(typeInfo.gqlType);
2924
+ if (rawType instanceof graphql.GraphQLObjectType || rawType instanceof graphql.GraphQLInterfaceType) {
2925
+ const fields = rawType.getFields();
2926
+ const hasIdFieldInType = !!fields[fieldName];
2927
+ const checkedFragmentSpreads = new Set();
2928
+ if (hasIdFieldInType) {
2929
+ let found = false;
2930
+ for (const selection of node.selections) {
2931
+ if (selection.kind === 'Field' && selection.name.value === fieldName) {
2932
+ found = true;
2933
+ }
2934
+ else if (selection.kind === 'InlineFragment') {
2935
+ found = (((_a = selection.selectionSet) === null || _a === void 0 ? void 0 : _a.selections) || []).some(s => s.kind === 'Field' && s.name.value === fieldName);
2936
+ }
2937
+ else if (selection.kind === 'FragmentSpread') {
2938
+ const foundSpread = siblings.getFragment(selection.name.value);
2939
+ if (foundSpread[0]) {
2940
+ checkedFragmentSpreads.add(foundSpread[0].document.name.value);
2941
+ found = (((_b = foundSpread[0].document.selectionSet) === null || _b === void 0 ? void 0 : _b.selections) || []).some(s => s.kind === 'Field' && s.name.value === fieldName);
2942
+ }
2943
+ }
2944
+ if (found) {
2945
+ break;
2946
+ }
2947
+ }
2948
+ const { parent } = node;
2949
+ const hasIdFieldInInterfaceSelectionSet = parent &&
2950
+ parent.kind === 'InlineFragment' &&
2951
+ parent.parent &&
2952
+ parent.parent.kind === 'SelectionSet' &&
2953
+ parent.parent.selections.some(s => s.kind === 'Field' && s.name.value === fieldName);
2954
+ if (!found && !hasIdFieldInInterfaceSelectionSet) {
2955
+ context.report({
2956
+ loc: {
2957
+ start: {
2958
+ line: node.loc.start.line,
2959
+ column: node.loc.start.column - 1,
2960
+ },
2961
+ end: {
2962
+ line: node.loc.end.line,
2963
+ column: node.loc.end.column - 1,
2964
+ },
2965
+ },
2966
+ messageId: REQUIRE_ID_WHEN_AVAILABLE,
2967
+ data: {
2968
+ checkedFragments: checkedFragmentSpreads.size === 0 ? '' : `(${Array.from(checkedFragmentSpreads).join(', ')})`,
2969
+ fieldName,
2970
+ },
2971
+ });
2972
+ }
2923
2973
  }
2924
2974
  }
2925
- if (found) {
2926
- break;
2927
- }
2928
- }
2929
- const { parent } = node;
2930
- const hasIdFieldInInterfaceSelectionSet = parent &&
2931
- parent.kind === graphql.Kind.INLINE_FRAGMENT &&
2932
- parent.parent &&
2933
- parent.parent.kind === graphql.Kind.SELECTION_SET &&
2934
- parent.parent.selections.some(s => isFound(s));
2935
- if (!found && !hasIdFieldInInterfaceSelectionSet) {
2936
- context.report({
2937
- loc: getLocation(node.loc),
2938
- messageId: REQUIRE_ID_WHEN_AVAILABLE,
2939
- data: {
2940
- checkedFragments: checkedFragmentSpreads.size === 0 ? '' : `(${[...checkedFragmentSpreads].join(', ')}) `,
2941
- fieldName: idNames.map(name => `"${name}"`).join(' or '),
2942
- },
2943
- });
2944
2975
  }
2945
2976
  },
2946
2977
  };
@@ -3032,7 +3063,7 @@ const rule$k = {
3032
3063
  // eslint-disable-next-line no-console
3033
3064
  console.warn(`Rule "selection-set-depth" works best with siblings operations loaded. For more info: http://bit.ly/graphql-eslint-operations`);
3034
3065
  }
3035
- const { maxDepth } = context.options[0];
3066
+ const maxDepth = context.options[0].maxDepth;
3036
3067
  const ignore = context.options[0].ignore || [];
3037
3068
  const checkFn = depthLimit(maxDepth, { ignore });
3038
3069
  return {
@@ -3229,7 +3260,7 @@ const rule$l = {
3229
3260
  },
3230
3261
  };
3231
3262
 
3232
- const RULE_NAME$3 = 'unique-fragment-name';
3263
+ const RULE_NAME$1 = 'unique-fragment-name';
3233
3264
  const UNIQUE_FRAGMENT_NAME = 'UNIQUE_FRAGMENT_NAME';
3234
3265
  const checkNode = (context, node, ruleName, messageId) => {
3235
3266
  const documentName = node.name.value;
@@ -3261,7 +3292,7 @@ const rule$m = {
3261
3292
  docs: {
3262
3293
  category: 'Operations',
3263
3294
  description: `Enforce unique fragment names across your project.`,
3264
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$3}.md`,
3295
+ url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$1}.md`,
3265
3296
  requiresSiblings: true,
3266
3297
  examples: [
3267
3298
  {
@@ -3306,13 +3337,13 @@ const rule$m = {
3306
3337
  create(context) {
3307
3338
  return {
3308
3339
  FragmentDefinition(node) {
3309
- checkNode(context, node, RULE_NAME$3, UNIQUE_FRAGMENT_NAME);
3340
+ checkNode(context, node, RULE_NAME$1, UNIQUE_FRAGMENT_NAME);
3310
3341
  },
3311
3342
  };
3312
3343
  },
3313
3344
  };
3314
3345
 
3315
- const RULE_NAME$4 = 'unique-operation-name';
3346
+ const RULE_NAME$2 = 'unique-operation-name';
3316
3347
  const UNIQUE_OPERATION_NAME = 'UNIQUE_OPERATION_NAME';
3317
3348
  const rule$n = {
3318
3349
  meta: {
@@ -3320,7 +3351,7 @@ const rule$n = {
3320
3351
  docs: {
3321
3352
  category: 'Operations',
3322
3353
  description: `Enforce unique operation names across your project.`,
3323
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$4}.md`,
3354
+ url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$2}.md`,
3324
3355
  requiresSiblings: true,
3325
3356
  examples: [
3326
3357
  {
@@ -3369,7 +3400,7 @@ const rule$n = {
3369
3400
  create(context) {
3370
3401
  return {
3371
3402
  'OperationDefinition[name!=undefined]'(node) {
3372
- checkNode(context, node, RULE_NAME$4, UNIQUE_OPERATION_NAME);
3403
+ checkNode(context, node, RULE_NAME$2, UNIQUE_OPERATION_NAME);
3373
3404
  },
3374
3405
  };
3375
3406
  },