Skip to content

Commit 4c944d9

Browse files
committed
Adds Unique Directives Validation Rule.
This implements graphql/graphql-spec#229, in order to adhere with the October2016 edition of the spec.
1 parent c23f578 commit 4c944d9

File tree

3 files changed

+165
-0
lines changed

3 files changed

+165
-0
lines changed
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/**
2+
* Copyright (c) 2015, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
import { describe, it } from 'mocha';
11+
import { expectPassesRule, expectFailsRule } from './harness';
12+
import {
13+
UniqueDirectivesPerLocation,
14+
duplicateDirectiveMessage,
15+
} from '../rules/UniqueDirectivesPerLocation';
16+
17+
18+
function duplicateDirective(directiveName, l1, c1, l2, c2) {
19+
return {
20+
message: duplicateDirectiveMessage(directiveName),
21+
locations: [ { line: l1, column: c1 }, { line: l2, column: c2 } ],
22+
};
23+
}
24+
25+
describe('Validate: Directives Are Unique Per Location', () => {
26+
27+
it('no directives', () => {
28+
expectPassesRule(UniqueDirectivesPerLocation, `
29+
fragment Test on Type {
30+
field
31+
}
32+
`);
33+
});
34+
35+
it('unique directives in different locations', () => {
36+
expectPassesRule(UniqueDirectivesPerLocation, `
37+
fragment Test on Type @directiveA {
38+
field @directiveB
39+
}
40+
`);
41+
});
42+
43+
it('unique directives in same locations', () => {
44+
expectPassesRule(UniqueDirectivesPerLocation, `
45+
fragment Test on Type @directiveA @directiveB {
46+
field @directiveA @directiveB
47+
}
48+
`);
49+
});
50+
51+
it('same directives in different locations', () => {
52+
expectPassesRule(UniqueDirectivesPerLocation, `
53+
fragment Test on Type @directiveA {
54+
field @directiveA
55+
}
56+
`);
57+
});
58+
59+
it('same directives in similar locations', () => {
60+
expectPassesRule(UniqueDirectivesPerLocation, `
61+
fragment Test on Type {
62+
field @directive
63+
field @directive
64+
}
65+
`);
66+
});
67+
68+
it('duplicate directives in one location', () => {
69+
expectFailsRule(UniqueDirectivesPerLocation, `
70+
fragment Test on Type {
71+
field @directive @directive
72+
}
73+
`, [
74+
duplicateDirective('directive', 3, 15, 3, 26)
75+
]);
76+
});
77+
78+
it('many duplicate directives in one location', () => {
79+
expectFailsRule(UniqueDirectivesPerLocation, `
80+
fragment Test on Type {
81+
field @directive @directive @directive
82+
}
83+
`, [
84+
duplicateDirective('directive', 3, 15, 3, 26),
85+
duplicateDirective('directive', 3, 15, 3, 37)
86+
]);
87+
});
88+
89+
it('different duplicate directives in one location', () => {
90+
expectFailsRule(UniqueDirectivesPerLocation, `
91+
fragment Test on Type {
92+
field @directiveA @directiveB @directiveA @directiveB
93+
}
94+
`, [
95+
duplicateDirective('directiveA', 3, 15, 3, 39),
96+
duplicateDirective('directiveB', 3, 27, 3, 51)
97+
]);
98+
});
99+
100+
it('duplicate directives in many locations', () => {
101+
expectFailsRule(UniqueDirectivesPerLocation, `
102+
fragment Test on Type @directive @directive {
103+
field @directive @directive
104+
}
105+
`, [
106+
duplicateDirective('directive', 2, 29, 2, 40),
107+
duplicateDirective('directive', 3, 15, 3, 26)
108+
]);
109+
});
110+
111+
});
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/* @flow */
2+
/**
3+
* Copyright (c) 2015, Facebook, Inc.
4+
* All rights reserved.
5+
*
6+
* This source code is licensed under the BSD-style license found in the
7+
* LICENSE file in the root directory of this source tree. An additional grant
8+
* of patent rights can be found in the PATENTS file in the same directory.
9+
*/
10+
11+
import type { ValidationContext } from '../index';
12+
import { GraphQLError } from '../../error';
13+
14+
15+
export function duplicateDirectiveMessage(directiveName: string): string {
16+
return `The directive "${directiveName}" can only be used once at ` +
17+
'this location.';
18+
}
19+
20+
/**
21+
* Unique directive names per location
22+
*
23+
* A GraphQL document is only valid if all directives at a given location
24+
* are uniquely named.
25+
*/
26+
export function UniqueDirectivesPerLocation(context: ValidationContext): any {
27+
return {
28+
// Many different AST nodes may contain directives. Rather than listing
29+
// them all, just listen for entering any node, and check to see if it
30+
// defines any directives.
31+
enter(node) {
32+
if (node.directives) {
33+
const knownDirectives = Object.create(null);
34+
node.directives.forEach(directive => {
35+
const directiveName = directive.name.value;
36+
if (knownDirectives[directiveName]) {
37+
context.reportError(new GraphQLError(
38+
duplicateDirectiveMessage(directiveName),
39+
[ knownDirectives[directiveName], directive ]
40+
));
41+
} else {
42+
knownDirectives[directiveName] = directive;
43+
}
44+
});
45+
}
46+
}
47+
};
48+
}

src/validation/specifiedRules.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ import { NoUnusedVariables } from './rules/NoUnusedVariables';
5656
// Spec Section: "Directives Are Defined"
5757
import { KnownDirectives } from './rules/KnownDirectives';
5858

59+
// Spec Section: "Directives Are Unique Per Location"
60+
import {
61+
UniqueDirectivesPerLocation
62+
} from './rules/UniqueDirectivesPerLocation';
63+
5964
// Spec Section: "Argument Names"
6065
import { KnownArgumentNames } from './rules/KnownArgumentNames';
6166

@@ -104,6 +109,7 @@ export const specifiedRules: Array<(context: ValidationContext) => any> = [
104109
NoUndefinedVariables,
105110
NoUnusedVariables,
106111
KnownDirectives,
112+
UniqueDirectivesPerLocation,
107113
KnownArgumentNames,
108114
UniqueArgumentNames,
109115
ArgumentsOfCorrectType,

0 commit comments

Comments
 (0)