Skip to content

Commit 71c7a14

Browse files
Reuse groupBy in validation rules (#3307)
1 parent 96b146d commit 71c7a14

6 files changed

+62
-67
lines changed

src/jsutils/groupBy.ts

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Groups array items into a Map, given a function to produce grouping key.
3+
*/
4+
export function groupBy<K, T>(
5+
list: ReadonlyArray<T>,
6+
keyFn: (item: T) => K,
7+
): Map<K, ReadonlyArray<T>> {
8+
const result = new Map<K, Array<T>>();
9+
for (const item of list) {
10+
const key = keyFn(item);
11+
const group = result.get(key);
12+
if (group === undefined) {
13+
result.set(key, [item]);
14+
} else {
15+
group.push(item);
16+
}
17+
}
18+
return result;
19+
}

src/validation/__tests__/UniqueArgumentNamesRule-test.ts

-12
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,6 @@ describe('Validate: Unique argument names', () => {
113113
locations: [
114114
{ line: 3, column: 15 },
115115
{ line: 3, column: 30 },
116-
],
117-
},
118-
{
119-
message: 'There can be only one argument named "arg1".',
120-
locations: [
121-
{ line: 3, column: 15 },
122116
{ line: 3, column: 45 },
123117
],
124118
},
@@ -152,12 +146,6 @@ describe('Validate: Unique argument names', () => {
152146
locations: [
153147
{ line: 3, column: 26 },
154148
{ line: 3, column: 41 },
155-
],
156-
},
157-
{
158-
message: 'There can be only one argument named "arg1".',
159-
locations: [
160-
{ line: 3, column: 26 },
161149
{ line: 3, column: 56 },
162150
],
163151
},

src/validation/__tests__/UniqueVariableNamesRule-test.ts

-6
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,6 @@ describe('Validate: Unique variable names', () => {
3131
locations: [
3232
{ line: 2, column: 16 },
3333
{ line: 2, column: 25 },
34-
],
35-
},
36-
{
37-
message: 'There can be only one variable named "$x".',
38-
locations: [
39-
{ line: 2, column: 16 },
4034
{ line: 2, column: 34 },
4135
],
4236
},

src/validation/rules/UniqueArgumentDefinitionNamesRule.ts

+2-17
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { groupBy } from '../../jsutils/groupBy';
2+
13
import { GraphQLError } from '../../error/GraphQLError';
24

35
import type { ASTVisitor } from '../../language/visitor';
@@ -72,20 +74,3 @@ export function UniqueArgumentDefinitionNamesRule(
7274
return false;
7375
}
7476
}
75-
76-
function groupBy<K, T>(
77-
list: ReadonlyArray<T>,
78-
keyFn: (item: T) => K,
79-
): Map<K, Array<T>> {
80-
const result = new Map<K, Array<T>>();
81-
for (const item of list) {
82-
const key = keyFn(item);
83-
const group = result.get(key);
84-
if (group === undefined) {
85-
result.set(key, [item]);
86-
} else {
87-
group.push(item);
88-
}
89-
}
90-
return result;
91-
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
import { groupBy } from '../../jsutils/groupBy';
2+
13
import { GraphQLError } from '../../error/GraphQLError';
4+
5+
import type { ArgumentNode } from '../../language/ast';
26
import type { ASTVisitor } from '../../language/visitor';
37

48
import type { ASTValidationContext } from '../ValidationContext';
@@ -14,27 +18,28 @@ import type { ASTValidationContext } from '../ValidationContext';
1418
export function UniqueArgumentNamesRule(
1519
context: ASTValidationContext,
1620
): ASTVisitor {
17-
let knownArgNames = Object.create(null);
1821
return {
19-
Field() {
20-
knownArgNames = Object.create(null);
21-
},
22-
Directive() {
23-
knownArgNames = Object.create(null);
24-
},
25-
Argument(node) {
26-
const argName = node.name.value;
27-
if (knownArgNames[argName]) {
22+
Field: checkArgUniqueness,
23+
Directive: checkArgUniqueness,
24+
};
25+
26+
function checkArgUniqueness(parentNode: {
27+
arguments?: ReadonlyArray<ArgumentNode>;
28+
}) {
29+
// istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203')
30+
const argumentNodes = parentNode.arguments ?? [];
31+
32+
const seenArgs = groupBy(argumentNodes, (arg) => arg.name.value);
33+
34+
for (const [argName, argNodes] of seenArgs) {
35+
if (argNodes.length > 1) {
2836
context.reportError(
2937
new GraphQLError(
3038
`There can be only one argument named "${argName}".`,
31-
[knownArgNames[argName], node.name],
39+
argNodes.map((node) => node.name),
3240
),
3341
);
34-
} else {
35-
knownArgNames[argName] = node.name;
3642
}
37-
return false;
38-
},
39-
};
43+
}
44+
}
4045
}

src/validation/rules/UniqueVariableNamesRule.ts

+20-16
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import { groupBy } from '../../jsutils/groupBy';
2+
13
import { GraphQLError } from '../../error/GraphQLError';
24

35
import type { ASTVisitor } from '../../language/visitor';
4-
import type { VariableDefinitionNode } from '../../language/ast';
56

67
import type { ASTValidationContext } from '../ValidationContext';
78

@@ -13,22 +14,25 @@ import type { ASTValidationContext } from '../ValidationContext';
1314
export function UniqueVariableNamesRule(
1415
context: ASTValidationContext,
1516
): ASTVisitor {
16-
let knownVariableNames = Object.create(null);
1717
return {
18-
OperationDefinition() {
19-
knownVariableNames = Object.create(null);
20-
},
21-
VariableDefinition(node: VariableDefinitionNode) {
22-
const variableName = node.variable.name.value;
23-
if (knownVariableNames[variableName]) {
24-
context.reportError(
25-
new GraphQLError(
26-
`There can be only one variable named "$${variableName}".`,
27-
[knownVariableNames[variableName], node.variable.name],
28-
),
29-
);
30-
} else {
31-
knownVariableNames[variableName] = node.variable.name;
18+
OperationDefinition(operationNode) {
19+
// istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203')
20+
const variableDefinitions = operationNode.variableDefinitions ?? [];
21+
22+
const seenVariableDefinitions = groupBy(
23+
variableDefinitions,
24+
(node) => node.variable.name.value,
25+
);
26+
27+
for (const [variableName, variableNodes] of seenVariableDefinitions) {
28+
if (variableNodes.length > 1) {
29+
context.reportError(
30+
new GraphQLError(
31+
`There can be only one variable named "$${variableName}".`,
32+
variableNodes.map((node) => node.variable.name),
33+
),
34+
);
35+
}
3236
}
3337
},
3438
};

0 commit comments

Comments
 (0)