Skip to content

Commit cd3b99e

Browse files
committed
Track directive definition directives across extensions
1 parent 7e7bc46 commit cd3b99e

2 files changed

Lines changed: 56 additions & 0 deletions

File tree

src/validation/__tests__/UniqueDirectivesPerLocationRule-test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { expectJSON } from '../../__testUtils__/expectJSON';
12
import { describe, it } from 'mocha';
23

34
import { parse } from '../../language/parser';
@@ -6,6 +7,7 @@ import type { GraphQLSchema } from '../../type/schema';
67

78
import { extendSchema } from '../../utilities/extendSchema';
89

10+
import { validateSDL } from '../validate';
911
import { UniqueDirectivesPerLocationRule } from '../rules/UniqueDirectivesPerLocationRule';
1012

1113
import {
@@ -42,6 +44,14 @@ function expectSDLErrors(sdlStr: string, schema?: GraphQLSchema) {
4244
);
4345
}
4446

47+
function expectExperimentalSDLErrors(sdlStr: string, schema?: GraphQLSchema) {
48+
const doc = parse(sdlStr, {
49+
experimentalDirectivesOnDirectiveDefinitions: true,
50+
});
51+
const errors = validateSDL(doc, schema, [UniqueDirectivesPerLocationRule]);
52+
return expectJSON(errors);
53+
}
54+
4555
describe('Validate: Directives Are Unique Per Location', () => {
4656
it('no directives', () => {
4757
expectValid(`
@@ -391,4 +401,39 @@ describe('Validate: Directives Are Unique Per Location', () => {
391401
},
392402
]);
393403
});
404+
405+
it('duplicate directives on directive definitions', () => {
406+
expectExperimentalSDLErrors(`
407+
directive @nonRepeatable on DIRECTIVE_DEFINITION
408+
409+
directive @testDirective @nonRepeatable @nonRepeatable on FIELD_DEFINITION
410+
`).toDeepEqual([
411+
{
412+
message:
413+
'The directive "@nonRepeatable" can only be used once at this location.',
414+
locations: [
415+
{ line: 4, column: 32 },
416+
{ line: 4, column: 47 },
417+
],
418+
},
419+
]);
420+
});
421+
422+
it('duplicate directives between directive definitions and extensions', () => {
423+
expectExperimentalSDLErrors(`
424+
directive @nonRepeatable on DIRECTIVE_DEFINITION
425+
426+
directive @testDirective @nonRepeatable on FIELD_DEFINITION
427+
extend directive @testDirective @nonRepeatable
428+
`).toDeepEqual([
429+
{
430+
message:
431+
'The directive "@nonRepeatable" can only be used once at this location.',
432+
locations: [
433+
{ line: 4, column: 32 },
434+
{ line: 5, column: 39 },
435+
],
436+
},
437+
]);
438+
});
394439
});

src/validation/rules/UniqueDirectivesPerLocationRule.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export function UniqueDirectivesPerLocationRule(
4444

4545
const schemaDirectives = Object.create(null);
4646
const typeDirectivesMap = Object.create(null);
47+
const directiveDirectivesMap = Object.create(null);
4748

4849
return {
4950
// Many different AST nodes may contain directives. Rather than listing
@@ -66,6 +67,16 @@ export function UniqueDirectivesPerLocationRule(
6667
if (seenDirectives === undefined) {
6768
typeDirectivesMap[typeName] = seenDirectives = Object.create(null);
6869
}
70+
} else if (
71+
node.kind === Kind.DIRECTIVE_DEFINITION ||
72+
node.kind === Kind.DIRECTIVE_EXTENSION
73+
) {
74+
const directiveName = node.name.value;
75+
seenDirectives = directiveDirectivesMap[directiveName];
76+
if (seenDirectives === undefined) {
77+
directiveDirectivesMap[directiveName] = seenDirectives =
78+
Object.create(null);
79+
}
6980
} else {
7081
seenDirectives = Object.create(null);
7182
}

0 commit comments

Comments
 (0)