Files
30-seconds-of-code/node_modules/@gatsbyjs/relay-compiler/lib/inferRootArgumentDefinitions.js
2019-08-20 15:52:05 +02:00

289 lines
10 KiB
JavaScript

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* strict-local
* @format
*/
'use strict';
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread"));
var GraphQLCompilerContext = require("./GraphQLCompilerContext");
var GraphQLIRVisitor = require("./GraphQLIRVisitor");
var _require = require("./RelayCompilerError"),
createCompilerError = _require.createCompilerError;
var _require2 = require("graphql"),
GraphQLNonNull = _require2.GraphQLNonNull,
GraphQLBoolean = _require2.GraphQLBoolean;
/**
* Returns a transformed version of the input context where each document's
* argument definitions are updated to accurately describe the root variables
* used (or reachable) from that document:
* - Fragment argument definitions are updated to include local argument
* definitions and any root variables that are referenced
* by the fragment (or any fragments it transitively spreads).
* - Root argument definitions are updated to reflect the variables
* referenced locally and all root variables referenced by any
* fragments it (transitively) spreads.
*/
function inferRootArgumentDefinitions(context) {
// This transform does two main tasks:
// - Determine the set of root variables referenced locally in each
// fragment. Note that RootArgumentDefinitions in the fragment's
// argumentDefinitions can contain spurious entries for legacy
// reasons. Instead of using those the fragment is traversed
// to reanalyze variable usage.
// - Determine the set of root variables that are transitively referenced
// by each fragment, ie the union of all root variables used in the
// fragment and any fragments it transitively spreads.
// Cache fragments as they are transformed to avoid duplicate processing.
// Because @argument values don't matter (only variable names/types),
// each reachable fragment only has to be checked once.
var transformed = new Map();
var nextContext = new GraphQLCompilerContext(context.serverSchema, context.clientSchema);
return nextContext.addAll(Array.from(context.documents(), function (node) {
switch (node.kind) {
case 'Fragment':
{
var argumentDefinitions = transformFragmentArguments(context, transformed, node);
return (0, _objectSpread2["default"])({}, node, {
argumentDefinitions: Array.from(argumentDefinitions.values())
});
}
case 'Root':
{
return transformRoot(context, transformed, node);
}
case 'SplitOperation':
{
return node;
}
default:
{
node;
throw createCompilerError("inferRootArgumentDefinitions: Unsupported kind '".concat(node.kind, "'."));
}
}
}));
}
function transformRoot(context, transformed, root) {
// Ignore argument definitions, determine what root variables are
// transitively referenced
var argumentDefinitions = new Map();
var localArgumentDefinitions = new Map();
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = root.argumentDefinitions.entries()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var _step$value = _step.value,
name = _step$value[0],
argDef = _step$value[1];
if (argDef.kind === 'LocalArgumentDefinition') {
localArgumentDefinitions.set(name, argDef);
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator["return"] != null) {
_iterator["return"]();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
visit(context, transformed, argumentDefinitions, root);
return (0, _objectSpread2["default"])({}, root, {
argumentDefinitions: Array.from(argumentDefinitions.values(), function (argDef) {
var _ref, _ref2;
if (argDef.kind !== 'RootArgumentDefinition') {
throw createCompilerError("inferRootArgumentDefinitions: Expected inferred variable '$".concat(argDef.name, "' to be a root variables."), [argDef.loc]);
}
var localDefinition = localArgumentDefinitions.get(argDef.name);
return {
defaultValue: (_ref = localDefinition === null || localDefinition === void 0 ? void 0 : localDefinition.defaultValue) !== null && _ref !== void 0 ? _ref : null,
kind: 'LocalArgumentDefinition',
loc: argDef.loc,
metadata: null,
name: argDef.name,
type: (_ref2 = localDefinition === null || localDefinition === void 0 ? void 0 : localDefinition.type) !== null && _ref2 !== void 0 ? _ref2 : argDef.type
};
})
});
}
function transformFragmentArguments(context, transformed, fragment) {
var name = fragment.name;
var transformedArguments = transformed.get(name);
if (transformedArguments != null) {
return transformedArguments;
} // Start with only the explicitly defined local arguments, recover the
// correct set of root variables excluding invalid @arguments values.
var argumentDefinitions = new Map();
fragment.argumentDefinitions.forEach(function (argDef) {
if (argDef.kind === 'LocalArgumentDefinition') {
argumentDefinitions.set(argDef.name, argDef);
}
}); // Break cycles by initially caching a version that only has local
// arguments. If the current fragment is reached again, it won't have
// any root variables to add to its parents. The traversal below will
// find any root variables and update the cached version of the
// fragment.
transformed.set(name, argumentDefinitions);
visit(context, transformed, argumentDefinitions, fragment);
transformed.set(name, argumentDefinitions);
return argumentDefinitions;
}
function visit(context, transformed, argumentDefinitions, node) {
GraphQLIRVisitor.visit(node, {
FragmentSpread: function FragmentSpread(fragmentSpread) {
var fragment;
try {
fragment = context.getFragment(fragmentSpread.name);
} catch (_unused) {
// Handle cases where a compat fragment references a classic fragment
// that is not accessible to Relay compiler
// TODO: disallow unknown fragment references
// throw createCompilerError(
// `Document '${node.name}' referenced unknown fragment '${
// fragmentSpread.name
// }'.`,
// [fragmentSpread.loc],
// );
return false;
}
var referencedFragmentArguments = transformFragmentArguments(context, transformed, fragment); // Detect root variables being passed as the value of @arguments;
// recover the expected type from the corresponding argument definitions.
fragmentSpread.args.forEach(function (arg) {
var argDef = referencedFragmentArguments.get(arg.name);
if (argDef != null && arg.value.kind === 'Variable' && !argumentDefinitions.has(arg.value.variableName)) {
argumentDefinitions.set(arg.value.variableName, {
kind: 'RootArgumentDefinition',
loc: {
kind: 'Derived',
source: arg.loc
},
metadata: null,
name: arg.value.variableName,
type: argDef.type
});
}
}); // Merge any root variables referenced by the spread fragment
// into this (parent) fragment's arguments.
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = referencedFragmentArguments.values()[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var argDef = _step2.value;
if (argDef.kind === 'RootArgumentDefinition' && !argumentDefinitions.has(argDef.name)) {
argumentDefinitions.set(argDef.name, argDef);
}
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2["return"] != null) {
_iterator2["return"]();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
return false;
},
Argument: function Argument(argument) {
if (argument.value.kind !== 'Variable') {
return false;
}
var variable = argument.value;
if (argument.type == null && variable.type == null) {
return;
}
if (!argumentDefinitions.has(variable.variableName)) {
// root variable
argumentDefinitions.set(variable.variableName, {
kind: 'RootArgumentDefinition',
loc: {
kind: 'Derived',
source: argument.loc
},
metadata: null,
name: variable.variableName,
type: variable.type || argument.type
});
}
return false;
},
Condition: function Condition(condition) {
var _variable$type;
if (condition.condition.kind !== 'Variable') {
return;
}
var variable = condition.condition;
var type = (_variable$type = variable.type) !== null && _variable$type !== void 0 ? _variable$type : new GraphQLNonNull(GraphQLBoolean);
if (!argumentDefinitions.has(variable.variableName)) {
// root variable
argumentDefinitions.set(variable.variableName, {
kind: 'RootArgumentDefinition',
loc: {
kind: 'Derived',
source: condition.loc
},
metadata: null,
name: variable.variableName,
type: type
});
}
}
});
}
module.exports = inferRootArgumentDefinitions;