Skip to content

Commit e287ba1

Browse files
committed
Track directive definition directives across extensions
1 parent a475823 commit e287ba1

2 files changed

Lines changed: 92 additions & 0 deletions

File tree

src/validation/__tests__/UniqueDirectivesPerLocationRule-test.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import { describe, it } from 'mocha';
22

3+
import { expectJSON } from '../../__testUtils__/expectJSON';
4+
35
import { parse } from '../../language/parser';
46

57
import type { GraphQLSchema } from '../../type/schema';
68

79
import { extendSchema } from '../../utilities/extendSchema';
810

911
import { UniqueDirectivesPerLocationRule } from '../rules/UniqueDirectivesPerLocationRule';
12+
import { validateSDL } from '../validate';
1013

1114
import {
1215
expectSDLValidationErrors,
@@ -42,6 +45,14 @@ function expectSDLErrors(sdlStr: string, schema?: GraphQLSchema) {
4245
);
4346
}
4447

48+
function expectExperimentalSDLErrors(sdlStr: string, schema?: GraphQLSchema) {
49+
const doc = parse(sdlStr, {
50+
experimentalDirectivesOnDirectiveDefinitions: true,
51+
});
52+
const errors = validateSDL(doc, schema, [UniqueDirectivesPerLocationRule]);
53+
return expectJSON(errors);
54+
}
55+
4556
describe('Validate: Directives Are Unique Per Location', () => {
4657
it('no directives', () => {
4758
expectValid(`
@@ -391,4 +402,74 @@ describe('Validate: Directives Are Unique Per Location', () => {
391402
},
392403
]);
393404
});
405+
406+
it('duplicate directives on directive definitions', () => {
407+
expectExperimentalSDLErrors(`
408+
directive @nonRepeatable on DIRECTIVE_DEFINITION
409+
410+
directive @testDirective @nonRepeatable @nonRepeatable on FIELD_DEFINITION
411+
`).toDeepEqual([
412+
{
413+
message:
414+
'The directive "@nonRepeatable" can only be used once at this location.',
415+
locations: [
416+
{ line: 4, column: 32 },
417+
{ line: 4, column: 47 },
418+
],
419+
},
420+
]);
421+
});
422+
423+
it('duplicate directives on directive extensions', () => {
424+
expectExperimentalSDLErrors(`
425+
directive @nonRepeatable on DIRECTIVE_DEFINITION
426+
427+
extend directive @testDirective @nonRepeatable @nonRepeatable
428+
`).toDeepEqual([
429+
{
430+
message:
431+
'The directive "@nonRepeatable" can only be used once at this location.',
432+
locations: [
433+
{ line: 4, column: 39 },
434+
{ line: 4, column: 54 },
435+
],
436+
},
437+
]);
438+
});
439+
440+
it('duplicate directives between directive definitions and extensions', () => {
441+
expectExperimentalSDLErrors(`
442+
directive @nonRepeatable on DIRECTIVE_DEFINITION
443+
444+
directive @testDirective @nonRepeatable on FIELD_DEFINITION
445+
extend directive @testDirective @nonRepeatable
446+
`).toDeepEqual([
447+
{
448+
message:
449+
'The directive "@nonRepeatable" can only be used once at this location.',
450+
locations: [
451+
{ line: 4, column: 32 },
452+
{ line: 5, column: 39 },
453+
],
454+
},
455+
]);
456+
});
457+
458+
it('duplicate directives between directive extensions', () => {
459+
expectExperimentalSDLErrors(`
460+
directive @nonRepeatable on DIRECTIVE_DEFINITION
461+
462+
extend directive @testDirective @nonRepeatable
463+
extend directive @testDirective @nonRepeatable
464+
`).toDeepEqual([
465+
{
466+
message:
467+
'The directive "@nonRepeatable" can only be used once at this location.',
468+
locations: [
469+
{ line: 4, column: 39 },
470+
{ line: 5, column: 39 },
471+
],
472+
},
473+
]);
474+
});
394475
});

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)