UNPKG

11.4 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 *
8 * @format
9 */
10'use strict';
11
12var _objectSpread2 = require("@babel/runtime/helpers/interopRequireDefault")(require("@babel/runtime/helpers/objectSpread"));
13
14var _toConsumableArray2 = require("@babel/runtime/helpers/interopRequireDefault")(require("@babel/runtime/helpers/toConsumableArray"));
15
16/**
17 * A tranform that converts a set of documents containing fragments/fragment
18 * spreads *with* arguments to one where all arguments have been inlined. This
19 * is effectively static currying of functions. Nodes are changed as follows:
20 * - Fragment spreads with arguments are replaced with references to an inlined
21 * version of the referenced fragment.
22 * - Fragments with argument definitions are cloned once per unique set of
23 * arguments, with the name changed to original name + hash and all nested
24 * variable references changed to the value of that variable given its
25 * arguments.
26 * - Field & directive argument variables are replaced with the value of those
27 * variables in context.
28 * - All nodes are cloned with updated children.
29 *
30 * The transform also handles statically passing/failing Condition nodes:
31 * - Literal Conditions with a passing value are elided and their selections
32 * inlined in their parent.
33 * - Literal Conditions with a failing value are removed.
34 * - Nodes that would become empty as a result of the above are removed.
35 *
36 * Note that unreferenced fragments are not added to the output.
37 */
38function relayApplyFragmentArgumentTransform(context) {
39 var fragments = new Map();
40
41 var nextContext = require("./GraphQLIRTransformer").transform(context, {
42 Root: function Root(node) {
43 var scope = require("./RelayCompilerScope").getRootScope(node.argumentDefinitions);
44
45 return transformNode(context, fragments, scope, node, [node]);
46 },
47 // Fragments are included below where referenced.
48 // Unreferenced fragments are not included.
49 Fragment: function Fragment() {
50 return null;
51 }
52 });
53
54 return Array.from(fragments.values()).reduce(function (ctx, fragment) {
55 return fragment ? ctx.add(fragment) : ctx;
56 }, nextContext);
57}
58
59function transformNode(context, fragments, scope, node, errorContext) {
60 var selections = transformSelections(context, fragments, scope, node.selections, errorContext);
61
62 if (!selections) {
63 return null;
64 }
65
66 if (node.hasOwnProperty('directives')) {
67 var directives = transformDirectives(scope, node.directives, errorContext); // $FlowIssue: this is a valid `Node`:
68
69 return (0, _objectSpread2["default"])({}, node, {
70 directives: directives,
71 selections: selections
72 });
73 }
74
75 return (0, _objectSpread2["default"])({}, node, {
76 selections: selections
77 });
78}
79
80function transformFragmentSpread(context, fragments, scope, spread, errorContext) {
81 var directives = transformDirectives(scope, spread.directives, errorContext);
82 var appliedFragment = transformFragment(context, fragments, scope, spread, spread.args, (0, _toConsumableArray2["default"])(errorContext).concat([spread]));
83
84 if (!appliedFragment) {
85 return null;
86 }
87
88 var transformed = (0, _objectSpread2["default"])({}, spread, {
89 kind: 'FragmentSpread',
90 args: [],
91 directives: directives,
92 name: appliedFragment.name
93 });
94 return transformed;
95}
96
97function transformField(context, fragments, scope, field, errorContext) {
98 var args = transformArguments(scope, field.args, errorContext);
99 var directives = transformDirectives(scope, field.directives, errorContext);
100
101 if (field.kind === 'LinkedField' || field.kind === 'MatchField') {
102 var selections = transformSelections(context, fragments, scope, field.selections, errorContext);
103
104 if (!selections) {
105 return null;
106 } // $FlowFixMe(>=0.28.0)
107
108
109 return (0, _objectSpread2["default"])({}, field, {
110 args: args,
111 directives: directives,
112 selections: selections
113 });
114 } else {
115 return (0, _objectSpread2["default"])({}, field, {
116 args: args,
117 directives: directives
118 });
119 }
120}
121
122function transformCondition(context, fragments, scope, node, errorContext) {
123 var condition = transformValue(scope, node.condition, errorContext);
124
125 if (!(condition.kind === 'Literal' || condition.kind === 'Variable')) {
126 // This transform does whole-program optimization, errors in
127 // a single document could break invariants and/or cause
128 // additional spurious errors.
129 throw require("./RelayCompilerError").createNonRecoverableUserError('A non-scalar value was applied to an @include or @skip directive, ' + 'the `if` argument value must be a ' + 'variable or a literal Boolean.', [condition.loc]);
130 }
131
132 if (condition.kind === 'Literal' && condition.value !== node.passingValue) {
133 // Dead code, no need to traverse further.
134 return null;
135 }
136
137 var selections = transformSelections(context, fragments, scope, node.selections, errorContext);
138
139 if (!selections) {
140 return null;
141 }
142
143 if (condition.kind === 'Literal' && condition.value === node.passingValue) {
144 // Always passes, return inlined selections
145 return selections;
146 }
147
148 return [(0, _objectSpread2["default"])({}, node, {
149 condition: condition,
150 selections: selections
151 })];
152}
153
154function transformSelections(context, fragments, scope, selections, errorContext) {
155 var nextSelections = null;
156 selections.forEach(function (selection) {
157 var nextSelection;
158
159 if (selection.kind === 'InlineFragment' || selection.kind === 'MatchBranch') {
160 nextSelection = transformNode(context, fragments, scope, selection, errorContext);
161 } else if (selection.kind === 'FragmentSpread') {
162 nextSelection = transformFragmentSpread(context, fragments, scope, selection, errorContext);
163 } else if (selection.kind === 'Condition') {
164 var conditionSelections = transformCondition(context, fragments, scope, selection, errorContext);
165
166 if (conditionSelections) {
167 var _nextSelections;
168
169 nextSelections = nextSelections || [];
170
171 (_nextSelections = nextSelections).push.apply(_nextSelections, (0, _toConsumableArray2["default"])(conditionSelections));
172 }
173 } else if (selection.kind === 'LinkedField' || selection.kind === 'ScalarField' || selection.kind === 'MatchField') {
174 nextSelection = transformField(context, fragments, scope, selection, errorContext);
175 } else if (selection.kind === 'Defer' || selection.kind === 'Stream') {
176 throw require("./RelayCompilerError").createCompilerError('RelayApplyFragmentArgumentTransform: Expected to be applied before processing @defer/@stream.', [selection.loc]);
177 } else {
178 selection;
179 throw require("./RelayCompilerError").createCompilerError("RelayApplyFragmentArgumentTransform: Unsupported kind '".concat(selection.kind, "'."), [selection.loc]);
180 }
181
182 if (nextSelection) {
183 nextSelections = nextSelections || [];
184 nextSelections.push(nextSelection);
185 }
186 });
187 return nextSelections;
188}
189
190function transformDirectives(scope, directives, errorContext) {
191 return directives.map(function (directive) {
192 var args = transformArguments(scope, directive.args, errorContext);
193 return (0, _objectSpread2["default"])({}, directive, {
194 args: args
195 });
196 });
197}
198
199function transformArguments(scope, args, errorContext) {
200 return args.map(function (arg) {
201 var value = transformValue(scope, arg.value, errorContext);
202 return value === arg.value ? arg : (0, _objectSpread2["default"])({}, arg, {
203 value: value
204 });
205 });
206}
207
208function transformValue(scope, value, errorContext) {
209 if (value.kind === 'Variable') {
210 var scopeValue = scope[value.variableName];
211
212 if (scopeValue == null) {
213 // This transform does whole-program optimization, errors in
214 // a single document could break invariants and/or cause
215 // additional spurious errors.
216 throw require("./RelayCompilerError").createNonRecoverableUserError("Variable '$".concat(value.variableName, "' is not in scope."), [value.loc]);
217 }
218
219 return scopeValue;
220 } else if (value.kind === 'ListValue') {
221 return (0, _objectSpread2["default"])({}, value, {
222 items: value.items.map(function (item) {
223 return transformValue(scope, item, errorContext);
224 })
225 });
226 } else if (value.kind === 'ObjectValue') {
227 return (0, _objectSpread2["default"])({}, value, {
228 fields: value.fields.map(function (field) {
229 return (0, _objectSpread2["default"])({}, field, {
230 value: transformValue(scope, field.value, errorContext)
231 });
232 })
233 });
234 }
235
236 return value;
237}
238/**
239 * Apply arguments to a fragment, creating a new fragment (with the given name)
240 * with all values recursively applied.
241 */
242
243
244function transformFragment(context, fragments, parentScope, spread, args, errorContext) {
245 var fragment = context.getFragment(spread.name);
246 var argumentsHash = hashArguments(args, parentScope, errorContext);
247 var fragmentName = argumentsHash ? "".concat(fragment.name, "_").concat(argumentsHash) : fragment.name;
248 var appliedFragment = fragments.get(fragmentName);
249
250 if (appliedFragment) {
251 return appliedFragment;
252 }
253
254 var fragmentScope = require("./RelayCompilerScope").getFragmentScope(fragment.argumentDefinitions, args, parentScope, spread);
255
256 if (fragments.get(fragmentName) === null) {
257 // This transform does whole-program optimization, errors in
258 // a single document could break invariants and/or cause
259 // additional spurious errors.
260 throw require("./RelayCompilerError").createNonRecoverableUserError("Found a circular reference from fragment '".concat(fragment.name, "'."), errorContext.map(function (node) {
261 return node.loc;
262 }));
263 }
264
265 fragments.set(fragmentName, null); // to detect circular references
266
267 var transformedFragment = null;
268 var selections = transformSelections(context, fragments, fragmentScope, fragment.selections, errorContext);
269
270 if (selections) {
271 transformedFragment = (0, _objectSpread2["default"])({}, fragment, {
272 selections: selections,
273 name: fragmentName,
274 argumentDefinitions: []
275 });
276 }
277
278 fragments.set(fragmentName, transformedFragment);
279 return transformedFragment;
280}
281
282function hashArguments(args, scope, errorContext) {
283 if (!args.length) {
284 return null;
285 }
286
287 var sortedArgs = (0, _toConsumableArray2["default"])(args).sort(function (a, b) {
288 return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
289 });
290 var printedArgs = JSON.stringify(sortedArgs.map(function (arg) {
291 var value;
292
293 if (arg.value.kind === 'Variable') {
294 value = scope[arg.value.variableName];
295
296 if (value == null) {
297 // This transform does whole-program optimization, errors in
298 // a single document could break invariants and/or cause
299 // additional spurious errors.
300 throw require("./RelayCompilerError").createNonRecoverableUserError("Variable '$".concat(arg.value.variableName, "' is not in scope."), [arg.value.loc]);
301 }
302 } else {
303 value = arg.value;
304 }
305
306 return {
307 name: arg.name,
308 value: require("./getIdentifierForArgumentValue")(value)
309 };
310 }));
311 return require("./murmurHash")(printedArgs);
312}
313
314module.exports = {
315 transform: relayApplyFragmentArgumentTransform
316};
\No newline at end of file