UNPKG

24.8 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.OverlappingFieldsCanBeMergedRule = OverlappingFieldsCanBeMergedRule;
7
8var _find = _interopRequireDefault(require("../../polyfills/find.js"));
9
10var _objectEntries3 = _interopRequireDefault(require("../../polyfills/objectEntries.js"));
11
12var _inspect = _interopRequireDefault(require("../../jsutils/inspect.js"));
13
14var _GraphQLError = require("../../error/GraphQLError.js");
15
16var _kinds = require("../../language/kinds.js");
17
18var _printer = require("../../language/printer.js");
19
20var _definition = require("../../type/definition.js");
21
22var _typeFromAST = require("../../utilities/typeFromAST.js");
23
24function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
25
26function reasonMessage(reason) {
27 if (Array.isArray(reason)) {
28 return reason.map(function (_ref) {
29 var responseName = _ref[0],
30 subReason = _ref[1];
31 return "subfields \"".concat(responseName, "\" conflict because ") + reasonMessage(subReason);
32 }).join(' and ');
33 }
34
35 return reason;
36}
37/**
38 * Overlapping fields can be merged
39 *
40 * A selection set is only valid if all fields (including spreading any
41 * fragments) either correspond to distinct response names or can be merged
42 * without ambiguity.
43 */
44
45
46function OverlappingFieldsCanBeMergedRule(context) {
47 // A memoization for when two fragments are compared "between" each other for
48 // conflicts. Two fragments may be compared many times, so memoizing this can
49 // dramatically improve the performance of this validator.
50 var comparedFragmentPairs = new PairSet(); // A cache for the "field map" and list of fragment names found in any given
51 // selection set. Selection sets may be asked for this information multiple
52 // times, so this improves the performance of this validator.
53
54 var cachedFieldsAndFragmentNames = new Map();
55 return {
56 SelectionSet: function SelectionSet(selectionSet) {
57 var conflicts = findConflictsWithinSelectionSet(context, cachedFieldsAndFragmentNames, comparedFragmentPairs, context.getParentType(), selectionSet);
58
59 for (var _i2 = 0; _i2 < conflicts.length; _i2++) {
60 var _ref3 = conflicts[_i2];
61 var _ref2$ = _ref3[0];
62 var responseName = _ref2$[0];
63 var reason = _ref2$[1];
64 var fields1 = _ref3[1];
65 var fields2 = _ref3[2];
66 var reasonMsg = reasonMessage(reason);
67 context.reportError(new _GraphQLError.GraphQLError("Fields \"".concat(responseName, "\" conflict because ").concat(reasonMsg, ". Use different aliases on the fields to fetch both if this was intentional."), fields1.concat(fields2)));
68 }
69 }
70 };
71}
72
73/**
74 * Algorithm:
75 *
76 * Conflicts occur when two fields exist in a query which will produce the same
77 * response name, but represent differing values, thus creating a conflict.
78 * The algorithm below finds all conflicts via making a series of comparisons
79 * between fields. In order to compare as few fields as possible, this makes
80 * a series of comparisons "within" sets of fields and "between" sets of fields.
81 *
82 * Given any selection set, a collection produces both a set of fields by
83 * also including all inline fragments, as well as a list of fragments
84 * referenced by fragment spreads.
85 *
86 * A) Each selection set represented in the document first compares "within" its
87 * collected set of fields, finding any conflicts between every pair of
88 * overlapping fields.
89 * Note: This is the *only time* that a the fields "within" a set are compared
90 * to each other. After this only fields "between" sets are compared.
91 *
92 * B) Also, if any fragment is referenced in a selection set, then a
93 * comparison is made "between" the original set of fields and the
94 * referenced fragment.
95 *
96 * C) Also, if multiple fragments are referenced, then comparisons
97 * are made "between" each referenced fragment.
98 *
99 * D) When comparing "between" a set of fields and a referenced fragment, first
100 * a comparison is made between each field in the original set of fields and
101 * each field in the the referenced set of fields.
102 *
103 * E) Also, if any fragment is referenced in the referenced selection set,
104 * then a comparison is made "between" the original set of fields and the
105 * referenced fragment (recursively referring to step D).
106 *
107 * F) When comparing "between" two fragments, first a comparison is made between
108 * each field in the first referenced set of fields and each field in the the
109 * second referenced set of fields.
110 *
111 * G) Also, any fragments referenced by the first must be compared to the
112 * second, and any fragments referenced by the second must be compared to the
113 * first (recursively referring to step F).
114 *
115 * H) When comparing two fields, if both have selection sets, then a comparison
116 * is made "between" both selection sets, first comparing the set of fields in
117 * the first selection set with the set of fields in the second.
118 *
119 * I) Also, if any fragment is referenced in either selection set, then a
120 * comparison is made "between" the other set of fields and the
121 * referenced fragment.
122 *
123 * J) Also, if two fragments are referenced in both selection sets, then a
124 * comparison is made "between" the two fragments.
125 *
126 */
127// Find all conflicts found "within" a selection set, including those found
128// via spreading in fragments. Called when visiting each SelectionSet in the
129// GraphQL Document.
130function findConflictsWithinSelectionSet(context, cachedFieldsAndFragmentNames, comparedFragmentPairs, parentType, selectionSet) {
131 var conflicts = [];
132
133 var _getFieldsAndFragment = getFieldsAndFragmentNames(context, cachedFieldsAndFragmentNames, parentType, selectionSet),
134 fieldMap = _getFieldsAndFragment[0],
135 fragmentNames = _getFieldsAndFragment[1]; // (A) Find find all conflicts "within" the fields of this selection set.
136 // Note: this is the *only place* `collectConflictsWithin` is called.
137
138
139 collectConflictsWithin(context, conflicts, cachedFieldsAndFragmentNames, comparedFragmentPairs, fieldMap);
140
141 if (fragmentNames.length !== 0) {
142 // (B) Then collect conflicts between these fields and those represented by
143 // each spread fragment name found.
144 for (var i = 0; i < fragmentNames.length; i++) {
145 collectConflictsBetweenFieldsAndFragment(context, conflicts, cachedFieldsAndFragmentNames, comparedFragmentPairs, false, fieldMap, fragmentNames[i]); // (C) Then compare this fragment with all other fragments found in this
146 // selection set to collect conflicts between fragments spread together.
147 // This compares each item in the list of fragment names to every other
148 // item in that same list (except for itself).
149
150 for (var j = i + 1; j < fragmentNames.length; j++) {
151 collectConflictsBetweenFragments(context, conflicts, cachedFieldsAndFragmentNames, comparedFragmentPairs, false, fragmentNames[i], fragmentNames[j]);
152 }
153 }
154 }
155
156 return conflicts;
157} // Collect all conflicts found between a set of fields and a fragment reference
158// including via spreading in any nested fragments.
159
160
161function collectConflictsBetweenFieldsAndFragment(context, conflicts, cachedFieldsAndFragmentNames, comparedFragmentPairs, areMutuallyExclusive, fieldMap, fragmentName) {
162 var fragment = context.getFragment(fragmentName);
163
164 if (!fragment) {
165 return;
166 }
167
168 var _getReferencedFieldsA = getReferencedFieldsAndFragmentNames(context, cachedFieldsAndFragmentNames, fragment),
169 fieldMap2 = _getReferencedFieldsA[0],
170 fragmentNames2 = _getReferencedFieldsA[1]; // Do not compare a fragment's fieldMap to itself.
171
172
173 if (fieldMap === fieldMap2) {
174 return;
175 } // (D) First collect any conflicts between the provided collection of fields
176 // and the collection of fields represented by the given fragment.
177
178
179 collectConflictsBetween(context, conflicts, cachedFieldsAndFragmentNames, comparedFragmentPairs, areMutuallyExclusive, fieldMap, fieldMap2); // (E) Then collect any conflicts between the provided collection of fields
180 // and any fragment names found in the given fragment.
181
182 for (var i = 0; i < fragmentNames2.length; i++) {
183 collectConflictsBetweenFieldsAndFragment(context, conflicts, cachedFieldsAndFragmentNames, comparedFragmentPairs, areMutuallyExclusive, fieldMap, fragmentNames2[i]);
184 }
185} // Collect all conflicts found between two fragments, including via spreading in
186// any nested fragments.
187
188
189function collectConflictsBetweenFragments(context, conflicts, cachedFieldsAndFragmentNames, comparedFragmentPairs, areMutuallyExclusive, fragmentName1, fragmentName2) {
190 // No need to compare a fragment to itself.
191 if (fragmentName1 === fragmentName2) {
192 return;
193 } // Memoize so two fragments are not compared for conflicts more than once.
194
195
196 if (comparedFragmentPairs.has(fragmentName1, fragmentName2, areMutuallyExclusive)) {
197 return;
198 }
199
200 comparedFragmentPairs.add(fragmentName1, fragmentName2, areMutuallyExclusive);
201 var fragment1 = context.getFragment(fragmentName1);
202 var fragment2 = context.getFragment(fragmentName2);
203
204 if (!fragment1 || !fragment2) {
205 return;
206 }
207
208 var _getReferencedFieldsA2 = getReferencedFieldsAndFragmentNames(context, cachedFieldsAndFragmentNames, fragment1),
209 fieldMap1 = _getReferencedFieldsA2[0],
210 fragmentNames1 = _getReferencedFieldsA2[1];
211
212 var _getReferencedFieldsA3 = getReferencedFieldsAndFragmentNames(context, cachedFieldsAndFragmentNames, fragment2),
213 fieldMap2 = _getReferencedFieldsA3[0],
214 fragmentNames2 = _getReferencedFieldsA3[1]; // (F) First, collect all conflicts between these two collections of fields
215 // (not including any nested fragments).
216
217
218 collectConflictsBetween(context, conflicts, cachedFieldsAndFragmentNames, comparedFragmentPairs, areMutuallyExclusive, fieldMap1, fieldMap2); // (G) Then collect conflicts between the first fragment and any nested
219 // fragments spread in the second fragment.
220
221 for (var j = 0; j < fragmentNames2.length; j++) {
222 collectConflictsBetweenFragments(context, conflicts, cachedFieldsAndFragmentNames, comparedFragmentPairs, areMutuallyExclusive, fragmentName1, fragmentNames2[j]);
223 } // (G) Then collect conflicts between the second fragment and any nested
224 // fragments spread in the first fragment.
225
226
227 for (var i = 0; i < fragmentNames1.length; i++) {
228 collectConflictsBetweenFragments(context, conflicts, cachedFieldsAndFragmentNames, comparedFragmentPairs, areMutuallyExclusive, fragmentNames1[i], fragmentName2);
229 }
230} // Find all conflicts found between two selection sets, including those found
231// via spreading in fragments. Called when determining if conflicts exist
232// between the sub-fields of two overlapping fields.
233
234
235function findConflictsBetweenSubSelectionSets(context, cachedFieldsAndFragmentNames, comparedFragmentPairs, areMutuallyExclusive, parentType1, selectionSet1, parentType2, selectionSet2) {
236 var conflicts = [];
237
238 var _getFieldsAndFragment2 = getFieldsAndFragmentNames(context, cachedFieldsAndFragmentNames, parentType1, selectionSet1),
239 fieldMap1 = _getFieldsAndFragment2[0],
240 fragmentNames1 = _getFieldsAndFragment2[1];
241
242 var _getFieldsAndFragment3 = getFieldsAndFragmentNames(context, cachedFieldsAndFragmentNames, parentType2, selectionSet2),
243 fieldMap2 = _getFieldsAndFragment3[0],
244 fragmentNames2 = _getFieldsAndFragment3[1]; // (H) First, collect all conflicts between these two collections of field.
245
246
247 collectConflictsBetween(context, conflicts, cachedFieldsAndFragmentNames, comparedFragmentPairs, areMutuallyExclusive, fieldMap1, fieldMap2); // (I) Then collect conflicts between the first collection of fields and
248 // those referenced by each fragment name associated with the second.
249
250 if (fragmentNames2.length !== 0) {
251 for (var j = 0; j < fragmentNames2.length; j++) {
252 collectConflictsBetweenFieldsAndFragment(context, conflicts, cachedFieldsAndFragmentNames, comparedFragmentPairs, areMutuallyExclusive, fieldMap1, fragmentNames2[j]);
253 }
254 } // (I) Then collect conflicts between the second collection of fields and
255 // those referenced by each fragment name associated with the first.
256
257
258 if (fragmentNames1.length !== 0) {
259 for (var i = 0; i < fragmentNames1.length; i++) {
260 collectConflictsBetweenFieldsAndFragment(context, conflicts, cachedFieldsAndFragmentNames, comparedFragmentPairs, areMutuallyExclusive, fieldMap2, fragmentNames1[i]);
261 }
262 } // (J) Also collect conflicts between any fragment names by the first and
263 // fragment names by the second. This compares each item in the first set of
264 // names to each item in the second set of names.
265
266
267 for (var _i3 = 0; _i3 < fragmentNames1.length; _i3++) {
268 for (var _j = 0; _j < fragmentNames2.length; _j++) {
269 collectConflictsBetweenFragments(context, conflicts, cachedFieldsAndFragmentNames, comparedFragmentPairs, areMutuallyExclusive, fragmentNames1[_i3], fragmentNames2[_j]);
270 }
271 }
272
273 return conflicts;
274} // Collect all Conflicts "within" one collection of fields.
275
276
277function collectConflictsWithin(context, conflicts, cachedFieldsAndFragmentNames, comparedFragmentPairs, fieldMap) {
278 // A field map is a keyed collection, where each key represents a response
279 // name and the value at that key is a list of all fields which provide that
280 // response name. For every response name, if there are multiple fields, they
281 // must be compared to find a potential conflict.
282 for (var _i5 = 0, _objectEntries2 = (0, _objectEntries3.default)(fieldMap); _i5 < _objectEntries2.length; _i5++) {
283 var _ref5 = _objectEntries2[_i5];
284 var responseName = _ref5[0];
285 var fields = _ref5[1];
286
287 // This compares every field in the list to every other field in this list
288 // (except to itself). If the list only has one item, nothing needs to
289 // be compared.
290 if (fields.length > 1) {
291 for (var i = 0; i < fields.length; i++) {
292 for (var j = i + 1; j < fields.length; j++) {
293 var conflict = findConflict(context, cachedFieldsAndFragmentNames, comparedFragmentPairs, false, // within one collection is never mutually exclusive
294 responseName, fields[i], fields[j]);
295
296 if (conflict) {
297 conflicts.push(conflict);
298 }
299 }
300 }
301 }
302 }
303} // Collect all Conflicts between two collections of fields. This is similar to,
304// but different from the `collectConflictsWithin` function above. This check
305// assumes that `collectConflictsWithin` has already been called on each
306// provided collection of fields. This is true because this validator traverses
307// each individual selection set.
308
309
310function collectConflictsBetween(context, conflicts, cachedFieldsAndFragmentNames, comparedFragmentPairs, parentFieldsAreMutuallyExclusive, fieldMap1, fieldMap2) {
311 // A field map is a keyed collection, where each key represents a response
312 // name and the value at that key is a list of all fields which provide that
313 // response name. For any response name which appears in both provided field
314 // maps, each field from the first field map must be compared to every field
315 // in the second field map to find potential conflicts.
316 for (var _i7 = 0, _Object$keys2 = Object.keys(fieldMap1); _i7 < _Object$keys2.length; _i7++) {
317 var responseName = _Object$keys2[_i7];
318 var fields2 = fieldMap2[responseName];
319
320 if (fields2) {
321 var fields1 = fieldMap1[responseName];
322
323 for (var i = 0; i < fields1.length; i++) {
324 for (var j = 0; j < fields2.length; j++) {
325 var conflict = findConflict(context, cachedFieldsAndFragmentNames, comparedFragmentPairs, parentFieldsAreMutuallyExclusive, responseName, fields1[i], fields2[j]);
326
327 if (conflict) {
328 conflicts.push(conflict);
329 }
330 }
331 }
332 }
333 }
334} // Determines if there is a conflict between two particular fields, including
335// comparing their sub-fields.
336
337
338function findConflict(context, cachedFieldsAndFragmentNames, comparedFragmentPairs, parentFieldsAreMutuallyExclusive, responseName, field1, field2) {
339 var parentType1 = field1[0],
340 node1 = field1[1],
341 def1 = field1[2];
342 var parentType2 = field2[0],
343 node2 = field2[1],
344 def2 = field2[2]; // If it is known that two fields could not possibly apply at the same
345 // time, due to the parent types, then it is safe to permit them to diverge
346 // in aliased field or arguments used as they will not present any ambiguity
347 // by differing.
348 // It is known that two parent types could never overlap if they are
349 // different Object types. Interface or Union types might overlap - if not
350 // in the current state of the schema, then perhaps in some future version,
351 // thus may not safely diverge.
352
353 var areMutuallyExclusive = parentFieldsAreMutuallyExclusive || parentType1 !== parentType2 && (0, _definition.isObjectType)(parentType1) && (0, _definition.isObjectType)(parentType2);
354
355 if (!areMutuallyExclusive) {
356 var _node1$arguments, _node2$arguments;
357
358 // Two aliases must refer to the same field.
359 var name1 = node1.name.value;
360 var name2 = node2.name.value;
361
362 if (name1 !== name2) {
363 return [[responseName, "\"".concat(name1, "\" and \"").concat(name2, "\" are different fields")], [node1], [node2]];
364 } // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203')
365
366
367 var args1 = (_node1$arguments = node1.arguments) !== null && _node1$arguments !== void 0 ? _node1$arguments : []; // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203')
368
369 var args2 = (_node2$arguments = node2.arguments) !== null && _node2$arguments !== void 0 ? _node2$arguments : []; // Two field calls must have the same arguments.
370
371 if (!sameArguments(args1, args2)) {
372 return [[responseName, 'they have differing arguments'], [node1], [node2]];
373 }
374 } // The return type for each field.
375
376
377 var type1 = def1 === null || def1 === void 0 ? void 0 : def1.type;
378 var type2 = def2 === null || def2 === void 0 ? void 0 : def2.type;
379
380 if (type1 && type2 && doTypesConflict(type1, type2)) {
381 return [[responseName, "they return conflicting types \"".concat((0, _inspect.default)(type1), "\" and \"").concat((0, _inspect.default)(type2), "\"")], [node1], [node2]];
382 } // Collect and compare sub-fields. Use the same "visited fragment names" list
383 // for both collections so fields in a fragment reference are never
384 // compared to themselves.
385
386
387 var selectionSet1 = node1.selectionSet;
388 var selectionSet2 = node2.selectionSet;
389
390 if (selectionSet1 && selectionSet2) {
391 var conflicts = findConflictsBetweenSubSelectionSets(context, cachedFieldsAndFragmentNames, comparedFragmentPairs, areMutuallyExclusive, (0, _definition.getNamedType)(type1), selectionSet1, (0, _definition.getNamedType)(type2), selectionSet2);
392 return subfieldConflicts(conflicts, responseName, node1, node2);
393 }
394}
395
396function sameArguments(arguments1, arguments2) {
397 if (arguments1.length !== arguments2.length) {
398 return false;
399 }
400
401 return arguments1.every(function (argument1) {
402 var argument2 = (0, _find.default)(arguments2, function (argument) {
403 return argument.name.value === argument1.name.value;
404 });
405
406 if (!argument2) {
407 return false;
408 }
409
410 return sameValue(argument1.value, argument2.value);
411 });
412}
413
414function sameValue(value1, value2) {
415 return (0, _printer.print)(value1) === (0, _printer.print)(value2);
416} // Two types conflict if both types could not apply to a value simultaneously.
417// Composite types are ignored as their individual field types will be compared
418// later recursively. However List and Non-Null types must match.
419
420
421function doTypesConflict(type1, type2) {
422 if ((0, _definition.isListType)(type1)) {
423 return (0, _definition.isListType)(type2) ? doTypesConflict(type1.ofType, type2.ofType) : true;
424 }
425
426 if ((0, _definition.isListType)(type2)) {
427 return true;
428 }
429
430 if ((0, _definition.isNonNullType)(type1)) {
431 return (0, _definition.isNonNullType)(type2) ? doTypesConflict(type1.ofType, type2.ofType) : true;
432 }
433
434 if ((0, _definition.isNonNullType)(type2)) {
435 return true;
436 }
437
438 if ((0, _definition.isLeafType)(type1) || (0, _definition.isLeafType)(type2)) {
439 return type1 !== type2;
440 }
441
442 return false;
443} // Given a selection set, return the collection of fields (a mapping of response
444// name to field nodes and definitions) as well as a list of fragment names
445// referenced via fragment spreads.
446
447
448function getFieldsAndFragmentNames(context, cachedFieldsAndFragmentNames, parentType, selectionSet) {
449 var cached = cachedFieldsAndFragmentNames.get(selectionSet);
450
451 if (!cached) {
452 var nodeAndDefs = Object.create(null);
453 var fragmentNames = Object.create(null);
454
455 _collectFieldsAndFragmentNames(context, parentType, selectionSet, nodeAndDefs, fragmentNames);
456
457 cached = [nodeAndDefs, Object.keys(fragmentNames)];
458 cachedFieldsAndFragmentNames.set(selectionSet, cached);
459 }
460
461 return cached;
462} // Given a reference to a fragment, return the represented collection of fields
463// as well as a list of nested fragment names referenced via fragment spreads.
464
465
466function getReferencedFieldsAndFragmentNames(context, cachedFieldsAndFragmentNames, fragment) {
467 // Short-circuit building a type from the node if possible.
468 var cached = cachedFieldsAndFragmentNames.get(fragment.selectionSet);
469
470 if (cached) {
471 return cached;
472 }
473
474 var fragmentType = (0, _typeFromAST.typeFromAST)(context.getSchema(), fragment.typeCondition);
475 return getFieldsAndFragmentNames(context, cachedFieldsAndFragmentNames, fragmentType, fragment.selectionSet);
476}
477
478function _collectFieldsAndFragmentNames(context, parentType, selectionSet, nodeAndDefs, fragmentNames) {
479 for (var _i9 = 0, _selectionSet$selecti2 = selectionSet.selections; _i9 < _selectionSet$selecti2.length; _i9++) {
480 var selection = _selectionSet$selecti2[_i9];
481
482 switch (selection.kind) {
483 case _kinds.Kind.FIELD:
484 {
485 var fieldName = selection.name.value;
486 var fieldDef = void 0;
487
488 if ((0, _definition.isObjectType)(parentType) || (0, _definition.isInterfaceType)(parentType)) {
489 fieldDef = parentType.getFields()[fieldName];
490 }
491
492 var responseName = selection.alias ? selection.alias.value : fieldName;
493
494 if (!nodeAndDefs[responseName]) {
495 nodeAndDefs[responseName] = [];
496 }
497
498 nodeAndDefs[responseName].push([parentType, selection, fieldDef]);
499 break;
500 }
501
502 case _kinds.Kind.FRAGMENT_SPREAD:
503 fragmentNames[selection.name.value] = true;
504 break;
505
506 case _kinds.Kind.INLINE_FRAGMENT:
507 {
508 var typeCondition = selection.typeCondition;
509 var inlineFragmentType = typeCondition ? (0, _typeFromAST.typeFromAST)(context.getSchema(), typeCondition) : parentType;
510
511 _collectFieldsAndFragmentNames(context, inlineFragmentType, selection.selectionSet, nodeAndDefs, fragmentNames);
512
513 break;
514 }
515 }
516 }
517} // Given a series of Conflicts which occurred between two sub-fields, generate
518// a single Conflict.
519
520
521function subfieldConflicts(conflicts, responseName, node1, node2) {
522 if (conflicts.length > 0) {
523 return [[responseName, conflicts.map(function (_ref6) {
524 var reason = _ref6[0];
525 return reason;
526 })], conflicts.reduce(function (allFields, _ref7) {
527 var fields1 = _ref7[1];
528 return allFields.concat(fields1);
529 }, [node1]), conflicts.reduce(function (allFields, _ref8) {
530 var fields2 = _ref8[2];
531 return allFields.concat(fields2);
532 }, [node2])];
533 }
534}
535/**
536 * A way to keep track of pairs of things when the ordering of the pair does
537 * not matter. We do this by maintaining a sort of double adjacency sets.
538 */
539
540
541var PairSet = /*#__PURE__*/function () {
542 function PairSet() {
543 this._data = Object.create(null);
544 }
545
546 var _proto = PairSet.prototype;
547
548 _proto.has = function has(a, b, areMutuallyExclusive) {
549 var first = this._data[a];
550 var result = first && first[b];
551
552 if (result === undefined) {
553 return false;
554 } // areMutuallyExclusive being false is a superset of being true,
555 // hence if we want to know if this PairSet "has" these two with no
556 // exclusivity, we have to ensure it was added as such.
557
558
559 if (areMutuallyExclusive === false) {
560 return result === false;
561 }
562
563 return true;
564 };
565
566 _proto.add = function add(a, b, areMutuallyExclusive) {
567 this._pairSetAdd(a, b, areMutuallyExclusive);
568
569 this._pairSetAdd(b, a, areMutuallyExclusive);
570 };
571
572 _proto._pairSetAdd = function _pairSetAdd(a, b, areMutuallyExclusive) {
573 var map = this._data[a];
574
575 if (!map) {
576 map = Object.create(null);
577 this._data[a] = map;
578 }
579
580 map[b] = areMutuallyExclusive;
581 };
582
583 return PairSet;
584}();