UniqueDirectivesPerLocation.js.flow 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. // @flow strict
  2. import { GraphQLError } from '../../error/GraphQLError';
  3. import { Kind } from '../../language/kinds';
  4. import { type DirectiveNode } from '../../language/ast';
  5. import { type ASTVisitor } from '../../language/visitor';
  6. import { specifiedDirectives } from '../../type/directives';
  7. import {
  8. type SDLValidationContext,
  9. type ValidationContext,
  10. } from '../ValidationContext';
  11. export function duplicateDirectiveMessage(directiveName: string): string {
  12. return `The directive "${directiveName}" can only be used once at this location.`;
  13. }
  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 UniqueDirectivesPerLocation(
  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. return {
  38. // Many different AST nodes may contain directives. Rather than listing
  39. // them all, just listen for entering any node, and check to see if it
  40. // defines any directives.
  41. enter(node) {
  42. // Flow can't refine that node.directives will only contain directives,
  43. // so we cast so the rest of the code is well typed.
  44. const directives: ?$ReadOnlyArray<DirectiveNode> = (node: any).directives;
  45. if (directives) {
  46. const knownDirectives = Object.create(null);
  47. for (const directive of directives) {
  48. const directiveName = directive.name.value;
  49. if (uniqueDirectiveMap[directiveName]) {
  50. if (knownDirectives[directiveName]) {
  51. context.reportError(
  52. new GraphQLError(duplicateDirectiveMessage(directiveName), [
  53. knownDirectives[directiveName],
  54. directive,
  55. ]),
  56. );
  57. } else {
  58. knownDirectives[directiveName] = directive;
  59. }
  60. }
  61. }
  62. }
  63. },
  64. };
  65. }