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

348 lines
9.5 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 invariant = require("fbjs/lib/invariant");
var _require = require("./RelayCompilerError"),
createCombinedError = _require.createCombinedError,
eachWithErrors = _require.eachWithErrors;
/**
* @public
*
* Helper for writing compiler transforms that apply "map" and/or "filter"-style
* operations to compiler contexts. The `visitor` argument accepts a map of IR
* kinds to user-defined functions that can map nodes of that kind to new values
* (of the same kind).
*
* If a visitor function is defined for a kind, the visitor function is
* responsible for traversing its children (by calling `this.traverse(node)`)
* and returning either the input (to indicate no changes), a new node (to
* indicate changes), or null/undefined (to indicate the removal of that node
* from the output).
*
* If a visitor function is *not* defined for a kind, a default traversal is
* used to evaluate its children.
*
* The `stateInitializer` argument accepts an optional function to construct the
* state for each document (fragment or root) in the context. Any documents for
* which the initializer returns null/undefined is deleted from the context
* without being traversed.
*
* Example: Alias all scalar fields with the reverse of their name:
*
* ```
* transform(context, {
* ScalarField: visitScalarField,
* });
*
* function visitScalarField(field: ScalarField, state: State): ?ScalarField {
* // Traverse child nodes - for a scalar field these are the arguments &
* // directives.
* const nextField = this.traverse(field, state);
* // Return a new node with a different alias.
* return {
* ...nextField,
* alias: nextField.name.split('').reverse().join(''),
* };
* }
* ```
*/
function transform(context, visitor, stateInitializer) {
var transformer = new Transformer(context, visitor);
return context.withMutations(function (ctx) {
var nextContext = ctx;
var errors = eachWithErrors(context.documents(), function (prevNode) {
var nextNode;
if (stateInitializer === undefined) {
nextNode = transformer.visit(prevNode, undefined);
} else {
var _state = stateInitializer(prevNode);
if (_state != null) {
nextNode = transformer.visit(prevNode, _state);
}
}
if (!nextNode) {
nextContext = nextContext.remove(prevNode.name);
} else if (nextNode !== prevNode) {
nextContext = nextContext.replace(nextNode);
}
});
if (errors != null && errors.length !== 0) {
throw createCombinedError(errors);
}
return nextContext;
});
}
/**
* @internal
*/
var Transformer =
/*#__PURE__*/
function () {
function Transformer(context, visitor) {
this._context = context;
this._states = [];
this._visitor = visitor;
}
/**
* @public
*
* Returns the original compiler context that is being transformed. This can
* be used to look up fragments by name, for example.
*/
var _proto = Transformer.prototype;
_proto.getContext = function getContext() {
return this._context;
};
/**
* @public
*
* Transforms the node, calling a user-defined visitor function if defined for
* the node's kind. Uses the given state for this portion of the traversal.
*
* Note: This differs from `traverse` in that it calls a visitor function for
* the node itself.
*/
_proto.visit = function visit(node, state) {
this._states.push(state);
var nextNode = this._visit(node);
this._states.pop();
return nextNode;
};
/**
* @public
*
* Transforms the children of the given node, skipping the user-defined
* visitor function for the node itself. Uses the given state for this portion
* of the traversal.
*
* Note: This differs from `visit` in that it does not call a visitor function
* for the node itself.
*/
_proto.traverse = function traverse(node, state) {
this._states.push(state);
var nextNode = this._traverse(node);
this._states.pop();
return nextNode;
};
_proto._visit = function _visit(node) {
var nodeVisitor = this._visitor[node.kind];
if (nodeVisitor) {
// If a handler for the kind is defined, it is responsible for calling
// `traverse` to transform children as necessary.
var _state2 = this._getState();
var nextNode = nodeVisitor.call(this, node, _state2);
return nextNode;
} // Otherwise traverse is called automatically.
return this._traverse(node);
};
_proto._traverse = function _traverse(prevNode) {
var nextNode;
switch (prevNode.kind) {
case 'Argument':
nextNode = this._traverseChildren(prevNode, null, ['value']);
break;
case 'Literal':
case 'LocalArgumentDefinition':
case 'RootArgumentDefinition':
case 'Variable':
nextNode = prevNode;
break;
case 'Defer':
nextNode = this._traverseChildren(prevNode, ['selections'], ['if']);
break;
case 'Stream':
nextNode = this._traverseChildren(prevNode, ['selections'], ['if', 'initialCount']);
break;
case 'Directive':
nextNode = this._traverseChildren(prevNode, ['args']);
break;
case 'MatchBranch':
nextNode = this._traverseChildren(prevNode, ['selections']);
if (!nextNode.selections.length) {
nextNode = null;
}
break;
case 'FragmentSpread':
case 'ScalarField':
nextNode = this._traverseChildren(prevNode, ['args', 'directives']);
break;
case 'LinkedField':
nextNode = this._traverseChildren(prevNode, ['args', 'directives', 'selections']);
if (!nextNode.selections.length) {
nextNode = null;
}
break;
case 'ListValue':
nextNode = this._traverseChildren(prevNode, ['items']);
break;
case 'MatchField':
nextNode = this._traverseChildren(prevNode, ['args', 'directives', 'selections']);
break;
case 'ObjectFieldValue':
nextNode = this._traverseChildren(prevNode, null, ['value']);
break;
case 'ObjectValue':
nextNode = this._traverseChildren(prevNode, ['fields']);
break;
case 'Condition':
nextNode = this._traverseChildren(prevNode, ['directives', 'selections'], ['condition']);
if (!nextNode.selections.length) {
nextNode = null;
}
break;
case 'InlineFragment':
nextNode = this._traverseChildren(prevNode, ['directives', 'selections']);
if (!nextNode.selections.length) {
nextNode = null;
}
break;
case 'Fragment':
case 'Root':
nextNode = this._traverseChildren(prevNode, ['argumentDefinitions', 'directives', 'selections']);
break;
case 'Request':
nextNode = this._traverseChildren(prevNode, null, ['fragment', 'root']);
break;
case 'SplitOperation':
nextNode = this._traverseChildren(prevNode, ['selections']);
break;
default:
prevNode;
!false ? process.env.NODE_ENV !== "production" ? invariant(false, 'GraphQLIRTransformer: Unknown kind `%s`.', prevNode.kind) : invariant(false) : void 0;
}
return nextNode;
};
_proto._traverseChildren = function _traverseChildren(prevNode, pluralKeys, singularKeys) {
var _this = this;
var nextNode;
pluralKeys && pluralKeys.forEach(function (key) {
var prevItems = prevNode[key];
if (!prevItems) {
return;
}
!Array.isArray(prevItems) ? process.env.NODE_ENV !== "production" ? invariant(false, 'GraphQLIRTransformer: Expected data for `%s` to be an array, got `%s`.', key, prevItems) : invariant(false) : void 0;
var nextItems = _this._map(prevItems);
if (nextNode || nextItems !== prevItems) {
nextNode = nextNode || (0, _objectSpread2["default"])({}, prevNode);
nextNode[key] = nextItems;
}
});
singularKeys && singularKeys.forEach(function (key) {
var prevItem = prevNode[key];
if (!prevItem) {
return;
}
var nextItem = _this._visit(prevItem);
if (nextNode || nextItem !== prevItem) {
nextNode = nextNode || (0, _objectSpread2["default"])({}, prevNode);
nextNode[key] = nextItem;
}
});
return nextNode || prevNode;
};
_proto._map = function _map(prevItems) {
var _this2 = this;
var nextItems;
prevItems.forEach(function (prevItem, index) {
var nextItem = _this2._visit(prevItem);
if (nextItems || nextItem !== prevItem) {
nextItems = nextItems || prevItems.slice(0, index);
if (nextItem) {
nextItems.push(nextItem);
}
}
});
return nextItems || prevItems;
};
_proto._getState = function _getState() {
!this._states.length ? process.env.NODE_ENV !== "production" ? invariant(false, 'GraphQLIRTransformer: Expected a current state to be set but found none. ' + 'This is usually the result of mismatched number of pushState()/popState() ' + 'calls.') : invariant(false) : void 0;
return this._states[this._states.length - 1];
};
return Transformer;
}();
module.exports = {
transform: transform
};