OverlappingFieldsCanBeMergedRule.mjs 24 KB

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