@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.mjs CHANGED
@@ -1,8 +1,7 @@
1
- import { Kind, validate, TokenKind, isScalarType, isNonNullType, isListType, isObjectType as isObjectType$1, visit, visitWithTypeInfo, GraphQLObjectType, GraphQLInterfaceType, TypeInfo, isInterfaceType, Source, GraphQLError } from 'graphql';
1
+ import { Kind, validate, TypeInfo, visitWithTypeInfo, visit, TokenKind, isScalarType, isNonNullType, isListType, isObjectType as isObjectType$1, GraphQLObjectType, GraphQLInterfaceType, isInterfaceType, Source, GraphQLError } from 'graphql';
2
2
  import { validateSDL } from 'graphql/validation/validate';
3
- import { processImport, parseImportLine } from '@graphql-tools/import';
4
3
  import { statSync, existsSync, readFileSync } from 'fs';
5
- import { dirname, join, extname, basename, relative, resolve } from 'path';
4
+ import { dirname, extname, basename, relative, resolve } from 'path';
6
5
  import { asArray, parseGraphQLSDL } from '@graphql-tools/utils';
7
6
  import lowerCase from 'lodash.lowercase';
8
7
  import depthLimit from 'graphql-depth-limit';
@@ -329,59 +328,99 @@ function getLocation(loc, fieldName = '', offset) {
329
328
  };
330
329
  }
331
330
 
332
- function extractRuleName(stack) {
333
- const match = (stack || '').match(/validation[/\\]rules[/\\](.*?)\.js:/) || [];
334
- return match[1] || null;
331
+ function validateDocument(sourceNode, context, schema, documentNode, rule) {
332
+ if (documentNode.definitions.length === 0) {
333
+ return;
334
+ }
335
+ try {
336
+ const validationErrors = schema
337
+ ? validate(schema, documentNode, [rule])
338
+ : validateSDL(documentNode, null, [rule]);
339
+ for (const error of validationErrors) {
340
+ context.report({
341
+ loc: getLocation({ start: error.locations[0] }),
342
+ message: error.message,
343
+ });
344
+ }
345
+ }
346
+ catch (e) {
347
+ context.report({
348
+ node: sourceNode,
349
+ message: e.message,
350
+ });
351
+ }
335
352
  }
336
- function validateDoc(sourceNode, context, schema, documentNode, rules, ruleName = null) {
337
- var _a;
338
- if (((_a = documentNode === null || documentNode === void 0 ? void 0 : documentNode.definitions) === null || _a === void 0 ? void 0 : _a.length) > 0) {
339
- try {
340
- const validationErrors = schema ? validate(schema, documentNode, rules) : validateSDL(documentNode, null, rules);
341
- for (const error of validationErrors) {
342
- const validateRuleName = ruleName || `[${extractRuleName(error.stack)}]`;
343
- context.report({
344
- loc: getLocation({ start: error.locations[0] }),
345
- message: ruleName ? error.message : `${validateRuleName} ${error.message}`,
346
- });
353
+ const getFragmentDefsAndFragmentSpreads = (schema, node) => {
354
+ const typeInfo = new TypeInfo(schema);
355
+ const fragmentDefs = new Set();
356
+ const fragmentSpreads = new Set();
357
+ const visitor = visitWithTypeInfo(typeInfo, {
358
+ FragmentDefinition(node) {
359
+ fragmentDefs.add(`${node.name.value}:${node.typeCondition.name.value}`);
360
+ },
361
+ FragmentSpread(node) {
362
+ const parentType = typeInfo.getParentType();
363
+ if (parentType) {
364
+ fragmentSpreads.add(`${node.name.value}:${parentType.name}`);
347
365
  }
366
+ },
367
+ });
368
+ visit(node, visitor);
369
+ return { fragmentDefs, fragmentSpreads };
370
+ };
371
+ const getMissingFragments = (schema, node) => {
372
+ const { fragmentDefs, fragmentSpreads } = getFragmentDefsAndFragmentSpreads(schema, node);
373
+ return [...fragmentSpreads].filter(name => !fragmentDefs.has(name));
374
+ };
375
+ const handleMissingFragments = ({ ruleId, context, schema, node }) => {
376
+ const missingFragments = getMissingFragments(schema, node);
377
+ if (missingFragments.length > 0) {
378
+ const siblings = requireSiblingsOperations(ruleId, context);
379
+ const fragmentsToAdd = [];
380
+ for (const missingFragment of missingFragments) {
381
+ const [fragmentName, fragmentTypeName] = missingFragment.split(':');
382
+ const fragments = siblings
383
+ .getFragment(fragmentName)
384
+ .map(source => source.document)
385
+ .filter(fragment => fragment.typeCondition.name.value === fragmentTypeName);
386
+ fragmentsToAdd.push(fragments[0]);
348
387
  }
349
- catch (e) {
350
- context.report({
351
- node: sourceNode,
352
- message: e.message,
388
+ if (fragmentsToAdd.length > 0) {
389
+ // recall fn to make sure to add fragments inside fragments
390
+ return handleMissingFragments({
391
+ ruleId,
392
+ context,
393
+ schema,
394
+ node: {
395
+ kind: Kind.DOCUMENT,
396
+ definitions: [...node.definitions, ...fragmentsToAdd],
397
+ },
353
398
  });
354
399
  }
355
400
  }
356
- }
357
- const isGraphQLImportFile = rawSDL => {
358
- const trimmedRawSDL = rawSDL.trimLeft();
359
- return trimmedRawSDL.startsWith('# import') || trimmedRawSDL.startsWith('#import');
401
+ return node;
360
402
  };
361
- const validationToRule = (name, ruleName, docs, getDocumentNode) => {
362
- var _a;
403
+ const validationToRule = (ruleId, ruleName, docs, getDocumentNode) => {
363
404
  let ruleFn = null;
364
405
  try {
365
406
  ruleFn = require(`graphql/validation/rules/${ruleName}Rule`)[`${ruleName}Rule`];
366
407
  }
367
- catch (e) {
408
+ catch (_a) {
368
409
  try {
369
410
  ruleFn = require(`graphql/validation/rules/${ruleName}`)[`${ruleName}Rule`];
370
411
  }
371
- catch (e) {
412
+ catch (_b) {
372
413
  ruleFn = require('graphql/validation')[`${ruleName}Rule`];
373
414
  }
374
415
  }
375
- const requiresSchema = (_a = docs.requiresSchema) !== null && _a !== void 0 ? _a : true;
376
416
  return {
377
- [name]: {
417
+ [ruleId]: {
378
418
  meta: {
379
419
  docs: {
380
420
  recommended: true,
381
421
  ...docs,
382
422
  graphQLJSRuleName: ruleName,
383
- requiresSchema,
384
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${name}.md`,
423
+ url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${ruleId}.md`,
385
424
  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).`,
386
425
  },
387
426
  },
@@ -390,56 +429,53 @@ const validationToRule = (name, ruleName, docs, getDocumentNode) => {
390
429
  Document(node) {
391
430
  if (!ruleFn) {
392
431
  // eslint-disable-next-line no-console
393
- 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...`);
432
+ 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...`);
394
433
  return;
395
434
  }
396
- const schema = requiresSchema ? requireGraphQLSchemaFromContext(name, context) : null;
397
- let documentNode;
398
- const isRealFile = existsSync(context.getFilename());
399
- if (isRealFile && getDocumentNode) {
400
- documentNode = getDocumentNode(context);
401
- }
402
- validateDoc(node, context, schema, documentNode || node.rawNode(), [ruleFn], ruleName);
435
+ const schema = docs.requiresSchema ? requireGraphQLSchemaFromContext(ruleId, context) : null;
436
+ const documentNode = getDocumentNode
437
+ ? getDocumentNode({ ruleId, context, schema, node: node.rawNode() })
438
+ : node.rawNode();
439
+ validateDocument(node, context, schema, documentNode, ruleFn);
403
440
  },
404
441
  };
405
442
  },
406
443
  },
407
444
  };
408
445
  };
409
- const importFiles = (context) => {
410
- const code = context.getSourceCode().text;
411
- if (!isGraphQLImportFile(code)) {
412
- return null;
413
- }
414
- // Import documents because file contains '#import' comments
415
- return processImport(context.getFilename());
416
- };
417
446
  const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-definitions', 'ExecutableDefinitions', {
418
447
  category: 'Operations',
419
448
  description: `A GraphQL document is only valid for execution if all definitions are either operation or fragment definitions.`,
449
+ requiresSchema: true,
420
450
  }), validationToRule('fields-on-correct-type', 'FieldsOnCorrectType', {
421
451
  category: 'Operations',
422
452
  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`.',
453
+ requiresSchema: true,
423
454
  }), validationToRule('fragments-on-composite-type', 'FragmentsOnCompositeTypes', {
424
455
  category: 'Operations',
425
456
  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.`,
457
+ requiresSchema: true,
426
458
  }), validationToRule('known-argument-names', 'KnownArgumentNames', {
427
459
  category: ['Schema', 'Operations'],
428
460
  description: `A GraphQL field is only valid if all supplied arguments are defined by that field.`,
461
+ requiresSchema: true,
429
462
  }), validationToRule('known-directives', 'KnownDirectives', {
430
463
  category: ['Schema', 'Operations'],
431
464
  description: `A GraphQL document is only valid if all \`@directives\` are known by the schema and legally positioned.`,
465
+ requiresSchema: true,
432
466
  }), validationToRule('known-fragment-names', 'KnownFragmentNames', {
433
467
  category: 'Operations',
434
468
  description: `A GraphQL document is only valid if all \`...Fragment\` fragment spreads refer to fragments defined in the same document.`,
469
+ requiresSchema: true,
470
+ requiresSiblings: true,
435
471
  examples: [
436
472
  {
437
- title: 'Incorrect (fragment not defined in the document)',
473
+ title: 'Incorrect',
438
474
  code: /* GraphQL */ `
439
475
  query {
440
476
  user {
441
477
  id
442
- ...UserFields
478
+ ...UserFields # fragment not defined in the document
443
479
  }
444
480
  }
445
481
  `,
@@ -461,153 +497,151 @@ const GRAPHQL_JS_VALIDATIONS = Object.assign({}, validationToRule('executable-de
461
497
  `,
462
498
  },
463
499
  {
464
- title: 'Correct (existing import to UserFields fragment)',
500
+ title: 'Correct (`UserFields` fragment located in a separate file)',
465
501
  code: /* GraphQL */ `
466
- #import '../UserFields.gql'
467
-
502
+ # user.gql
468
503
  query {
469
504
  user {
470
505
  id
471
506
  ...UserFields
472
507
  }
473
508
  }
474
- `,
475
- },
476
- {
477
- 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.",
478
- code: `
479
- const USER_FIELDS = gql\`
480
- fragment UserFields on User {
481
- id
482
- }
483
- \`
484
-
485
- const GET_USER = /* GraphQL */ \`
486
- # eslint @graphql-eslint/known-fragment-names: 'error'
487
-
488
- query User {
489
- user {
490
- ...UserFields
491
- }
492
- }
493
509
 
494
- # Will give false positive error 'Unknown fragment "UserFields"'
495
- \${USER_FIELDS}
496
- \``,
510
+ # user-fields.gql
511
+ fragment UserFields on User {
512
+ id
513
+ }
514
+ `,
497
515
  },
498
516
  ],
499
- }, importFiles), validationToRule('known-type-names', 'KnownTypeNames', {
517
+ }, handleMissingFragments), validationToRule('known-type-names', 'KnownTypeNames', {
500
518
  category: ['Schema', 'Operations'],
501
519
  description: `A GraphQL document is only valid if referenced types (specifically variable definitions and fragment conditions) are defined by the type schema.`,
520
+ requiresSchema: true,
502
521
  }), validationToRule('lone-anonymous-operation', 'LoneAnonymousOperation', {
503
522
  category: 'Operations',
504
523
  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.`,
524
+ requiresSchema: true,
505
525
  }), validationToRule('lone-schema-definition', 'LoneSchemaDefinition', {
506
526
  category: 'Schema',
507
527
  description: `A GraphQL document is only valid if it contains only one schema definition.`,
508
- requiresSchema: false,
509
528
  }), validationToRule('no-fragment-cycles', 'NoFragmentCycles', {
510
529
  category: 'Operations',
511
530
  description: `A GraphQL fragment is only valid when it does not have cycles in fragments usage.`,
531
+ requiresSchema: true,
512
532
  }), validationToRule('no-undefined-variables', 'NoUndefinedVariables', {
513
533
  category: 'Operations',
514
534
  description: `A GraphQL operation is only valid if all variables encountered, both directly and via fragment spreads, are defined by that operation.`,
515
- }, importFiles), validationToRule('no-unused-fragments', 'NoUnusedFragments', {
535
+ requiresSchema: true,
536
+ requiresSiblings: true,
537
+ }, handleMissingFragments), validationToRule('no-unused-fragments', 'NoUnusedFragments', {
516
538
  category: 'Operations',
517
539
  description: `A GraphQL document is only valid if all fragment definitions are spread within operations, or spread within other fragments spread within operations.`,
540
+ requiresSchema: true,
518
541
  requiresSiblings: true,
519
- }, context => {
520
- const siblings = requireSiblingsOperations('no-unused-fragments', context);
521
- const documents = [...siblings.getOperations(), ...siblings.getFragments()]
522
- .filter(({ document }) => isGraphQLImportFile(document.loc.source.body))
523
- .map(({ filePath, document }) => ({
524
- filePath,
525
- code: document.loc.source.body,
526
- }));
527
- const getParentNode = (filePath) => {
528
- for (const { filePath: docFilePath, code } of documents) {
529
- const isFileImported = code
530
- .split('\n')
531
- .filter(isGraphQLImportFile)
532
- .map(line => parseImportLine(line.replace('#', '')))
533
- .some(o => filePath === join(dirname(docFilePath), o.from));
534
- if (!isFileImported) {
535
- continue;
536
- }
537
- // Import first file that import this file
538
- const document = processImport(docFilePath);
539
- // Import most top file that import this file
540
- return getParentNode(docFilePath) || document;
542
+ }, ({ ruleId, context, schema, node }) => {
543
+ const siblings = requireSiblingsOperations(ruleId, context);
544
+ const FilePathToDocumentsMap = [...siblings.getOperations(), ...siblings.getFragments()].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(schema, node);
552
+ if (fragmentDefs.size === 0) {
553
+ return node;
541
554
  }
542
- return null;
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(schema, {
559
+ kind: Kind.DOCUMENT,
560
+ definitions: documents,
561
+ });
562
+ const isCurrentFileImportFragment = missingFragments.some(fragment => fragmentDefs.has(fragment));
563
+ if (isCurrentFileImportFragment) {
564
+ return getParentNode(filePath, {
565
+ kind: Kind.DOCUMENT,
566
+ definitions: [...node.definitions, ...documents],
567
+ });
568
+ }
569
+ }
570
+ return node;
543
571
  };
544
- return getParentNode(context.getFilename());
572
+ return getParentNode(context.getFilename(), node);
545
573
  }), validationToRule('no-unused-variables', 'NoUnusedVariables', {
546
574
  category: 'Operations',
547
575
  description: `A GraphQL operation is only valid if all variables defined by an operation are used, either directly or within a spread fragment.`,
548
- }, importFiles), validationToRule('overlapping-fields-can-be-merged', 'OverlappingFieldsCanBeMerged', {
576
+ requiresSchema: true,
577
+ requiresSiblings: true,
578
+ }, handleMissingFragments), validationToRule('overlapping-fields-can-be-merged', 'OverlappingFieldsCanBeMerged', {
549
579
  category: 'Operations',
550
580
  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.`,
581
+ requiresSchema: true,
551
582
  }), validationToRule('possible-fragment-spread', 'PossibleFragmentSpreads', {
552
583
  category: 'Operations',
553
584
  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.`,
585
+ requiresSchema: true,
554
586
  }), validationToRule('possible-type-extension', 'PossibleTypeExtensions', {
555
587
  category: 'Schema',
556
588
  description: `A type extension is only valid if the type is defined and has the same kind.`,
557
- requiresSchema: false,
558
589
  recommended: false, // TODO: enable after https://github.com/dotansimha/graphql-eslint/issues/787 will be fixed
559
590
  }), validationToRule('provided-required-arguments', 'ProvidedRequiredArguments', {
560
591
  category: ['Schema', 'Operations'],
561
592
  description: `A field or directive is only valid if all required (non-null without a default value) field arguments have been provided.`,
593
+ requiresSchema: true,
562
594
  }), validationToRule('scalar-leafs', 'ScalarLeafs', {
563
595
  category: 'Operations',
564
596
  description: `A GraphQL document is valid only if all leaf fields (fields without sub selections) are of scalar or enum types.`,
597
+ requiresSchema: true,
565
598
  }), validationToRule('one-field-subscriptions', 'SingleFieldSubscriptions', {
566
599
  category: 'Operations',
567
600
  description: `A GraphQL subscription is valid only if it contains a single root field.`,
601
+ requiresSchema: true,
568
602
  }), validationToRule('unique-argument-names', 'UniqueArgumentNames', {
569
603
  category: 'Operations',
570
604
  description: `A GraphQL field or directive is only valid if all supplied arguments are uniquely named.`,
605
+ requiresSchema: true,
571
606
  }), validationToRule('unique-directive-names', 'UniqueDirectiveNames', {
572
607
  category: 'Schema',
573
608
  description: `A GraphQL document is only valid if all defined directives have unique names.`,
574
- requiresSchema: false,
575
609
  }), validationToRule('unique-directive-names-per-location', 'UniqueDirectivesPerLocation', {
576
610
  category: ['Schema', 'Operations'],
577
611
  description: `A GraphQL document is only valid if all non-repeatable directives at a given location are uniquely named.`,
612
+ requiresSchema: true,
578
613
  }), validationToRule('unique-enum-value-names', 'UniqueEnumValueNames', {
579
614
  category: 'Schema',
580
615
  description: `A GraphQL enum type is only valid if all its values are uniquely named.`,
581
- requiresSchema: false,
582
616
  recommended: false,
583
617
  }), validationToRule('unique-field-definition-names', 'UniqueFieldDefinitionNames', {
584
618
  category: 'Schema',
585
619
  description: `A GraphQL complex type is only valid if all its fields are uniquely named.`,
586
- requiresSchema: false,
587
620
  }), validationToRule('unique-input-field-names', 'UniqueInputFieldNames', {
588
621
  category: 'Operations',
589
622
  description: `A GraphQL input object value is only valid if all supplied fields are uniquely named.`,
590
- requiresSchema: false,
591
623
  }), validationToRule('unique-operation-types', 'UniqueOperationTypes', {
592
624
  category: 'Schema',
593
625
  description: `A GraphQL document is only valid if it has only one type per operation.`,
594
- requiresSchema: false,
595
626
  }), validationToRule('unique-type-names', 'UniqueTypeNames', {
596
627
  category: 'Schema',
597
628
  description: `A GraphQL document is only valid if all defined types have unique names.`,
598
- requiresSchema: false,
599
629
  }), validationToRule('unique-variable-names', 'UniqueVariableNames', {
600
630
  category: 'Operations',
601
631
  description: `A GraphQL operation is only valid if all its variables are uniquely named.`,
632
+ requiresSchema: true,
602
633
  }), validationToRule('value-literals-of-correct-type', 'ValuesOfCorrectType', {
603
634
  category: 'Operations',
604
635
  description: `A GraphQL document is only valid if all value literals are of the type expected at their position.`,
636
+ requiresSchema: true,
605
637
  }), validationToRule('variables-are-input-types', 'VariablesAreInputTypes', {
606
638
  category: 'Operations',
607
639
  description: `A GraphQL operation is only valid if all the variables it defines are of input types (scalar, enum, or input object).`,
640
+ requiresSchema: true,
608
641
  }), validationToRule('variables-in-allowed-position', 'VariablesInAllowedPosition', {
609
642
  category: 'Operations',
610
643
  description: `Variables passed to field arguments conform to type.`,
644
+ requiresSchema: true,
611
645
  }));
612
646
 
613
647
  const ALPHABETIZE = 'ALPHABETIZE';
@@ -1839,7 +1873,7 @@ const HASHTAG_COMMENT = 'HASHTAG_COMMENT';
1839
1873
  const rule$9 = {
1840
1874
  meta: {
1841
1875
  messages: {
1842
- [HASHTAG_COMMENT]: 'Using hashtag (#) for adding GraphQL descriptions is not allowed. Prefer using """ for multiline, or " for a single line description.',
1876
+ [HASHTAG_COMMENT]: `Using hashtag (#) for adding GraphQL descriptions is not allowed. Prefer using """ for multiline, or " for a single line description.`,
1843
1877
  },
1844
1878
  docs: {
1845
1879
  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.',
@@ -1886,14 +1920,15 @@ const rule$9 = {
1886
1920
  schema: [],
1887
1921
  },
1888
1922
  create(context) {
1923
+ const selector = `${Kind.DOCUMENT}[definitions.0.kind!=/^(${Kind.OPERATION_DEFINITION}|${Kind.FRAGMENT_DEFINITION})$/]`;
1889
1924
  return {
1890
- Document(node) {
1925
+ [selector](node) {
1891
1926
  const rawNode = node.rawNode();
1892
1927
  let token = rawNode.loc.startToken;
1893
1928
  while (token !== null) {
1894
1929
  const { kind, prev, next, value, line, column } = token;
1895
1930
  if (kind === TokenKind.COMMENT && prev && next) {
1896
- const isEslintComment = value.trimLeft().startsWith('eslint');
1931
+ const isEslintComment = value.trimStart().startsWith('eslint');
1897
1932
  const linesAfter = next.line - line;
1898
1933
  if (!isEslintComment && line !== prev.line && next.kind === TokenKind.NAME && linesAfter < 2) {
1899
1934
  context.report({
@@ -2100,7 +2135,22 @@ const rule$c = {
2100
2135
  };
2101
2136
 
2102
2137
  const UNREACHABLE_TYPE = 'UNREACHABLE_TYPE';
2103
- const RULE_NAME = 'no-unreachable-types';
2138
+ const RULE_ID = 'no-unreachable-types';
2139
+ const KINDS = [
2140
+ Kind.DIRECTIVE_DEFINITION,
2141
+ Kind.OBJECT_TYPE_DEFINITION,
2142
+ Kind.OBJECT_TYPE_EXTENSION,
2143
+ Kind.INTERFACE_TYPE_DEFINITION,
2144
+ Kind.INTERFACE_TYPE_EXTENSION,
2145
+ Kind.SCALAR_TYPE_DEFINITION,
2146
+ Kind.SCALAR_TYPE_EXTENSION,
2147
+ Kind.INPUT_OBJECT_TYPE_DEFINITION,
2148
+ Kind.INPUT_OBJECT_TYPE_EXTENSION,
2149
+ Kind.UNION_TYPE_DEFINITION,
2150
+ Kind.UNION_TYPE_EXTENSION,
2151
+ Kind.ENUM_TYPE_DEFINITION,
2152
+ Kind.ENUM_TYPE_EXTENSION,
2153
+ ];
2104
2154
  const rule$d = {
2105
2155
  meta: {
2106
2156
  messages: {
@@ -2109,7 +2159,7 @@ const rule$d = {
2109
2159
  docs: {
2110
2160
  description: `Requires all types to be reachable at some level by root level fields.`,
2111
2161
  category: 'Schema',
2112
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME}.md`,
2162
+ url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
2113
2163
  requiresSchema: true,
2114
2164
  examples: [
2115
2165
  {
@@ -2141,43 +2191,36 @@ const rule$d = {
2141
2191
  ],
2142
2192
  recommended: true,
2143
2193
  },
2144
- fixable: 'code',
2145
2194
  type: 'suggestion',
2146
2195
  schema: [],
2196
+ hasSuggestions: true,
2147
2197
  },
2148
2198
  create(context) {
2149
- const reachableTypes = requireReachableTypesFromContext(RULE_NAME, context);
2150
- function ensureReachability(node) {
2151
- const typeName = node.name.value;
2152
- if (!reachableTypes.has(typeName)) {
2153
- context.report({
2154
- loc: getLocation(node.name.loc, typeName, { offsetStart: node.kind === Kind.DIRECTIVE_DEFINITION ? 2 : 1 }),
2155
- messageId: UNREACHABLE_TYPE,
2156
- data: { typeName },
2157
- fix: fixer => fixer.remove(node),
2158
- });
2159
- }
2160
- }
2199
+ const reachableTypes = requireReachableTypesFromContext(RULE_ID, context);
2200
+ const selector = KINDS.join(',');
2161
2201
  return {
2162
- DirectiveDefinition: ensureReachability,
2163
- ObjectTypeDefinition: ensureReachability,
2164
- ObjectTypeExtension: ensureReachability,
2165
- InterfaceTypeDefinition: ensureReachability,
2166
- InterfaceTypeExtension: ensureReachability,
2167
- ScalarTypeDefinition: ensureReachability,
2168
- ScalarTypeExtension: ensureReachability,
2169
- InputObjectTypeDefinition: ensureReachability,
2170
- InputObjectTypeExtension: ensureReachability,
2171
- UnionTypeDefinition: ensureReachability,
2172
- UnionTypeExtension: ensureReachability,
2173
- EnumTypeDefinition: ensureReachability,
2174
- EnumTypeExtension: ensureReachability,
2202
+ [selector](node) {
2203
+ const typeName = node.name.value;
2204
+ if (!reachableTypes.has(typeName)) {
2205
+ context.report({
2206
+ loc: getLocation(node.name.loc, typeName, { offsetStart: node.kind === Kind.DIRECTIVE_DEFINITION ? 2 : 1 }),
2207
+ messageId: UNREACHABLE_TYPE,
2208
+ data: { typeName },
2209
+ suggest: [
2210
+ {
2211
+ desc: `Remove ${typeName}`,
2212
+ fix: fixer => fixer.remove(node),
2213
+ },
2214
+ ],
2215
+ });
2216
+ }
2217
+ },
2175
2218
  };
2176
2219
  },
2177
2220
  };
2178
2221
 
2179
2222
  const UNUSED_FIELD = 'UNUSED_FIELD';
2180
- const RULE_NAME$1 = 'no-unused-fields';
2223
+ const RULE_ID$1 = 'no-unused-fields';
2181
2224
  const rule$e = {
2182
2225
  meta: {
2183
2226
  messages: {
@@ -2186,7 +2229,7 @@ const rule$e = {
2186
2229
  docs: {
2187
2230
  description: `Requires all fields to be used at some level by siblings operations.`,
2188
2231
  category: 'Schema',
2189
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$1}.md`,
2232
+ url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_ID$1}.md`,
2190
2233
  requiresSiblings: true,
2191
2234
  requiresSchema: true,
2192
2235
  examples: [
@@ -2233,12 +2276,12 @@ const rule$e = {
2233
2276
  },
2234
2277
  ],
2235
2278
  },
2236
- fixable: 'code',
2237
2279
  type: 'suggestion',
2238
2280
  schema: [],
2281
+ hasSuggestions: true,
2239
2282
  },
2240
2283
  create(context) {
2241
- const usedFields = requireUsedFieldsFromContext(RULE_NAME$1, context);
2284
+ const usedFields = requireUsedFieldsFromContext(RULE_ID$1, context);
2242
2285
  return {
2243
2286
  FieldDefinition(node) {
2244
2287
  var _a;
@@ -2252,22 +2295,18 @@ const rule$e = {
2252
2295
  loc: getLocation(node.loc, fieldName),
2253
2296
  messageId: UNUSED_FIELD,
2254
2297
  data: { fieldName },
2255
- fix(fixer) {
2256
- const sourceCode = context.getSourceCode();
2257
- const tokenBefore = sourceCode.getTokenBefore(node);
2258
- const tokenAfter = sourceCode.getTokenAfter(node);
2259
- const isEmptyType = tokenBefore.type === '{' && tokenAfter.type === '}';
2260
- if (isEmptyType) {
2261
- // Remove type
2262
- const { parent } = node;
2263
- const parentBeforeToken = sourceCode.getTokenBefore(parent);
2264
- return parentBeforeToken
2265
- ? fixer.removeRange([parentBeforeToken.range[1], parent.range[1]])
2266
- : fixer.remove(parent);
2267
- }
2268
- // Remove whitespace before token
2269
- return fixer.removeRange([tokenBefore.range[1], node.range[1]]);
2270
- },
2298
+ suggest: [
2299
+ {
2300
+ desc: `Remove "${fieldName}" field`,
2301
+ fix(fixer) {
2302
+ const sourceCode = context.getSourceCode();
2303
+ const tokenBefore = sourceCode.getTokenBefore(node);
2304
+ const tokenAfter = sourceCode.getTokenAfter(node);
2305
+ const isEmptyType = tokenBefore.type === '{' && tokenAfter.type === '}';
2306
+ return isEmptyType ? fixer.remove(node.parent) : fixer.remove(node);
2307
+ },
2308
+ },
2309
+ ],
2271
2310
  });
2272
2311
  },
2273
2312
  };
@@ -2630,14 +2669,14 @@ const rule$h = {
2630
2669
  },
2631
2670
  };
2632
2671
 
2633
- const RULE_NAME$2 = 'require-field-of-type-query-in-mutation-result';
2672
+ const RULE_NAME = 'require-field-of-type-query-in-mutation-result';
2634
2673
  const rule$i = {
2635
2674
  meta: {
2636
2675
  type: 'suggestion',
2637
2676
  docs: {
2638
2677
  category: 'Schema',
2639
2678
  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`.',
2640
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$2}.md`,
2679
+ url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME}.md`,
2641
2680
  requiresSchema: true,
2642
2681
  examples: [
2643
2682
  {
@@ -2672,7 +2711,7 @@ const rule$i = {
2672
2711
  schema: [],
2673
2712
  },
2674
2713
  create(context) {
2675
- const schema = requireGraphQLSchemaFromContext(RULE_NAME$2, context);
2714
+ const schema = requireGraphQLSchemaFromContext(RULE_NAME, context);
2676
2715
  const mutationType = schema.getMutationType();
2677
2716
  const queryType = schema.getQueryType();
2678
2717
  if (!mutationType || !queryType) {
@@ -2846,22 +2885,9 @@ const rule$j = {
2846
2885
  recommended: true,
2847
2886
  },
2848
2887
  messages: {
2849
- [REQUIRE_ID_WHEN_AVAILABLE]: [
2850
- `Field {{ fieldName }} must be selected when it's available on a type. Please make sure to include it in your selection set!`,
2851
- `If you are using fragments, make sure that all used fragments {{ checkedFragments }}specifies the field {{ fieldName }}.`,
2852
- ].join('\n'),
2888
+ [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 }}".`,
2853
2889
  },
2854
2890
  schema: {
2855
- definitions: {
2856
- asString: {
2857
- type: 'string',
2858
- },
2859
- asArray: {
2860
- type: 'array',
2861
- minItems: 1,
2862
- uniqueItems: true,
2863
- },
2864
- },
2865
2891
  type: 'array',
2866
2892
  maxItems: 1,
2867
2893
  items: {
@@ -2869,7 +2895,7 @@ const rule$j = {
2869
2895
  additionalProperties: false,
2870
2896
  properties: {
2871
2897
  fieldName: {
2872
- oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asArray' }],
2898
+ type: 'string',
2873
2899
  default: DEFAULT_ID_FIELD_NAME,
2874
2900
  },
2875
2901
  },
@@ -2877,64 +2903,69 @@ const rule$j = {
2877
2903
  },
2878
2904
  },
2879
2905
  create(context) {
2880
- requireGraphQLSchemaFromContext('require-id-when-available', context);
2881
- const siblings = requireSiblingsOperations('require-id-when-available', context);
2882
- const { fieldName = DEFAULT_ID_FIELD_NAME } = context.options[0] || {};
2883
- const idNames = Array.isArray(fieldName) ? fieldName : [fieldName];
2884
- const isFound = (s) => s.kind === Kind.FIELD && idNames.includes(s.name.value);
2885
2906
  return {
2886
2907
  SelectionSet(node) {
2887
2908
  var _a, _b;
2888
- const typeInfo = node.typeInfo();
2889
- if (!typeInfo.gqlType) {
2890
- return;
2891
- }
2892
- const rawType = getBaseType(typeInfo.gqlType);
2893
- const isObjectType = rawType instanceof GraphQLObjectType;
2894
- const isInterfaceType = rawType instanceof GraphQLInterfaceType;
2895
- if (!isObjectType && !isInterfaceType) {
2909
+ requireGraphQLSchemaFromContext('require-id-when-available', context);
2910
+ const siblings = requireSiblingsOperations('require-id-when-available', context);
2911
+ const fieldName = (context.options[0] || {}).fieldName || DEFAULT_ID_FIELD_NAME;
2912
+ if (!node.selections || node.selections.length === 0) {
2896
2913
  return;
2897
2914
  }
2898
- const fields = rawType.getFields();
2899
- const hasIdFieldInType = idNames.some(name => fields[name]);
2900
- if (!hasIdFieldInType) {
2901
- return;
2902
- }
2903
- const checkedFragmentSpreads = new Set();
2904
- let found = false;
2905
- for (const selection of node.selections) {
2906
- if (isFound(selection)) {
2907
- found = true;
2908
- }
2909
- else if (selection.kind === Kind.INLINE_FRAGMENT) {
2910
- found = (_a = selection.selectionSet) === null || _a === void 0 ? void 0 : _a.selections.some(s => isFound(s));
2911
- }
2912
- else if (selection.kind === Kind.FRAGMENT_SPREAD) {
2913
- const [foundSpread] = siblings.getFragment(selection.name.value);
2914
- if (foundSpread) {
2915
- checkedFragmentSpreads.add(foundSpread.document.name.value);
2916
- found = (_b = foundSpread.document.selectionSet) === null || _b === void 0 ? void 0 : _b.selections.some(s => isFound(s));
2915
+ const typeInfo = node.typeInfo();
2916
+ if (typeInfo && typeInfo.gqlType) {
2917
+ const rawType = getBaseType(typeInfo.gqlType);
2918
+ if (rawType instanceof GraphQLObjectType || rawType instanceof GraphQLInterfaceType) {
2919
+ const fields = rawType.getFields();
2920
+ const hasIdFieldInType = !!fields[fieldName];
2921
+ const checkedFragmentSpreads = new Set();
2922
+ if (hasIdFieldInType) {
2923
+ let found = false;
2924
+ for (const selection of node.selections) {
2925
+ if (selection.kind === 'Field' && selection.name.value === fieldName) {
2926
+ found = true;
2927
+ }
2928
+ else if (selection.kind === 'InlineFragment') {
2929
+ found = (((_a = selection.selectionSet) === null || _a === void 0 ? void 0 : _a.selections) || []).some(s => s.kind === 'Field' && s.name.value === fieldName);
2930
+ }
2931
+ else if (selection.kind === 'FragmentSpread') {
2932
+ const foundSpread = siblings.getFragment(selection.name.value);
2933
+ if (foundSpread[0]) {
2934
+ checkedFragmentSpreads.add(foundSpread[0].document.name.value);
2935
+ found = (((_b = foundSpread[0].document.selectionSet) === null || _b === void 0 ? void 0 : _b.selections) || []).some(s => s.kind === 'Field' && s.name.value === fieldName);
2936
+ }
2937
+ }
2938
+ if (found) {
2939
+ break;
2940
+ }
2941
+ }
2942
+ const { parent } = node;
2943
+ const hasIdFieldInInterfaceSelectionSet = parent &&
2944
+ parent.kind === 'InlineFragment' &&
2945
+ parent.parent &&
2946
+ parent.parent.kind === 'SelectionSet' &&
2947
+ parent.parent.selections.some(s => s.kind === 'Field' && s.name.value === fieldName);
2948
+ if (!found && !hasIdFieldInInterfaceSelectionSet) {
2949
+ context.report({
2950
+ loc: {
2951
+ start: {
2952
+ line: node.loc.start.line,
2953
+ column: node.loc.start.column - 1,
2954
+ },
2955
+ end: {
2956
+ line: node.loc.end.line,
2957
+ column: node.loc.end.column - 1,
2958
+ },
2959
+ },
2960
+ messageId: REQUIRE_ID_WHEN_AVAILABLE,
2961
+ data: {
2962
+ checkedFragments: checkedFragmentSpreads.size === 0 ? '' : `(${Array.from(checkedFragmentSpreads).join(', ')})`,
2963
+ fieldName,
2964
+ },
2965
+ });
2966
+ }
2917
2967
  }
2918
2968
  }
2919
- if (found) {
2920
- break;
2921
- }
2922
- }
2923
- const { parent } = node;
2924
- const hasIdFieldInInterfaceSelectionSet = parent &&
2925
- parent.kind === Kind.INLINE_FRAGMENT &&
2926
- parent.parent &&
2927
- parent.parent.kind === Kind.SELECTION_SET &&
2928
- parent.parent.selections.some(s => isFound(s));
2929
- if (!found && !hasIdFieldInInterfaceSelectionSet) {
2930
- context.report({
2931
- loc: getLocation(node.loc),
2932
- messageId: REQUIRE_ID_WHEN_AVAILABLE,
2933
- data: {
2934
- checkedFragments: checkedFragmentSpreads.size === 0 ? '' : `(${[...checkedFragmentSpreads].join(', ')}) `,
2935
- fieldName: idNames.map(name => `"${name}"`).join(' or '),
2936
- },
2937
- });
2938
2969
  }
2939
2970
  },
2940
2971
  };
@@ -3026,7 +3057,7 @@ const rule$k = {
3026
3057
  // eslint-disable-next-line no-console
3027
3058
  console.warn(`Rule "selection-set-depth" works best with siblings operations loaded. For more info: http://bit.ly/graphql-eslint-operations`);
3028
3059
  }
3029
- const { maxDepth } = context.options[0];
3060
+ const maxDepth = context.options[0].maxDepth;
3030
3061
  const ignore = context.options[0].ignore || [];
3031
3062
  const checkFn = depthLimit(maxDepth, { ignore });
3032
3063
  return {
@@ -3223,7 +3254,7 @@ const rule$l = {
3223
3254
  },
3224
3255
  };
3225
3256
 
3226
- const RULE_NAME$3 = 'unique-fragment-name';
3257
+ const RULE_NAME$1 = 'unique-fragment-name';
3227
3258
  const UNIQUE_FRAGMENT_NAME = 'UNIQUE_FRAGMENT_NAME';
3228
3259
  const checkNode = (context, node, ruleName, messageId) => {
3229
3260
  const documentName = node.name.value;
@@ -3255,7 +3286,7 @@ const rule$m = {
3255
3286
  docs: {
3256
3287
  category: 'Operations',
3257
3288
  description: `Enforce unique fragment names across your project.`,
3258
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$3}.md`,
3289
+ url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$1}.md`,
3259
3290
  requiresSiblings: true,
3260
3291
  examples: [
3261
3292
  {
@@ -3300,13 +3331,13 @@ const rule$m = {
3300
3331
  create(context) {
3301
3332
  return {
3302
3333
  FragmentDefinition(node) {
3303
- checkNode(context, node, RULE_NAME$3, UNIQUE_FRAGMENT_NAME);
3334
+ checkNode(context, node, RULE_NAME$1, UNIQUE_FRAGMENT_NAME);
3304
3335
  },
3305
3336
  };
3306
3337
  },
3307
3338
  };
3308
3339
 
3309
- const RULE_NAME$4 = 'unique-operation-name';
3340
+ const RULE_NAME$2 = 'unique-operation-name';
3310
3341
  const UNIQUE_OPERATION_NAME = 'UNIQUE_OPERATION_NAME';
3311
3342
  const rule$n = {
3312
3343
  meta: {
@@ -3314,7 +3345,7 @@ const rule$n = {
3314
3345
  docs: {
3315
3346
  category: 'Operations',
3316
3347
  description: `Enforce unique operation names across your project.`,
3317
- url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$4}.md`,
3348
+ url: `https://github.com/dotansimha/graphql-eslint/blob/master/docs/rules/${RULE_NAME$2}.md`,
3318
3349
  requiresSiblings: true,
3319
3350
  examples: [
3320
3351
  {
@@ -3363,7 +3394,7 @@ const rule$n = {
3363
3394
  create(context) {
3364
3395
  return {
3365
3396
  'OperationDefinition[name!=undefined]'(node) {
3366
- checkNode(context, node, RULE_NAME$4, UNIQUE_OPERATION_NAME);
3397
+ checkNode(context, node, RULE_NAME$2, UNIQUE_OPERATION_NAME);
3367
3398
  },
3368
3399
  };
3369
3400
  },