UNPKG

graphql

Version:

A Query Language and Runtime which can target any service.

110 lines (91 loc) 3.32 kB
/* @flow */ /** * Copyright (c) 2015, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ 'use strict'; var _Object$defineProperty = require('babel-runtime/core-js/object/define-property')['default']; var _Set = require('babel-runtime/core-js/set')['default']; _Object$defineProperty(exports, '__esModule', { value: true }); exports['default'] = NoFragmentCycles; var _error = require('../../error'); var _languageKinds = require('../../language/kinds'); var _languageVisitor = require('../../language/visitor'); var _errors = require('../errors'); function NoFragmentCycles(context) { // Gather all the fragment spreads ASTs for each fragment definition. // Importantly this does not include inline fragments. var definitions = context.getDocument().definitions; var spreadsInFragment = definitions.reduce(function (map, node) { if (node.kind === _languageKinds.FRAGMENT_DEFINITION) { map[node.name.value] = gatherSpreads(node); } return map; }, {}); // Tracks spreads known to lead to cycles to ensure that cycles are not // redundantly reported. var knownToLeadToCycle = new _Set(); return { FragmentDefinition: function FragmentDefinition(node) { var errors = []; var initialName = node.name.value; // Array of AST nodes used to produce meaningful errors var spreadPath = []; // This does a straight-forward DFS to find cycles. // It does not terminate when a cycle was found but continues to explore // the graph to find all possible cycles. function detectCycleRecursive(fragmentName) { var spreadNodes = spreadsInFragment[fragmentName]; for (var i = 0; i < spreadNodes.length; ++i) { var spreadNode = spreadNodes[i]; if (knownToLeadToCycle.has(spreadNode)) { continue; } if (spreadNode.name.value === initialName) { var cyclePath = spreadPath.concat(spreadNode); cyclePath.forEach(function (spread) { return knownToLeadToCycle.add(spread); }); errors.push(new _error.GraphQLError((0, _errors.cycleErrorMessage)(initialName, spreadPath.map(function (s) { return s.name.value; })), cyclePath)); continue; } if (spreadPath.some(function (spread) { return spread === spreadNode; })) { continue; } spreadPath.push(spreadNode); detectCycleRecursive(spreadNode.name.value); spreadPath.pop(); } } detectCycleRecursive(initialName); if (errors.length > 0) { return errors; } } }; } /** * Given an operation or fragment AST node, gather all the * named spreads defined within the scope of the fragment * or operation */ function gatherSpreads(node) { var spreadNodes = []; (0, _languageVisitor.visit)(node, { FragmentSpread: function FragmentSpread(spread) { spreadNodes.push(spread); } }); return spreadNodes; } module.exports = exports['default'];