UniqueDirectivesPerLocationRule.js.flow 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. // @flow strict
  2. import { GraphQLError } from '../../error/GraphQLError';
  3. import { Kind } from '../../language/kinds';
  4. import type { ASTVisitor } from '../../language/visitor';
  5. import {
  6. isTypeDefinitionNode,
  7. isTypeExtensionNode,
  8. } from '../../language/predicates';
  9. import { specifiedDirectives } from '../../type/directives';
  10. import type {
  11. SDLValidationContext,
  12. ValidationContext,
  13. } from '../ValidationContext';
  14. /**
  15. * Unique directive names per location
  16. *
  17. * A GraphQL document is only valid if all non-repeatable directives at
  18. * a given location are uniquely named.
  19. */
  20. export function UniqueDirectivesPerLocationRule(
  21. context: ValidationContext | SDLValidationContext,
  22. ): ASTVisitor {
  23. const uniqueDirectiveMap = Object.create(null);
  24. const schema = context.getSchema();
  25. const definedDirectives = schema
  26. ? schema.getDirectives()
  27. : specifiedDirectives;
  28. for (const directive of definedDirectives) {
  29. uniqueDirectiveMap[directive.name] = !directive.isRepeatable;
  30. }
  31. const astDefinitions = context.getDocument().definitions;
  32. for (const def of astDefinitions) {
  33. if (def.kind === Kind.DIRECTIVE_DEFINITION) {
  34. uniqueDirectiveMap[def.name.value] = !def.repeatable;
  35. }
  36. }
  37. const schemaDirectives = Object.create(null);
  38. const typeDirectivesMap = Object.create(null);
  39. return {
  40. // Many different AST nodes may contain directives. Rather than listing
  41. // them all, just listen for entering any node, and check to see if it
  42. // defines any directives.
  43. enter(node) {
  44. if (node.directives == null) {
  45. return;
  46. }
  47. let seenDirectives;
  48. if (
  49. node.kind === Kind.SCHEMA_DEFINITION ||
  50. node.kind === Kind.SCHEMA_EXTENSION
  51. ) {
  52. seenDirectives = schemaDirectives;
  53. } else if (isTypeDefinitionNode(node) || isTypeExtensionNode(node)) {
  54. const typeName = node.name.value;
  55. seenDirectives = typeDirectivesMap[typeName];
  56. if (seenDirectives === undefined) {
  57. typeDirectivesMap[typeName] = seenDirectives = Object.create(null);
  58. }
  59. } else {
  60. seenDirectives = Object.create(null);
  61. }
  62. for (const directive of node.directives) {
  63. const directiveName = directive.name.value;
  64. if (uniqueDirectiveMap[directiveName]) {
  65. if (seenDirectives[directiveName]) {
  66. context.reportError(
  67. new GraphQLError(
  68. `The directive "@${directiveName}" can only be used once at this location.`,
  69. [seenDirectives[directiveName], directive],
  70. ),
  71. );
  72. } else {
  73. seenDirectives[directiveName] = directive;
  74. }
  75. }
  76. }
  77. },
  78. };
  79. }