UNPKG

9.65 kBJavaScriptView Raw
1/**
2 * Copyright (c) Facebook, Inc. and its affiliates.
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 * strict-local
8 * @format
9 */
10'use strict';
11
12var _objectSpread2 = require("@babel/runtime/helpers/interopRequireDefault")(require("@babel/runtime/helpers/objectSpread"));
13
14/**
15 * @public
16 *
17 * Helper for writing compiler transforms that apply "map" and/or "filter"-style
18 * operations to compiler contexts. The `visitor` argument accepts a map of IR
19 * kinds to user-defined functions that can map nodes of that kind to new values
20 * (of the same kind).
21 *
22 * If a visitor function is defined for a kind, the visitor function is
23 * responsible for traversing its children (by calling `this.traverse(node)`)
24 * and returning either the input (to indicate no changes), a new node (to
25 * indicate changes), or null/undefined (to indicate the removal of that node
26 * from the output).
27 *
28 * If a visitor function is *not* defined for a kind, a default traversal is
29 * used to evaluate its children.
30 *
31 * The `stateInitializer` argument accepts an optional function to construct the
32 * state for each document (fragment or root) in the context. Any documents for
33 * which the initializer returns null/undefined is deleted from the context
34 * without being traversed.
35 *
36 * Example: Alias all scalar fields with the reverse of their name:
37 *
38 * ```
39 * transform(context, {
40 * ScalarField: visitScalarField,
41 * });
42 *
43 * function visitScalarField(field: ScalarField, state: State): ?ScalarField {
44 * // Traverse child nodes - for a scalar field these are the arguments &
45 * // directives.
46 * const nextField = this.traverse(field, state);
47 * // Return a new node with a different alias.
48 * return {
49 * ...nextField,
50 * alias: nextField.name.split('').reverse().join(''),
51 * };
52 * }
53 * ```
54 */
55function transform(context, visitor, stateInitializer) {
56 var transformer = new Transformer(context, visitor);
57 return context.withMutations(function (ctx) {
58 var nextContext = ctx;
59
60 var errors = require("./RelayCompilerError").eachWithErrors(context.documents(), function (prevNode) {
61 var nextNode;
62
63 if (stateInitializer === undefined) {
64 nextNode = transformer.visit(prevNode, undefined);
65 } else {
66 var _state = stateInitializer(prevNode);
67
68 if (_state != null) {
69 nextNode = transformer.visit(prevNode, _state);
70 }
71 }
72
73 if (!nextNode) {
74 nextContext = nextContext.remove(prevNode.name);
75 } else if (nextNode !== prevNode) {
76 nextContext = nextContext.replace(nextNode);
77 }
78 });
79
80 if (errors != null && errors.length !== 0) {
81 throw require("./RelayCompilerError").createCombinedError(errors);
82 }
83
84 return nextContext;
85 });
86}
87/**
88 * @internal
89 */
90
91
92var Transformer =
93/*#__PURE__*/
94function () {
95 function Transformer(context, visitor) {
96 this._context = context;
97 this._states = [];
98 this._visitor = visitor;
99 }
100 /**
101 * @public
102 *
103 * Returns the original compiler context that is being transformed. This can
104 * be used to look up fragments by name, for example.
105 */
106
107
108 var _proto = Transformer.prototype;
109
110 _proto.getContext = function getContext() {
111 return this._context;
112 };
113 /**
114 * @public
115 *
116 * Transforms the node, calling a user-defined visitor function if defined for
117 * the node's kind. Uses the given state for this portion of the traversal.
118 *
119 * Note: This differs from `traverse` in that it calls a visitor function for
120 * the node itself.
121 */
122
123
124 _proto.visit = function visit(node, state) {
125 this._states.push(state);
126
127 var nextNode = this._visit(node);
128
129 this._states.pop();
130
131 return nextNode;
132 };
133 /**
134 * @public
135 *
136 * Transforms the children of the given node, skipping the user-defined
137 * visitor function for the node itself. Uses the given state for this portion
138 * of the traversal.
139 *
140 * Note: This differs from `visit` in that it does not call a visitor function
141 * for the node itself.
142 */
143
144
145 _proto.traverse = function traverse(node, state) {
146 this._states.push(state);
147
148 var nextNode = this._traverse(node);
149
150 this._states.pop();
151
152 return nextNode;
153 };
154
155 _proto._visit = function _visit(node) {
156 var nodeVisitor = this._visitor[node.kind];
157
158 if (nodeVisitor) {
159 // If a handler for the kind is defined, it is responsible for calling
160 // `traverse` to transform children as necessary.
161 var _state2 = this._getState();
162
163 var nextNode = nodeVisitor.call(this, node, _state2);
164 return nextNode;
165 } // Otherwise traverse is called automatically.
166
167
168 return this._traverse(node);
169 };
170
171 _proto._traverse = function _traverse(prevNode) {
172 var nextNode;
173
174 switch (prevNode.kind) {
175 case 'Argument':
176 nextNode = this._traverseChildren(prevNode, null, ['value']);
177 break;
178
179 case 'Literal':
180 case 'LocalArgumentDefinition':
181 case 'RootArgumentDefinition':
182 case 'Variable':
183 nextNode = prevNode;
184 break;
185
186 case 'Defer':
187 nextNode = this._traverseChildren(prevNode, ['selections'], ['if']);
188 break;
189
190 case 'Stream':
191 nextNode = this._traverseChildren(prevNode, ['selections'], ['if', 'initialCount']);
192 break;
193
194 case 'Directive':
195 nextNode = this._traverseChildren(prevNode, ['args']);
196 break;
197
198 case 'MatchBranch':
199 nextNode = this._traverseChildren(prevNode, ['selections']);
200
201 if (!nextNode.selections.length) {
202 nextNode = null;
203 }
204
205 break;
206
207 case 'FragmentSpread':
208 case 'ScalarField':
209 nextNode = this._traverseChildren(prevNode, ['args', 'directives']);
210 break;
211
212 case 'LinkedField':
213 nextNode = this._traverseChildren(prevNode, ['args', 'directives', 'selections']);
214
215 if (!nextNode.selections.length) {
216 nextNode = null;
217 }
218
219 break;
220
221 case 'ListValue':
222 nextNode = this._traverseChildren(prevNode, ['items']);
223 break;
224
225 case 'MatchField':
226 nextNode = this._traverseChildren(prevNode, ['args', 'directives', 'selections']);
227 break;
228
229 case 'ObjectFieldValue':
230 nextNode = this._traverseChildren(prevNode, null, ['value']);
231 break;
232
233 case 'ObjectValue':
234 nextNode = this._traverseChildren(prevNode, ['fields']);
235 break;
236
237 case 'Condition':
238 nextNode = this._traverseChildren(prevNode, ['directives', 'selections'], ['condition']);
239
240 if (!nextNode.selections.length) {
241 nextNode = null;
242 }
243
244 break;
245
246 case 'InlineFragment':
247 nextNode = this._traverseChildren(prevNode, ['directives', 'selections']);
248
249 if (!nextNode.selections.length) {
250 nextNode = null;
251 }
252
253 break;
254
255 case 'Fragment':
256 case 'Root':
257 nextNode = this._traverseChildren(prevNode, ['argumentDefinitions', 'directives', 'selections']);
258 break;
259
260 case 'Request':
261 nextNode = this._traverseChildren(prevNode, null, ['fragment', 'root']);
262 break;
263
264 case 'SplitOperation':
265 nextNode = this._traverseChildren(prevNode, ['selections']);
266 break;
267
268 default:
269 prevNode;
270 !false ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'GraphQLIRTransformer: Unknown kind `%s`.', prevNode.kind) : require("fbjs/lib/invariant")(false) : void 0;
271 }
272
273 return nextNode;
274 };
275
276 _proto._traverseChildren = function _traverseChildren(prevNode, pluralKeys, singularKeys) {
277 var _this = this;
278
279 var nextNode;
280 pluralKeys && pluralKeys.forEach(function (key) {
281 var prevItems = prevNode[key];
282
283 if (!prevItems) {
284 return;
285 }
286
287 !Array.isArray(prevItems) ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'GraphQLIRTransformer: Expected data for `%s` to be an array, got `%s`.', key, prevItems) : require("fbjs/lib/invariant")(false) : void 0;
288
289 var nextItems = _this._map(prevItems);
290
291 if (nextNode || nextItems !== prevItems) {
292 nextNode = nextNode || (0, _objectSpread2["default"])({}, prevNode);
293 nextNode[key] = nextItems;
294 }
295 });
296 singularKeys && singularKeys.forEach(function (key) {
297 var prevItem = prevNode[key];
298
299 if (!prevItem) {
300 return;
301 }
302
303 var nextItem = _this._visit(prevItem);
304
305 if (nextNode || nextItem !== prevItem) {
306 nextNode = nextNode || (0, _objectSpread2["default"])({}, prevNode);
307 nextNode[key] = nextItem;
308 }
309 });
310 return nextNode || prevNode;
311 };
312
313 _proto._map = function _map(prevItems) {
314 var _this2 = this;
315
316 var nextItems;
317 prevItems.forEach(function (prevItem, index) {
318 var nextItem = _this2._visit(prevItem);
319
320 if (nextItems || nextItem !== prevItem) {
321 nextItems = nextItems || prevItems.slice(0, index);
322
323 if (nextItem) {
324 nextItems.push(nextItem);
325 }
326 }
327 });
328 return nextItems || prevItems;
329 };
330
331 _proto._getState = function _getState() {
332 !this._states.length ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'GraphQLIRTransformer: Expected a current state to be set but found none. ' + 'This is usually the result of mismatched number of pushState()/popState() ' + 'calls.') : require("fbjs/lib/invariant")(false) : void 0;
333 return this._states[this._states.length - 1];
334 };
335
336 return Transformer;
337}();
338
339module.exports = {
340 transform: transform
341};
\No newline at end of file