OverlappingFieldsCanBeMergedRule.js 24 KB

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