UNPKG

9.74 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 * @format
8 *
9 */
10'use strict';
11
12var _objectSpread2 = require("@babel/runtime/helpers/interopRequireDefault")(require("@babel/runtime/helpers/objectSpread"));
13
14/**
15 * Transform that flattens inline fragments, fragment spreads, and conditionals.
16 *
17 * Inline fragments are inlined (replaced with their selections) when:
18 * - The fragment type matches the type of its parent.
19 * - The fragment has an abstract type and the `flattenAbstractTypes` option has
20 * been set.
21 * - The 'flattenInlineFragments' option has been set.
22 */
23function flattenTransformImpl(context, options) {
24 var state = {
25 flattenAbstractTypes: !!(options && options.flattenAbstractTypes),
26 flattenInlineFragments: !!(options && options.flattenInlineFragments),
27 parentType: null
28 };
29 return require("./GraphQLIRTransformer").transform(context, {
30 Root: flattenSelections,
31 Fragment: flattenSelections,
32 Condition: flattenSelections,
33 InlineFragment: flattenSelections,
34 LinkedField: flattenSelections,
35 MatchField: flattenSelections
36 }, function () {
37 return state;
38 });
39}
40/**
41 * @private
42 */
43
44
45function flattenSelections(node, state) {
46 // Determine the current type.
47 var parentType = state.parentType;
48 var type = node.kind === 'Condition' || node.kind === 'Defer' || node.kind === 'Stream' ? parentType : node.kind === 'InlineFragment' ? node.typeCondition : node.type;
49
50 if (type == null) {
51 throw require("./RelayCompilerError").createCompilerError('FlattenTransform: Expected a parent type.', [node.loc]);
52 } // Flatten the selections in this node, creating a new node with flattened
53 // selections if possible, then deeply traverse the flattened node, while
54 // keeping track of the parent type.
55
56
57 var nextSelections = new Map();
58 var hasFlattened = flattenSelectionsInto(nextSelections, node, state, type);
59 var flattenedNode = hasFlattened ? (0, _objectSpread2["default"])({}, node, {
60 selections: Array.from(nextSelections.values())
61 }) : node;
62 state.parentType = type;
63 var deeplyFlattenedNode = this.traverse(flattenedNode, state);
64 state.parentType = parentType;
65 return deeplyFlattenedNode;
66}
67/**
68 * @private
69 */
70
71
72function flattenSelectionsInto(flattenedSelections, node, state, type) {
73 var hasFlattened = false;
74 node.selections.forEach(function (selection) {
75 if (selection.kind === 'InlineFragment' && shouldFlattenInlineFragment(selection, state, type)) {
76 hasFlattened = true;
77 flattenSelectionsInto(flattenedSelections, selection, state, type);
78 return;
79 }
80
81 var nodeIdentifier = require("./getIdentifierForSelection")(selection);
82
83 var flattenedSelection = flattenedSelections.get(nodeIdentifier); // If this selection hasn't been seen before, keep track of it.
84
85 if (!flattenedSelection) {
86 flattenedSelections.set(nodeIdentifier, selection);
87 return;
88 } // Otherwise a similar selection exists which should be merged.
89
90
91 hasFlattened = true;
92
93 if (flattenedSelection.kind === 'InlineFragment') {
94 if (selection.kind !== 'InlineFragment') {
95 throw require("./RelayCompilerError").createCompilerError("FlattenTransform: Expected an InlineFragment, got a '".concat(selection.kind, "'"), [selection.loc]);
96 }
97
98 flattenedSelections.set(nodeIdentifier, (0, _objectSpread2["default"])({}, flattenedSelection, {
99 selections: mergeSelections(flattenedSelection, selection, state, selection.typeCondition)
100 }));
101 } else if (flattenedSelection.kind === 'Condition') {
102 if (selection.kind !== 'Condition') {
103 throw require("./RelayCompilerError").createCompilerError("FlattenTransform: Expected a Condition, got a '".concat(selection.kind, "'"), [selection.loc]);
104 }
105
106 flattenedSelections.set(nodeIdentifier, (0, _objectSpread2["default"])({}, flattenedSelection, {
107 selections: mergeSelections(flattenedSelection, selection, state, type)
108 }));
109 } else if (flattenedSelection.kind === 'FragmentSpread') {// Ignore duplicate fragment spreads.
110 } else if (flattenedSelection.kind === 'MatchField' || flattenedSelection.kind === 'MatchBranch') {// Ignore duplicate matches that select the same fragments and
111 // modules (encoded in the identifier)
112 // Also ignore incremental data placeholders
113 } else if (flattenedSelection.kind === 'Defer') {
114 if (selection.kind !== 'Defer') {
115 throw require("./RelayCompilerError").createCompilerError("FlattenTransform: Expected a Defer, got a '".concat(selection.kind, "'"), [selection.loc]);
116 }
117
118 flattenedSelections.set(nodeIdentifier, (0, _objectSpread2["default"])({
119 kind: 'Defer'
120 }, flattenedSelection, {
121 selections: mergeSelections(flattenedSelection, selection, state, type)
122 }));
123 } else if (flattenedSelection.kind === 'Stream') {
124 if (selection.kind !== 'Stream') {
125 throw require("./RelayCompilerError").createCompilerError("FlattenTransform: Expected a Stream, got a '".concat(selection.kind, "'"), [selection.loc]);
126 }
127
128 flattenedSelections.set(nodeIdentifier, (0, _objectSpread2["default"])({
129 kind: 'Stream'
130 }, flattenedSelection, {
131 selections: mergeSelections(flattenedSelection, selection, state, type)
132 }));
133 } else if (flattenedSelection.kind === 'LinkedField') {
134 if (selection.kind !== 'LinkedField') {
135 throw require("./RelayCompilerError").createCompilerError("FlattenTransform: Expected a LinkedField, got a '".concat(selection.kind, "'"), [selection.loc]);
136 } // Note: arguments are intentionally reversed to avoid rebuilds
137
138
139 assertUniqueArgsForAlias(selection, flattenedSelection);
140 flattenedSelections.set(nodeIdentifier, (0, _objectSpread2["default"])({
141 kind: 'LinkedField'
142 }, flattenedSelection, {
143 handles: mergeHandles(flattenedSelection, selection),
144 selections: mergeSelections(flattenedSelection, selection, state, selection.type)
145 }));
146 } else if (flattenedSelection.kind === 'ScalarField') {
147 if (selection.kind !== 'ScalarField') {
148 throw require("./RelayCompilerError").createCompilerError("FlattenTransform: Expected a ScalarField, got a '".concat(selection.kind, "'"), [selection.loc]);
149 } // Note: arguments are intentionally reversed to avoid rebuilds
150
151
152 assertUniqueArgsForAlias(selection, flattenedSelection);
153 flattenedSelections.set(nodeIdentifier, (0, _objectSpread2["default"])({
154 kind: 'ScalarField'
155 }, flattenedSelection, {
156 // Note: arguments are intentionally reversed to avoid rebuilds
157 handles: mergeHandles(selection, flattenedSelection)
158 }));
159 } else {
160 flattenedSelection.kind;
161 throw require("./RelayCompilerError").createCompilerError("FlattenTransform: Unknown kind '".concat(flattenedSelection.kind, "'"));
162 }
163 });
164 return hasFlattened;
165}
166/**
167 * @private
168 */
169
170
171function mergeSelections(nodeA, nodeB, state, type) {
172 var flattenedSelections = new Map();
173 flattenSelectionsInto(flattenedSelections, nodeA, state, type);
174 flattenSelectionsInto(flattenedSelections, nodeB, state, type);
175 return Array.from(flattenedSelections.values());
176}
177/**
178 * @private
179 * TODO(T19327202) This is redundant with OverlappingFieldsCanBeMergedRule once
180 * it can be enabled.
181 */
182
183
184function assertUniqueArgsForAlias(field, otherField) {
185 if (!areEqualFields(field, otherField)) {
186 var _field$alias;
187
188 throw require("./RelayCompilerError").createUserError('Expected all fields on the same parent with ' + "the name or alias '".concat((_field$alias = field.alias) !== null && _field$alias !== void 0 ? _field$alias : field.name, "' to have the same name and arguments."), [field.loc, otherField.loc]);
189 }
190}
191/**
192 * @private
193 */
194
195
196function shouldFlattenInlineFragment(fragment, state, type) {
197 return state.flattenInlineFragments || fragment.typeCondition.name === require("./GraphQLSchemaUtils").getRawType(type).name || state.flattenAbstractTypes && require("./GraphQLSchemaUtils").isAbstractType(fragment.typeCondition);
198}
199/**
200 * @private
201 *
202 * Verify that two fields are equal in all properties other than their
203 * selections.
204 */
205
206
207function areEqualFields(thisField, thatField) {
208 return thisField.kind === thatField.kind && thisField.name === thatField.name && thisField.alias === thatField.alias && areEqualArgs(thisField.args, thatField.args);
209}
210/**
211 * Verify that two sets of arguments are equivalent - same argument names
212 * and values. Notably this ignores the types of arguments and values, which
213 * may not always be inferred identically.
214 */
215
216
217function areEqualArgs(thisArgs, thatArgs) {
218 return thisArgs.length === thatArgs.length && thisArgs.every(function (thisArg, index) {
219 var thatArg = thatArgs[index];
220 return thisArg.name === thatArg.name && thisArg.value.kind === thatArg.value.kind && thisArg.value.variableName === thatArg.value.variableName && require("./areEqualOSS")(thisArg.value.value, thatArg.value.value);
221 });
222}
223/**
224 * @private
225 */
226
227
228function mergeHandles(nodeA, nodeB) {
229 if (!nodeA.handles) {
230 return nodeB.handles;
231 }
232
233 if (!nodeB.handles) {
234 return nodeA.handles;
235 }
236
237 var uniqueItems = new Map();
238 nodeA.handles.concat(nodeB.handles).forEach(function (item) {
239 return uniqueItems.set(item.name + item.key, item);
240 });
241 return Array.from(uniqueItems.values());
242}
243
244function transformWithOptions(options) {
245 return function flattenTransform(context) {
246 return flattenTransformImpl(context, options);
247 };
248}
249
250module.exports = {
251 transformWithOptions: transformWithOptions
252};
\No newline at end of file