12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485 |
- import { GraphQLError } from '../../error/GraphQLError.mjs';
- import { Kind } from '../../language/kinds.mjs';
- import {
- isTypeDefinitionNode,
- isTypeExtensionNode,
- } from '../../language/predicates.mjs';
- import { specifiedDirectives } from '../../type/directives.mjs';
- /**
- * Unique directive names per location
- *
- * A GraphQL document is only valid if all non-repeatable directives at
- * a given location are uniquely named.
- *
- * See https://spec.graphql.org/draft/#sec-Directives-Are-Unique-Per-Location
- */
- export function UniqueDirectivesPerLocationRule(context) {
- const uniqueDirectiveMap = Object.create(null);
- const schema = context.getSchema();
- const definedDirectives = schema
- ? schema.getDirectives()
- : specifiedDirectives;
- for (const directive of definedDirectives) {
- uniqueDirectiveMap[directive.name] = !directive.isRepeatable;
- }
- const astDefinitions = context.getDocument().definitions;
- for (const def of astDefinitions) {
- if (def.kind === Kind.DIRECTIVE_DEFINITION) {
- uniqueDirectiveMap[def.name.value] = !def.repeatable;
- }
- }
- const schemaDirectives = Object.create(null);
- const typeDirectivesMap = Object.create(null);
- return {
- // Many different AST nodes may contain directives. Rather than listing
- // them all, just listen for entering any node, and check to see if it
- // defines any directives.
- enter(node) {
- if (!('directives' in node) || !node.directives) {
- return;
- }
- let seenDirectives;
- if (
- node.kind === Kind.SCHEMA_DEFINITION ||
- node.kind === Kind.SCHEMA_EXTENSION
- ) {
- seenDirectives = schemaDirectives;
- } else if (isTypeDefinitionNode(node) || isTypeExtensionNode(node)) {
- const typeName = node.name.value;
- seenDirectives = typeDirectivesMap[typeName];
- if (seenDirectives === undefined) {
- typeDirectivesMap[typeName] = seenDirectives = Object.create(null);
- }
- } else {
- seenDirectives = Object.create(null);
- }
- for (const directive of node.directives) {
- const directiveName = directive.name.value;
- if (uniqueDirectiveMap[directiveName]) {
- if (seenDirectives[directiveName]) {
- context.reportError(
- new GraphQLError(
- `The directive "@${directiveName}" can only be used once at this location.`,
- {
- nodes: [seenDirectives[directiveName], directive],
- },
- ),
- );
- } else {
- seenDirectives[directiveName] = directive;
- }
- }
- }
- },
- };
- }
|