/** * 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 };