UNPKG

@graphql-tools/documents

Version:
103 lines (102 loc) 3.62 kB
import { Kind, print, } from 'graphql'; import { normalizeWhiteSpace } from './normalize-whitespace.js'; // Cache the sorted nodes to avoid sorting the same nodes multiple times const nodeSortCache = new WeakMap(); export function sortExecutableNodes(nodes) { if (nodes) { const shortcutNodes = nodeSortCache.get(nodes); if (shortcutNodes) { return shortcutNodes; } const cacheResult = (resultNodes) => { nodeSortCache.set(nodes, resultNodes); return resultNodes; }; if (nodes.length === 0) { return []; } if (isOfKindList(nodes, Kind.DIRECTIVE)) { return cacheResult(sortNodesByStringKey(nodes, node => node.name.value)); } if (isOfKindList(nodes, Kind.VARIABLE_DEFINITION)) { return cacheResult(sortNodesByStringKey(nodes, node => node.variable.name.value)); } if (isOfKindList(nodes, Kind.ARGUMENT)) { return cacheResult(sortNodesByStringKey(nodes, node => node.name.value)); } if (isOfKindList(nodes, [Kind.FIELD, Kind.FRAGMENT_SPREAD, Kind.INLINE_FRAGMENT])) { return cacheResult(sortNodesByStringKey(nodes, node => { if (node.kind === Kind.FIELD) { return sortPrefixField + node.name.value; } else if (node.kind === Kind.FRAGMENT_SPREAD) { return sortPrefixFragmentSpread + node.name.value; } else { const typeCondition = node.typeCondition?.name.value ?? ''; // if you have a better idea, send a PR :) const sortedNodes = buildInlineFragmentSelectionSetKey(cacheResult(sortExecutableNodes(node.selectionSet.selections))); return sortPrefixInlineFragmentNode + typeCondition + sortedNodes; } })); } return cacheResult(nodes .map((node, index) => ({ node, index, kind: node.kind, name: getNodeNameValue(node), })) .sort((a, b) => { const kindComparison = compareKeys(a.kind, b.kind); if (kindComparison !== 0) { return kindComparison; } const nameComparison = compareKeys(a.name, b.name); if (nameComparison !== 0) { return nameComparison; } return a.index - b.index; }) .map(item => item.node)); } } const sortPrefixField = '0'; const sortPrefixFragmentSpread = '1'; const sortPrefixInlineFragmentNode = '2'; function isOfKindList(nodes, kind) { return typeof kind === 'string' ? nodes[0].kind === kind : kind.includes(nodes[0].kind); } function buildInlineFragmentSelectionSetKey(nodes) { return normalizeWhiteSpace(nodes.map(node => print(node)).join(' ')); } function sortNodesByStringKey(nodes, getKey) { return nodes .map((node, index) => ({ node, index, key: getKey(node) })) .sort((a, b) => { const keyComparison = compareKeys(a.key, b.key); if (keyComparison !== 0) { return keyComparison; } return a.index - b.index; }) .map(item => item.node); } function compareKeys(a, b) { if (a == null) { return b == null ? 0 : 1; } if (b == null) { return -1; } if (a < b) { return -1; } if (a > b) { return 1; } return 0; } function getNodeNameValue(node) { return node.name?.value; }