348 lines
9.5 KiB
JavaScript
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
|
|
}; |