Spaces:
Paused
Paused
| 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); | |
| } | |
| } | |
| } | |