Skip to content

Commit 261b99b

Browse files
bgwleebyron
authored andcommitted
Eliminate circular dependency in validation rules (#1263)
`validation/validate.js` depends on all of the rules, and all of the rules depend on `ValidationContext`, which was previously exported by `validation/validate.js`. This moves `ValidationContext` out of `validate.js` to resolve this. Circular dependencies are fine, but they tend to slow down flow, since it moves more work to the merging phase, and the merging phase isn't as fast as the inference phase. As a result, this should make flow marginally faster. This does have a minor breaking change: `graphql/validatation/validate` no longer exports `ValidationContext`, but `graphql/validation` still does. I can add an export to `validate.js` to preserve that behavior, but it's a minor change to a non-public API, and graphql-js has broken these things in the past.
1 parent 499a759 commit 261b99b

32 files changed

+257
-238
lines changed

src/utilities/isValidLiteralValue.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { visit, visitWithTypeInfo } from '../language/visitor';
1515
import type { GraphQLInputType } from '../type/definition';
1616
import { GraphQLSchema } from '../type/schema';
1717
import { ValuesOfCorrectType } from '../validation/rules/ValuesOfCorrectType';
18-
import { ValidationContext } from '../validation/validate';
18+
import ValidationContext from '../validation/ValidationContext';
1919

2020
/**
2121
* Utility which determines if a value literal node is valid for an input type.

src/validation/ValidationContext.js

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict
8+
*/
9+
10+
import type { ObjMap } from '../jsutils/ObjMap';
11+
import { GraphQLError } from '../error';
12+
import { visit, visitWithTypeInfo } from '../language/visitor';
13+
import { Kind } from '../language/kinds';
14+
import type {
15+
DocumentNode,
16+
OperationDefinitionNode,
17+
VariableNode,
18+
SelectionSetNode,
19+
FragmentSpreadNode,
20+
FragmentDefinitionNode,
21+
} from '../language/ast';
22+
import { GraphQLSchema } from '../type/schema';
23+
import type {
24+
GraphQLInputType,
25+
GraphQLOutputType,
26+
GraphQLCompositeType,
27+
GraphQLField,
28+
GraphQLArgument,
29+
} from '../type/definition';
30+
import type { GraphQLDirective } from '../type/directives';
31+
import { TypeInfo } from '../utilities/TypeInfo';
32+
33+
type NodeWithSelectionSet = OperationDefinitionNode | FragmentDefinitionNode;
34+
type VariableUsage = { node: VariableNode, type: ?GraphQLInputType };
35+
36+
/**
37+
* An instance of this class is passed as the "this" context to all validators,
38+
* allowing access to commonly useful contextual information from within a
39+
* validation rule.
40+
*/
41+
export default class ValidationContext {
42+
_schema: GraphQLSchema;
43+
_ast: DocumentNode;
44+
_typeInfo: TypeInfo;
45+
_errors: Array<GraphQLError>;
46+
_fragments: ObjMap<FragmentDefinitionNode>;
47+
_fragmentSpreads: Map<SelectionSetNode, $ReadOnlyArray<FragmentSpreadNode>>;
48+
_recursivelyReferencedFragments: Map<
49+
OperationDefinitionNode,
50+
$ReadOnlyArray<FragmentDefinitionNode>,
51+
>;
52+
_variableUsages: Map<NodeWithSelectionSet, $ReadOnlyArray<VariableUsage>>;
53+
_recursiveVariableUsages: Map<
54+
OperationDefinitionNode,
55+
$ReadOnlyArray<VariableUsage>,
56+
>;
57+
58+
constructor(
59+
schema: GraphQLSchema,
60+
ast: DocumentNode,
61+
typeInfo: TypeInfo,
62+
): void {
63+
this._schema = schema;
64+
this._ast = ast;
65+
this._typeInfo = typeInfo;
66+
this._errors = [];
67+
this._fragmentSpreads = new Map();
68+
this._recursivelyReferencedFragments = new Map();
69+
this._variableUsages = new Map();
70+
this._recursiveVariableUsages = new Map();
71+
}
72+
73+
reportError(error: GraphQLError): void {
74+
this._errors.push(error);
75+
}
76+
77+
getErrors(): $ReadOnlyArray<GraphQLError> {
78+
return this._errors;
79+
}
80+
81+
getSchema(): GraphQLSchema {
82+
return this._schema;
83+
}
84+
85+
getDocument(): DocumentNode {
86+
return this._ast;
87+
}
88+
89+
getFragment(name: string): ?FragmentDefinitionNode {
90+
let fragments = this._fragments;
91+
if (!fragments) {
92+
this._fragments = fragments = this.getDocument().definitions.reduce(
93+
(frags, statement) => {
94+
if (statement.kind === Kind.FRAGMENT_DEFINITION) {
95+
frags[statement.name.value] = statement;
96+
}
97+
return frags;
98+
},
99+
Object.create(null),
100+
);
101+
}
102+
return fragments[name];
103+
}
104+
105+
getFragmentSpreads(
106+
node: SelectionSetNode,
107+
): $ReadOnlyArray<FragmentSpreadNode> {
108+
let spreads = this._fragmentSpreads.get(node);
109+
if (!spreads) {
110+
spreads = [];
111+
const setsToVisit: Array<SelectionSetNode> = [node];
112+
while (setsToVisit.length !== 0) {
113+
const set = setsToVisit.pop();
114+
for (let i = 0; i < set.selections.length; i++) {
115+
const selection = set.selections[i];
116+
if (selection.kind === Kind.FRAGMENT_SPREAD) {
117+
spreads.push(selection);
118+
} else if (selection.selectionSet) {
119+
setsToVisit.push(selection.selectionSet);
120+
}
121+
}
122+
}
123+
this._fragmentSpreads.set(node, spreads);
124+
}
125+
return spreads;
126+
}
127+
128+
getRecursivelyReferencedFragments(
129+
operation: OperationDefinitionNode,
130+
): $ReadOnlyArray<FragmentDefinitionNode> {
131+
let fragments = this._recursivelyReferencedFragments.get(operation);
132+
if (!fragments) {
133+
fragments = [];
134+
const collectedNames = Object.create(null);
135+
const nodesToVisit: Array<SelectionSetNode> = [operation.selectionSet];
136+
while (nodesToVisit.length !== 0) {
137+
const node = nodesToVisit.pop();
138+
const spreads = this.getFragmentSpreads(node);
139+
for (let i = 0; i < spreads.length; i++) {
140+
const fragName = spreads[i].name.value;
141+
if (collectedNames[fragName] !== true) {
142+
collectedNames[fragName] = true;
143+
const fragment = this.getFragment(fragName);
144+
if (fragment) {
145+
fragments.push(fragment);
146+
nodesToVisit.push(fragment.selectionSet);
147+
}
148+
}
149+
}
150+
}
151+
this._recursivelyReferencedFragments.set(operation, fragments);
152+
}
153+
return fragments;
154+
}
155+
156+
getVariableUsages(node: NodeWithSelectionSet): $ReadOnlyArray<VariableUsage> {
157+
let usages = this._variableUsages.get(node);
158+
if (!usages) {
159+
const newUsages = [];
160+
const typeInfo = new TypeInfo(this._schema);
161+
visit(
162+
node,
163+
visitWithTypeInfo(typeInfo, {
164+
VariableDefinition: () => false,
165+
Variable(variable) {
166+
newUsages.push({ node: variable, type: typeInfo.getInputType() });
167+
},
168+
}),
169+
);
170+
usages = newUsages;
171+
this._variableUsages.set(node, usages);
172+
}
173+
return usages;
174+
}
175+
176+
getRecursiveVariableUsages(
177+
operation: OperationDefinitionNode,
178+
): $ReadOnlyArray<VariableUsage> {
179+
let usages = this._recursiveVariableUsages.get(operation);
180+
if (!usages) {
181+
usages = this.getVariableUsages(operation);
182+
const fragments = this.getRecursivelyReferencedFragments(operation);
183+
for (let i = 0; i < fragments.length; i++) {
184+
Array.prototype.push.apply(
185+
usages,
186+
this.getVariableUsages(fragments[i]),
187+
);
188+
}
189+
this._recursiveVariableUsages.set(operation, usages);
190+
}
191+
return usages;
192+
}
193+
194+
getType(): ?GraphQLOutputType {
195+
return this._typeInfo.getType();
196+
}
197+
198+
getParentType(): ?GraphQLCompositeType {
199+
return this._typeInfo.getParentType();
200+
}
201+
202+
getInputType(): ?GraphQLInputType {
203+
return this._typeInfo.getInputType();
204+
}
205+
206+
getParentInputType(): ?GraphQLInputType {
207+
return this._typeInfo.getParentInputType();
208+
}
209+
210+
getFieldDef(): ?GraphQLField<*, *> {
211+
return this._typeInfo.getFieldDef();
212+
}
213+
214+
getDirective(): ?GraphQLDirective {
215+
return this._typeInfo.getDirective();
216+
}
217+
218+
getArgument(): ?GraphQLArgument {
219+
return this._typeInfo.getArgument();
220+
}
221+
}

src/validation/index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77
* @flow strict
88
*/
99

10-
export { validate, ValidationContext } from './validate';
10+
export { validate } from './validate';
11+
12+
// https://github.com/tc39/proposal-export-default-from
13+
import ValidationContext from './ValidationContext';
14+
export { ValidationContext };
1115

1216
export { specifiedRules } from './specifiedRules';
1317

src/validation/rules/ExecutableDefinitions.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @flow strict
88
*/
99

10-
import type { ValidationContext } from '../index';
10+
import type ValidationContext from '../ValidationContext';
1111
import { GraphQLError } from '../../error';
1212
import { Kind } from '../../language/kinds';
1313
import type { ASTVisitor } from '../../language/visitor';

src/validation/rules/FieldsOnCorrectType.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @flow strict
88
*/
99

10-
import type { ValidationContext } from '../index';
10+
import type ValidationContext from '../ValidationContext';
1111
import { GraphQLError } from '../../error';
1212
import suggestionList from '../../jsutils/suggestionList';
1313
import quotedOrList from '../../jsutils/quotedOrList';

src/validation/rules/FragmentsOnCompositeTypes.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @flow strict
88
*/
99

10-
import type { ValidationContext } from '../index';
10+
import type ValidationContext from '../ValidationContext';
1111
import { GraphQLError } from '../../error';
1212
import { print } from '../../language/printer';
1313
import type { ASTVisitor } from '../../language/visitor';

src/validation/rules/KnownArgumentNames.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @flow strict
88
*/
99

10-
import type { ValidationContext } from '../index';
10+
import type ValidationContext from '../ValidationContext';
1111
import { GraphQLError } from '../../error';
1212
import type { ASTVisitor } from '../../language/visitor';
1313
import suggestionList from '../../jsutils/suggestionList';

src/validation/rules/KnownDirectives.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @flow strict
88
*/
99

10-
import type { ValidationContext } from '../index';
10+
import type ValidationContext from '../ValidationContext';
1111
import { GraphQLError } from '../../error';
1212
import find from '../../jsutils/find';
1313
import { Kind } from '../../language/kinds';

src/validation/rules/KnownFragmentNames.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @flow strict
88
*/
99

10-
import type { ValidationContext } from '../index';
10+
import type ValidationContext from '../ValidationContext';
1111
import { GraphQLError } from '../../error';
1212
import type { ASTVisitor } from '../../language/visitor';
1313

src/validation/rules/KnownTypeNames.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @flow strict
88
*/
99

10-
import type { ValidationContext } from '../index';
10+
import type ValidationContext from '../ValidationContext';
1111
import { GraphQLError } from '../../error';
1212
import suggestionList from '../../jsutils/suggestionList';
1313
import quotedOrList from '../../jsutils/quotedOrList';

src/validation/rules/LoneAnonymousOperation.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @flow strict
88
*/
99

10-
import type { ValidationContext } from '../index';
10+
import type ValidationContext from '../ValidationContext';
1111
import { GraphQLError } from '../../error';
1212
import { Kind } from '../../language/kinds';
1313
import type { ASTVisitor } from '../../language/visitor';

src/validation/rules/NoFragmentCycles.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @flow strict
88
*/
99

10-
import type { ValidationContext } from '../index';
10+
import type ValidationContext from '../ValidationContext';
1111
import { GraphQLError } from '../../error';
1212
import type { FragmentDefinitionNode } from '../../language/ast';
1313
import type { ASTVisitor } from '../../language/visitor';

src/validation/rules/NoUndefinedVariables.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @flow strict
88
*/
99

10-
import type { ValidationContext } from '../index';
10+
import type ValidationContext from '../ValidationContext';
1111
import { GraphQLError } from '../../error';
1212
import type { ASTVisitor } from '../../language/visitor';
1313

src/validation/rules/NoUnusedFragments.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @flow strict
88
*/
99

10-
import type { ValidationContext } from '../index';
10+
import type ValidationContext from '../ValidationContext';
1111
import { GraphQLError } from '../../error';
1212
import type { ASTVisitor } from '../../language/visitor';
1313

src/validation/rules/NoUnusedVariables.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @flow strict
88
*/
99

10-
import type { ValidationContext } from '../index';
10+
import type ValidationContext from '../ValidationContext';
1111
import { GraphQLError } from '../../error';
1212
import type { ASTVisitor } from '../../language/visitor';
1313

src/validation/rules/OverlappingFieldsCanBeMerged.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @flow strict
88
*/
99

10-
import type { ValidationContext } from '../index';
10+
import type ValidationContext from '../ValidationContext';
1111
import { GraphQLError } from '../../error';
1212
import find from '../../jsutils/find';
1313
import type { ObjMap } from '../../jsutils/ObjMap';

src/validation/rules/PossibleFragmentSpreads.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @flow strict
88
*/
99

10-
import type { ValidationContext } from '../index';
10+
import type ValidationContext from '../ValidationContext';
1111
import { GraphQLError } from '../../error';
1212
import type { ASTVisitor } from '../../language/visitor';
1313
import { doTypesOverlap } from '../../utilities/typeComparators';

src/validation/rules/ProvidedNonNullArguments.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @flow strict
88
*/
99

10-
import type { ValidationContext } from '../index';
10+
import type ValidationContext from '../ValidationContext';
1111
import { GraphQLError } from '../../error';
1212
import keyMap from '../../jsutils/keyMap';
1313
import { isNonNullType } from '../../type/definition';

src/validation/rules/ScalarLeafs.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @flow strict
88
*/
99

10-
import type { ValidationContext } from '../index';
10+
import type ValidationContext from '../ValidationContext';
1111
import { GraphQLError } from '../../error';
1212
import type { FieldNode } from '../../language/ast';
1313
import { getNamedType, isLeafType } from '../../type/definition';

src/validation/rules/SingleFieldSubscriptions.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @flow strict
88
*/
99

10-
import type { ValidationContext } from '../index';
10+
import type ValidationContext from '../ValidationContext';
1111
import { GraphQLError } from '../../error';
1212
import type { OperationDefinitionNode } from '../../language/ast';
1313
import type { ASTVisitor } from '../../language/visitor';

0 commit comments

Comments
 (0)