graphql
Version:
A Query Language and Runtime which can target any service.
110 lines (91 loc) • 3.32 kB
JavaScript
/* @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.
*/
;
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'];