854 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			854 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import { inspect } from '../../jsutils/inspect.mjs';
 | |
| import { GraphQLError } from '../../error/GraphQLError.mjs';
 | |
| import { Kind } from '../../language/kinds.mjs';
 | |
| import { print } from '../../language/printer.mjs';
 | |
| import {
 | |
|   getNamedType,
 | |
|   isInterfaceType,
 | |
|   isLeafType,
 | |
|   isListType,
 | |
|   isNonNullType,
 | |
|   isObjectType,
 | |
| } from '../../type/definition.mjs';
 | |
| import { sortValueNode } from '../../utilities/sortValueNode.mjs';
 | |
| import { typeFromAST } from '../../utilities/typeFromAST.mjs';
 | |
| 
 | |
| function reasonMessage(reason) {
 | |
|   if (Array.isArray(reason)) {
 | |
|     return reason
 | |
|       .map(
 | |
|         ([responseName, subReason]) =>
 | |
|           `subfields "${responseName}" conflict because ` +
 | |
|           reasonMessage(subReason),
 | |
|       )
 | |
|       .join(' and ');
 | |
|   }
 | |
| 
 | |
|   return reason;
 | |
| }
 | |
| /**
 | |
|  * Overlapping fields can be merged
 | |
|  *
 | |
|  * A selection set is only valid if all fields (including spreading any
 | |
|  * fragments) either correspond to distinct response names or can be merged
 | |
|  * without ambiguity.
 | |
|  *
 | |
|  * See https://spec.graphql.org/draft/#sec-Field-Selection-Merging
 | |
|  */
 | |
| 
 | |
| export function OverlappingFieldsCanBeMergedRule(context) {
 | |
|   // A memoization for when fields and a fragment or two fragments are compared
 | |
|   // "between" each other for conflicts. Comparisons made be made many times,
 | |
|   // so memoizing this can dramatically improve the performance of this validator.
 | |
|   const comparedFieldsAndFragmentPairs = new OrderedPairSet();
 | |
|   const comparedFragmentPairs = new PairSet(); // A cache for the "field map" and list of fragment names found in any given
 | |
|   // selection set. Selection sets may be asked for this information multiple
 | |
|   // times, so this improves the performance of this validator.
 | |
| 
 | |
|   const cachedFieldsAndFragmentNames = new Map();
 | |
|   return {
 | |
|     SelectionSet(selectionSet) {
 | |
|       const conflicts = findConflictsWithinSelectionSet(
 | |
|         context,
 | |
|         cachedFieldsAndFragmentNames,
 | |
|         comparedFieldsAndFragmentPairs,
 | |
|         comparedFragmentPairs,
 | |
|         context.getParentType(),
 | |
|         selectionSet,
 | |
|       );
 | |
| 
 | |
|       for (const [[responseName, reason], fields1, fields2] of conflicts) {
 | |
|         const reasonMsg = reasonMessage(reason);
 | |
|         context.reportError(
 | |
|           new GraphQLError(
 | |
|             `Fields "${responseName}" conflict because ${reasonMsg}. Use different aliases on the fields to fetch both if this was intentional.`,
 | |
|             {
 | |
|               nodes: fields1.concat(fields2),
 | |
|             },
 | |
|           ),
 | |
|         );
 | |
|       }
 | |
|     },
 | |
|   };
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Algorithm:
 | |
|  *
 | |
|  * Conflicts occur when two fields exist in a query which will produce the same
 | |
|  * response name, but represent differing values, thus creating a conflict.
 | |
|  * The algorithm below finds all conflicts via making a series of comparisons
 | |
|  * between fields. In order to compare as few fields as possible, this makes
 | |
|  * a series of comparisons "within" sets of fields and "between" sets of fields.
 | |
|  *
 | |
|  * Given any selection set, a collection produces both a set of fields by
 | |
|  * also including all inline fragments, as well as a list of fragments
 | |
|  * referenced by fragment spreads.
 | |
|  *
 | |
|  * A) Each selection set represented in the document first compares "within" its
 | |
|  * collected set of fields, finding any conflicts between every pair of
 | |
|  * overlapping fields.
 | |
|  * Note: This is the *only time* that a the fields "within" a set are compared
 | |
|  * to each other. After this only fields "between" sets are compared.
 | |
|  *
 | |
|  * B) Also, if any fragment is referenced in a selection set, then a
 | |
|  * comparison is made "between" the original set of fields and the
 | |
|  * referenced fragment.
 | |
|  *
 | |
|  * C) Also, if multiple fragments are referenced, then comparisons
 | |
|  * are made "between" each referenced fragment.
 | |
|  *
 | |
|  * D) When comparing "between" a set of fields and a referenced fragment, first
 | |
|  * a comparison is made between each field in the original set of fields and
 | |
|  * each field in the the referenced set of fields.
 | |
|  *
 | |
|  * E) Also, if any fragment is referenced in the referenced selection set,
 | |
|  * then a comparison is made "between" the original set of fields and the
 | |
|  * referenced fragment (recursively referring to step D).
 | |
|  *
 | |
|  * F) When comparing "between" two fragments, first a comparison is made between
 | |
|  * each field in the first referenced set of fields and each field in the the
 | |
|  * second referenced set of fields.
 | |
|  *
 | |
|  * G) Also, any fragments referenced by the first must be compared to the
 | |
|  * second, and any fragments referenced by the second must be compared to the
 | |
|  * first (recursively referring to step F).
 | |
|  *
 | |
|  * H) When comparing two fields, if both have selection sets, then a comparison
 | |
|  * is made "between" both selection sets, first comparing the set of fields in
 | |
|  * the first selection set with the set of fields in the second.
 | |
|  *
 | |
|  * I) Also, if any fragment is referenced in either selection set, then a
 | |
|  * comparison is made "between" the other set of fields and the
 | |
|  * referenced fragment.
 | |
|  *
 | |
|  * J) Also, if two fragments are referenced in both selection sets, then a
 | |
|  * comparison is made "between" the two fragments.
 | |
|  *
 | |
|  */
 | |
| // Find all conflicts found "within" a selection set, including those found
 | |
| // via spreading in fragments. Called when visiting each SelectionSet in the
 | |
| // GraphQL Document.
 | |
| function findConflictsWithinSelectionSet(
 | |
|   context,
 | |
|   cachedFieldsAndFragmentNames,
 | |
|   comparedFieldsAndFragmentPairs,
 | |
|   comparedFragmentPairs,
 | |
|   parentType,
 | |
|   selectionSet,
 | |
| ) {
 | |
|   const conflicts = [];
 | |
|   const [fieldMap, fragmentNames] = getFieldsAndFragmentNames(
 | |
|     context,
 | |
|     cachedFieldsAndFragmentNames,
 | |
|     parentType,
 | |
|     selectionSet,
 | |
|   ); // (A) Find find all conflicts "within" the fields of this selection set.
 | |
|   // Note: this is the *only place* `collectConflictsWithin` is called.
 | |
| 
 | |
|   collectConflictsWithin(
 | |
|     context,
 | |
|     conflicts,
 | |
|     cachedFieldsAndFragmentNames,
 | |
|     comparedFieldsAndFragmentPairs,
 | |
|     comparedFragmentPairs,
 | |
|     fieldMap,
 | |
|   );
 | |
| 
 | |
|   if (fragmentNames.length !== 0) {
 | |
|     // (B) Then collect conflicts between these fields and those represented by
 | |
|     // each spread fragment name found.
 | |
|     for (let i = 0; i < fragmentNames.length; i++) {
 | |
|       collectConflictsBetweenFieldsAndFragment(
 | |
|         context,
 | |
|         conflicts,
 | |
|         cachedFieldsAndFragmentNames,
 | |
|         comparedFieldsAndFragmentPairs,
 | |
|         comparedFragmentPairs,
 | |
|         false,
 | |
|         fieldMap,
 | |
|         fragmentNames[i],
 | |
|       ); // (C) Then compare this fragment with all other fragments found in this
 | |
|       // selection set to collect conflicts between fragments spread together.
 | |
|       // This compares each item in the list of fragment names to every other
 | |
|       // item in that same list (except for itself).
 | |
| 
 | |
|       for (let j = i + 1; j < fragmentNames.length; j++) {
 | |
|         collectConflictsBetweenFragments(
 | |
|           context,
 | |
|           conflicts,
 | |
|           cachedFieldsAndFragmentNames,
 | |
|           comparedFieldsAndFragmentPairs,
 | |
|           comparedFragmentPairs,
 | |
|           false,
 | |
|           fragmentNames[i],
 | |
|           fragmentNames[j],
 | |
|         );
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return conflicts;
 | |
| } // Collect all conflicts found between a set of fields and a fragment reference
 | |
| // including via spreading in any nested fragments.
 | |
| 
 | |
| function collectConflictsBetweenFieldsAndFragment(
 | |
|   context,
 | |
|   conflicts,
 | |
|   cachedFieldsAndFragmentNames,
 | |
|   comparedFieldsAndFragmentPairs,
 | |
|   comparedFragmentPairs,
 | |
|   areMutuallyExclusive,
 | |
|   fieldMap,
 | |
|   fragmentName,
 | |
| ) {
 | |
|   // Memoize so the fields and fragments are not compared for conflicts more
 | |
|   // than once.
 | |
|   if (
 | |
|     comparedFieldsAndFragmentPairs.has(
 | |
|       fieldMap,
 | |
|       fragmentName,
 | |
|       areMutuallyExclusive,
 | |
|     )
 | |
|   ) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   comparedFieldsAndFragmentPairs.add(
 | |
|     fieldMap,
 | |
|     fragmentName,
 | |
|     areMutuallyExclusive,
 | |
|   );
 | |
|   const fragment = context.getFragment(fragmentName);
 | |
| 
 | |
|   if (!fragment) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   const [fieldMap2, referencedFragmentNames] =
 | |
|     getReferencedFieldsAndFragmentNames(
 | |
|       context,
 | |
|       cachedFieldsAndFragmentNames,
 | |
|       fragment,
 | |
|     ); // Do not compare a fragment's fieldMap to itself.
 | |
| 
 | |
|   if (fieldMap === fieldMap2) {
 | |
|     return;
 | |
|   } // (D) First collect any conflicts between the provided collection of fields
 | |
|   // and the collection of fields represented by the given fragment.
 | |
| 
 | |
|   collectConflictsBetween(
 | |
|     context,
 | |
|     conflicts,
 | |
|     cachedFieldsAndFragmentNames,
 | |
|     comparedFieldsAndFragmentPairs,
 | |
|     comparedFragmentPairs,
 | |
|     areMutuallyExclusive,
 | |
|     fieldMap,
 | |
|     fieldMap2,
 | |
|   ); // (E) Then collect any conflicts between the provided collection of fields
 | |
|   // and any fragment names found in the given fragment.
 | |
| 
 | |
|   for (const referencedFragmentName of referencedFragmentNames) {
 | |
|     collectConflictsBetweenFieldsAndFragment(
 | |
|       context,
 | |
|       conflicts,
 | |
|       cachedFieldsAndFragmentNames,
 | |
|       comparedFieldsAndFragmentPairs,
 | |
|       comparedFragmentPairs,
 | |
|       areMutuallyExclusive,
 | |
|       fieldMap,
 | |
|       referencedFragmentName,
 | |
|     );
 | |
|   }
 | |
| } // Collect all conflicts found between two fragments, including via spreading in
 | |
| // any nested fragments.
 | |
| 
 | |
| function collectConflictsBetweenFragments(
 | |
|   context,
 | |
|   conflicts,
 | |
|   cachedFieldsAndFragmentNames,
 | |
|   comparedFieldsAndFragmentPairs,
 | |
|   comparedFragmentPairs,
 | |
|   areMutuallyExclusive,
 | |
|   fragmentName1,
 | |
|   fragmentName2,
 | |
| ) {
 | |
|   // No need to compare a fragment to itself.
 | |
|   if (fragmentName1 === fragmentName2) {
 | |
|     return;
 | |
|   } // Memoize so two fragments are not compared for conflicts more than once.
 | |
| 
 | |
|   if (
 | |
|     comparedFragmentPairs.has(
 | |
|       fragmentName1,
 | |
|       fragmentName2,
 | |
|       areMutuallyExclusive,
 | |
|     )
 | |
|   ) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   comparedFragmentPairs.add(fragmentName1, fragmentName2, areMutuallyExclusive);
 | |
|   const fragment1 = context.getFragment(fragmentName1);
 | |
|   const fragment2 = context.getFragment(fragmentName2);
 | |
| 
 | |
|   if (!fragment1 || !fragment2) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   const [fieldMap1, referencedFragmentNames1] =
 | |
|     getReferencedFieldsAndFragmentNames(
 | |
|       context,
 | |
|       cachedFieldsAndFragmentNames,
 | |
|       fragment1,
 | |
|     );
 | |
|   const [fieldMap2, referencedFragmentNames2] =
 | |
|     getReferencedFieldsAndFragmentNames(
 | |
|       context,
 | |
|       cachedFieldsAndFragmentNames,
 | |
|       fragment2,
 | |
|     ); // (F) First, collect all conflicts between these two collections of fields
 | |
|   // (not including any nested fragments).
 | |
| 
 | |
|   collectConflictsBetween(
 | |
|     context,
 | |
|     conflicts,
 | |
|     cachedFieldsAndFragmentNames,
 | |
|     comparedFieldsAndFragmentPairs,
 | |
|     comparedFragmentPairs,
 | |
|     areMutuallyExclusive,
 | |
|     fieldMap1,
 | |
|     fieldMap2,
 | |
|   ); // (G) Then collect conflicts between the first fragment and any nested
 | |
|   // fragments spread in the second fragment.
 | |
| 
 | |
|   for (const referencedFragmentName2 of referencedFragmentNames2) {
 | |
|     collectConflictsBetweenFragments(
 | |
|       context,
 | |
|       conflicts,
 | |
|       cachedFieldsAndFragmentNames,
 | |
|       comparedFieldsAndFragmentPairs,
 | |
|       comparedFragmentPairs,
 | |
|       areMutuallyExclusive,
 | |
|       fragmentName1,
 | |
|       referencedFragmentName2,
 | |
|     );
 | |
|   } // (G) Then collect conflicts between the second fragment and any nested
 | |
|   // fragments spread in the first fragment.
 | |
| 
 | |
|   for (const referencedFragmentName1 of referencedFragmentNames1) {
 | |
|     collectConflictsBetweenFragments(
 | |
|       context,
 | |
|       conflicts,
 | |
|       cachedFieldsAndFragmentNames,
 | |
|       comparedFieldsAndFragmentPairs,
 | |
|       comparedFragmentPairs,
 | |
|       areMutuallyExclusive,
 | |
|       referencedFragmentName1,
 | |
|       fragmentName2,
 | |
|     );
 | |
|   }
 | |
| } // Find all conflicts found between two selection sets, including those found
 | |
| // via spreading in fragments. Called when determining if conflicts exist
 | |
| // between the sub-fields of two overlapping fields.
 | |
| 
 | |
| function findConflictsBetweenSubSelectionSets(
 | |
|   context,
 | |
|   cachedFieldsAndFragmentNames,
 | |
|   comparedFieldsAndFragmentPairs,
 | |
|   comparedFragmentPairs,
 | |
|   areMutuallyExclusive,
 | |
|   parentType1,
 | |
|   selectionSet1,
 | |
|   parentType2,
 | |
|   selectionSet2,
 | |
| ) {
 | |
|   const conflicts = [];
 | |
|   const [fieldMap1, fragmentNames1] = getFieldsAndFragmentNames(
 | |
|     context,
 | |
|     cachedFieldsAndFragmentNames,
 | |
|     parentType1,
 | |
|     selectionSet1,
 | |
|   );
 | |
|   const [fieldMap2, fragmentNames2] = getFieldsAndFragmentNames(
 | |
|     context,
 | |
|     cachedFieldsAndFragmentNames,
 | |
|     parentType2,
 | |
|     selectionSet2,
 | |
|   ); // (H) First, collect all conflicts between these two collections of field.
 | |
| 
 | |
|   collectConflictsBetween(
 | |
|     context,
 | |
|     conflicts,
 | |
|     cachedFieldsAndFragmentNames,
 | |
|     comparedFieldsAndFragmentPairs,
 | |
|     comparedFragmentPairs,
 | |
|     areMutuallyExclusive,
 | |
|     fieldMap1,
 | |
|     fieldMap2,
 | |
|   ); // (I) Then collect conflicts between the first collection of fields and
 | |
|   // those referenced by each fragment name associated with the second.
 | |
| 
 | |
|   for (const fragmentName2 of fragmentNames2) {
 | |
|     collectConflictsBetweenFieldsAndFragment(
 | |
|       context,
 | |
|       conflicts,
 | |
|       cachedFieldsAndFragmentNames,
 | |
|       comparedFieldsAndFragmentPairs,
 | |
|       comparedFragmentPairs,
 | |
|       areMutuallyExclusive,
 | |
|       fieldMap1,
 | |
|       fragmentName2,
 | |
|     );
 | |
|   } // (I) Then collect conflicts between the second collection of fields and
 | |
|   // those referenced by each fragment name associated with the first.
 | |
| 
 | |
|   for (const fragmentName1 of fragmentNames1) {
 | |
|     collectConflictsBetweenFieldsAndFragment(
 | |
|       context,
 | |
|       conflicts,
 | |
|       cachedFieldsAndFragmentNames,
 | |
|       comparedFieldsAndFragmentPairs,
 | |
|       comparedFragmentPairs,
 | |
|       areMutuallyExclusive,
 | |
|       fieldMap2,
 | |
|       fragmentName1,
 | |
|     );
 | |
|   } // (J) Also collect conflicts between any fragment names by the first and
 | |
|   // fragment names by the second. This compares each item in the first set of
 | |
|   // names to each item in the second set of names.
 | |
| 
 | |
|   for (const fragmentName1 of fragmentNames1) {
 | |
|     for (const fragmentName2 of fragmentNames2) {
 | |
|       collectConflictsBetweenFragments(
 | |
|         context,
 | |
|         conflicts,
 | |
|         cachedFieldsAndFragmentNames,
 | |
|         comparedFieldsAndFragmentPairs,
 | |
|         comparedFragmentPairs,
 | |
|         areMutuallyExclusive,
 | |
|         fragmentName1,
 | |
|         fragmentName2,
 | |
|       );
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return conflicts;
 | |
| } // Collect all Conflicts "within" one collection of fields.
 | |
| 
 | |
| function collectConflictsWithin(
 | |
|   context,
 | |
|   conflicts,
 | |
|   cachedFieldsAndFragmentNames,
 | |
|   comparedFieldsAndFragmentPairs,
 | |
|   comparedFragmentPairs,
 | |
|   fieldMap,
 | |
| ) {
 | |
|   // A field map is a keyed collection, where each key represents a response
 | |
|   // name and the value at that key is a list of all fields which provide that
 | |
|   // response name. For every response name, if there are multiple fields, they
 | |
|   // must be compared to find a potential conflict.
 | |
|   for (const [responseName, fields] of Object.entries(fieldMap)) {
 | |
|     // This compares every field in the list to every other field in this list
 | |
|     // (except to itself). If the list only has one item, nothing needs to
 | |
|     // be compared.
 | |
|     if (fields.length > 1) {
 | |
|       for (let i = 0; i < fields.length; i++) {
 | |
|         for (let j = i + 1; j < fields.length; j++) {
 | |
|           const conflict = findConflict(
 | |
|             context,
 | |
|             cachedFieldsAndFragmentNames,
 | |
|             comparedFieldsAndFragmentPairs,
 | |
|             comparedFragmentPairs,
 | |
|             false, // within one collection is never mutually exclusive
 | |
|             responseName,
 | |
|             fields[i],
 | |
|             fields[j],
 | |
|           );
 | |
| 
 | |
|           if (conflict) {
 | |
|             conflicts.push(conflict);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| } // Collect all Conflicts between two collections of fields. This is similar to,
 | |
| // but different from the `collectConflictsWithin` function above. This check
 | |
| // assumes that `collectConflictsWithin` has already been called on each
 | |
| // provided collection of fields. This is true because this validator traverses
 | |
| // each individual selection set.
 | |
| 
 | |
| function collectConflictsBetween(
 | |
|   context,
 | |
|   conflicts,
 | |
|   cachedFieldsAndFragmentNames,
 | |
|   comparedFieldsAndFragmentPairs,
 | |
|   comparedFragmentPairs,
 | |
|   parentFieldsAreMutuallyExclusive,
 | |
|   fieldMap1,
 | |
|   fieldMap2,
 | |
| ) {
 | |
|   // A field map is a keyed collection, where each key represents a response
 | |
|   // name and the value at that key is a list of all fields which provide that
 | |
|   // response name. For any response name which appears in both provided field
 | |
|   // maps, each field from the first field map must be compared to every field
 | |
|   // in the second field map to find potential conflicts.
 | |
|   for (const [responseName, fields1] of Object.entries(fieldMap1)) {
 | |
|     const fields2 = fieldMap2[responseName];
 | |
| 
 | |
|     if (fields2) {
 | |
|       for (const field1 of fields1) {
 | |
|         for (const field2 of fields2) {
 | |
|           const conflict = findConflict(
 | |
|             context,
 | |
|             cachedFieldsAndFragmentNames,
 | |
|             comparedFieldsAndFragmentPairs,
 | |
|             comparedFragmentPairs,
 | |
|             parentFieldsAreMutuallyExclusive,
 | |
|             responseName,
 | |
|             field1,
 | |
|             field2,
 | |
|           );
 | |
| 
 | |
|           if (conflict) {
 | |
|             conflicts.push(conflict);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| } // Determines if there is a conflict between two particular fields, including
 | |
| // comparing their sub-fields.
 | |
| 
 | |
| function findConflict(
 | |
|   context,
 | |
|   cachedFieldsAndFragmentNames,
 | |
|   comparedFieldsAndFragmentPairs,
 | |
|   comparedFragmentPairs,
 | |
|   parentFieldsAreMutuallyExclusive,
 | |
|   responseName,
 | |
|   field1,
 | |
|   field2,
 | |
| ) {
 | |
|   const [parentType1, node1, def1] = field1;
 | |
|   const [parentType2, node2, def2] = field2; // If it is known that two fields could not possibly apply at the same
 | |
|   // time, due to the parent types, then it is safe to permit them to diverge
 | |
|   // in aliased field or arguments used as they will not present any ambiguity
 | |
|   // by differing.
 | |
|   // It is known that two parent types could never overlap if they are
 | |
|   // different Object types. Interface or Union types might overlap - if not
 | |
|   // in the current state of the schema, then perhaps in some future version,
 | |
|   // thus may not safely diverge.
 | |
| 
 | |
|   const areMutuallyExclusive =
 | |
|     parentFieldsAreMutuallyExclusive ||
 | |
|     (parentType1 !== parentType2 &&
 | |
|       isObjectType(parentType1) &&
 | |
|       isObjectType(parentType2));
 | |
| 
 | |
|   if (!areMutuallyExclusive) {
 | |
|     // Two aliases must refer to the same field.
 | |
|     const name1 = node1.name.value;
 | |
|     const name2 = node2.name.value;
 | |
| 
 | |
|     if (name1 !== name2) {
 | |
|       return [
 | |
|         [responseName, `"${name1}" and "${name2}" are different fields`],
 | |
|         [node1],
 | |
|         [node2],
 | |
|       ];
 | |
|     } // Two field calls must have the same arguments.
 | |
| 
 | |
|     if (!sameArguments(node1, node2)) {
 | |
|       return [
 | |
|         [responseName, 'they have differing arguments'],
 | |
|         [node1],
 | |
|         [node2],
 | |
|       ];
 | |
|     }
 | |
|   } // The return type for each field.
 | |
| 
 | |
|   const type1 = def1 === null || def1 === void 0 ? void 0 : def1.type;
 | |
|   const type2 = def2 === null || def2 === void 0 ? void 0 : def2.type;
 | |
| 
 | |
|   if (type1 && type2 && doTypesConflict(type1, type2)) {
 | |
|     return [
 | |
|       [
 | |
|         responseName,
 | |
|         `they return conflicting types "${inspect(type1)}" and "${inspect(
 | |
|           type2,
 | |
|         )}"`,
 | |
|       ],
 | |
|       [node1],
 | |
|       [node2],
 | |
|     ];
 | |
|   } // Collect and compare sub-fields. Use the same "visited fragment names" list
 | |
|   // for both collections so fields in a fragment reference are never
 | |
|   // compared to themselves.
 | |
| 
 | |
|   const selectionSet1 = node1.selectionSet;
 | |
|   const selectionSet2 = node2.selectionSet;
 | |
| 
 | |
|   if (selectionSet1 && selectionSet2) {
 | |
|     const conflicts = findConflictsBetweenSubSelectionSets(
 | |
|       context,
 | |
|       cachedFieldsAndFragmentNames,
 | |
|       comparedFieldsAndFragmentPairs,
 | |
|       comparedFragmentPairs,
 | |
|       areMutuallyExclusive,
 | |
|       getNamedType(type1),
 | |
|       selectionSet1,
 | |
|       getNamedType(type2),
 | |
|       selectionSet2,
 | |
|     );
 | |
|     return subfieldConflicts(conflicts, responseName, node1, node2);
 | |
|   }
 | |
| }
 | |
| 
 | |
| function sameArguments(node1, node2) {
 | |
|   const args1 = node1.arguments;
 | |
|   const args2 = node2.arguments;
 | |
| 
 | |
|   if (args1 === undefined || args1.length === 0) {
 | |
|     return args2 === undefined || args2.length === 0;
 | |
|   }
 | |
| 
 | |
|   if (args2 === undefined || args2.length === 0) {
 | |
|     return false;
 | |
|   }
 | |
|   /* c8 ignore next */
 | |
| 
 | |
|   if (args1.length !== args2.length) {
 | |
|     /* c8 ignore next */
 | |
|     return false;
 | |
|     /* c8 ignore next */
 | |
|   }
 | |
| 
 | |
|   const values2 = new Map(args2.map(({ name, value }) => [name.value, value]));
 | |
|   return args1.every((arg1) => {
 | |
|     const value1 = arg1.value;
 | |
|     const value2 = values2.get(arg1.name.value);
 | |
| 
 | |
|     if (value2 === undefined) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     return stringifyValue(value1) === stringifyValue(value2);
 | |
|   });
 | |
| }
 | |
| 
 | |
| function stringifyValue(value) {
 | |
|   return print(sortValueNode(value));
 | |
| } // Two types conflict if both types could not apply to a value simultaneously.
 | |
| // Composite types are ignored as their individual field types will be compared
 | |
| // later recursively. However List and Non-Null types must match.
 | |
| 
 | |
| function doTypesConflict(type1, type2) {
 | |
|   if (isListType(type1)) {
 | |
|     return isListType(type2)
 | |
|       ? doTypesConflict(type1.ofType, type2.ofType)
 | |
|       : true;
 | |
|   }
 | |
| 
 | |
|   if (isListType(type2)) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   if (isNonNullType(type1)) {
 | |
|     return isNonNullType(type2)
 | |
|       ? doTypesConflict(type1.ofType, type2.ofType)
 | |
|       : true;
 | |
|   }
 | |
| 
 | |
|   if (isNonNullType(type2)) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   if (isLeafType(type1) || isLeafType(type2)) {
 | |
|     return type1 !== type2;
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| } // Given a selection set, return the collection of fields (a mapping of response
 | |
| // name to field nodes and definitions) as well as a list of fragment names
 | |
| // referenced via fragment spreads.
 | |
| 
 | |
| function getFieldsAndFragmentNames(
 | |
|   context,
 | |
|   cachedFieldsAndFragmentNames,
 | |
|   parentType,
 | |
|   selectionSet,
 | |
| ) {
 | |
|   const cached = cachedFieldsAndFragmentNames.get(selectionSet);
 | |
| 
 | |
|   if (cached) {
 | |
|     return cached;
 | |
|   }
 | |
| 
 | |
|   const nodeAndDefs = Object.create(null);
 | |
|   const fragmentNames = Object.create(null);
 | |
| 
 | |
|   _collectFieldsAndFragmentNames(
 | |
|     context,
 | |
|     parentType,
 | |
|     selectionSet,
 | |
|     nodeAndDefs,
 | |
|     fragmentNames,
 | |
|   );
 | |
| 
 | |
|   const result = [nodeAndDefs, Object.keys(fragmentNames)];
 | |
|   cachedFieldsAndFragmentNames.set(selectionSet, result);
 | |
|   return result;
 | |
| } // Given a reference to a fragment, return the represented collection of fields
 | |
| // as well as a list of nested fragment names referenced via fragment spreads.
 | |
| 
 | |
| function getReferencedFieldsAndFragmentNames(
 | |
|   context,
 | |
|   cachedFieldsAndFragmentNames,
 | |
|   fragment,
 | |
| ) {
 | |
|   // Short-circuit building a type from the node if possible.
 | |
|   const cached = cachedFieldsAndFragmentNames.get(fragment.selectionSet);
 | |
| 
 | |
|   if (cached) {
 | |
|     return cached;
 | |
|   }
 | |
| 
 | |
|   const fragmentType = typeFromAST(context.getSchema(), fragment.typeCondition);
 | |
|   return getFieldsAndFragmentNames(
 | |
|     context,
 | |
|     cachedFieldsAndFragmentNames,
 | |
|     fragmentType,
 | |
|     fragment.selectionSet,
 | |
|   );
 | |
| }
 | |
| 
 | |
| function _collectFieldsAndFragmentNames(
 | |
|   context,
 | |
|   parentType,
 | |
|   selectionSet,
 | |
|   nodeAndDefs,
 | |
|   fragmentNames,
 | |
| ) {
 | |
|   for (const selection of selectionSet.selections) {
 | |
|     switch (selection.kind) {
 | |
|       case Kind.FIELD: {
 | |
|         const fieldName = selection.name.value;
 | |
|         let fieldDef;
 | |
| 
 | |
|         if (isObjectType(parentType) || isInterfaceType(parentType)) {
 | |
|           fieldDef = parentType.getFields()[fieldName];
 | |
|         }
 | |
| 
 | |
|         const responseName = selection.alias
 | |
|           ? selection.alias.value
 | |
|           : fieldName;
 | |
| 
 | |
|         if (!nodeAndDefs[responseName]) {
 | |
|           nodeAndDefs[responseName] = [];
 | |
|         }
 | |
| 
 | |
|         nodeAndDefs[responseName].push([parentType, selection, fieldDef]);
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       case Kind.FRAGMENT_SPREAD:
 | |
|         fragmentNames[selection.name.value] = true;
 | |
|         break;
 | |
| 
 | |
|       case Kind.INLINE_FRAGMENT: {
 | |
|         const typeCondition = selection.typeCondition;
 | |
|         const inlineFragmentType = typeCondition
 | |
|           ? typeFromAST(context.getSchema(), typeCondition)
 | |
|           : parentType;
 | |
| 
 | |
|         _collectFieldsAndFragmentNames(
 | |
|           context,
 | |
|           inlineFragmentType,
 | |
|           selection.selectionSet,
 | |
|           nodeAndDefs,
 | |
|           fragmentNames,
 | |
|         );
 | |
| 
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| } // Given a series of Conflicts which occurred between two sub-fields, generate
 | |
| // a single Conflict.
 | |
| 
 | |
| function subfieldConflicts(conflicts, responseName, node1, node2) {
 | |
|   if (conflicts.length > 0) {
 | |
|     return [
 | |
|       [responseName, conflicts.map(([reason]) => reason)],
 | |
|       [node1, ...conflicts.map(([, fields1]) => fields1).flat()],
 | |
|       [node2, ...conflicts.map(([, , fields2]) => fields2).flat()],
 | |
|     ];
 | |
|   }
 | |
| }
 | |
| /**
 | |
|  * A way to keep track of pairs of things where the ordering of the pair
 | |
|  * matters.
 | |
|  *
 | |
|  * Provides a third argument for has/set to allow flagging the pair as
 | |
|  * weakly or strongly present within the collection.
 | |
|  */
 | |
| 
 | |
| class OrderedPairSet {
 | |
|   constructor() {
 | |
|     this._data = new Map();
 | |
|   }
 | |
| 
 | |
|   has(a, b, weaklyPresent) {
 | |
|     var _this$_data$get;
 | |
| 
 | |
|     const result =
 | |
|       (_this$_data$get = this._data.get(a)) === null ||
 | |
|       _this$_data$get === void 0
 | |
|         ? void 0
 | |
|         : _this$_data$get.get(b);
 | |
| 
 | |
|     if (result === undefined) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     return weaklyPresent ? true : weaklyPresent === result;
 | |
|   }
 | |
| 
 | |
|   add(a, b, weaklyPresent) {
 | |
|     const map = this._data.get(a);
 | |
| 
 | |
|     if (map === undefined) {
 | |
|       this._data.set(a, new Map([[b, weaklyPresent]]));
 | |
|     } else {
 | |
|       map.set(b, weaklyPresent);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| /**
 | |
|  * A way to keep track of pairs of similar things when the ordering of the pair
 | |
|  * does not matter.
 | |
|  */
 | |
| 
 | |
| class PairSet {
 | |
|   constructor() {
 | |
|     this._orderedPairSet = new OrderedPairSet();
 | |
|   }
 | |
| 
 | |
|   has(a, b, weaklyPresent) {
 | |
|     return a < b
 | |
|       ? this._orderedPairSet.has(a, b, weaklyPresent)
 | |
|       : this._orderedPairSet.has(b, a, weaklyPresent);
 | |
|   }
 | |
| 
 | |
|   add(a, b, weaklyPresent) {
 | |
|     if (a < b) {
 | |
|       this._orderedPairSet.add(a, b, weaklyPresent);
 | |
|     } else {
 | |
|       this._orderedPairSet.add(b, a, weaklyPresent);
 | |
|     }
 | |
|   }
 | |
| }
 |