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

270 lines
9.7 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.
*
* @format
*
*/
'use strict';
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread"));
var GraphQLCompilerContext = require("./GraphQLCompilerContext");
var GraphQLIRTransformer = require("./GraphQLIRTransformer");
var GraphQLSchemaUtils = require("./GraphQLSchemaUtils");
var areEqual = require("./areEqualOSS");
var getIdentifierForSelection = require("./getIdentifierForSelection");
var _require = require("./RelayCompilerError"),
createUserError = _require.createUserError,
createCompilerError = _require.createCompilerError;
var getRawType = GraphQLSchemaUtils.getRawType,
isAbstractType = GraphQLSchemaUtils.isAbstractType;
/**
* Transform that flattens inline fragments, fragment spreads, and conditionals.
*
* Inline fragments are inlined (replaced with their selections) when:
* - The fragment type matches the type of its parent.
* - The fragment has an abstract type and the `flattenAbstractTypes` option has
* been set.
* - The 'flattenInlineFragments' option has been set.
*/
function flattenTransformImpl(context, options) {
var state = {
flattenAbstractTypes: !!(options && options.flattenAbstractTypes),
flattenInlineFragments: !!(options && options.flattenInlineFragments),
parentType: null
};
return GraphQLIRTransformer.transform(context, {
Root: flattenSelections,
Fragment: flattenSelections,
Condition: flattenSelections,
InlineFragment: flattenSelections,
LinkedField: flattenSelections,
MatchField: flattenSelections
}, function () {
return state;
});
}
/**
* @private
*/
function flattenSelections(node, state) {
// Determine the current type.
var parentType = state.parentType;
var type = node.kind === 'Condition' || node.kind === 'Defer' || node.kind === 'Stream' ? parentType : node.kind === 'InlineFragment' ? node.typeCondition : node.type;
if (type == null) {
throw createCompilerError('FlattenTransform: Expected a parent type.', [node.loc]);
} // Flatten the selections in this node, creating a new node with flattened
// selections if possible, then deeply traverse the flattened node, while
// keeping track of the parent type.
var nextSelections = new Map();
var hasFlattened = flattenSelectionsInto(nextSelections, node, state, type);
var flattenedNode = hasFlattened ? (0, _objectSpread2["default"])({}, node, {
selections: Array.from(nextSelections.values())
}) : node;
state.parentType = type;
var deeplyFlattenedNode = this.traverse(flattenedNode, state);
state.parentType = parentType;
return deeplyFlattenedNode;
}
/**
* @private
*/
function flattenSelectionsInto(flattenedSelections, node, state, type) {
var hasFlattened = false;
node.selections.forEach(function (selection) {
if (selection.kind === 'InlineFragment' && shouldFlattenInlineFragment(selection, state, type)) {
hasFlattened = true;
flattenSelectionsInto(flattenedSelections, selection, state, type);
return;
}
var nodeIdentifier = getIdentifierForSelection(selection);
var flattenedSelection = flattenedSelections.get(nodeIdentifier); // If this selection hasn't been seen before, keep track of it.
if (!flattenedSelection) {
flattenedSelections.set(nodeIdentifier, selection);
return;
} // Otherwise a similar selection exists which should be merged.
hasFlattened = true;
if (flattenedSelection.kind === 'InlineFragment') {
if (selection.kind !== 'InlineFragment') {
throw createCompilerError("FlattenTransform: Expected an InlineFragment, got a '".concat(selection.kind, "'"), [selection.loc]);
}
flattenedSelections.set(nodeIdentifier, (0, _objectSpread2["default"])({}, flattenedSelection, {
selections: mergeSelections(flattenedSelection, selection, state, selection.typeCondition)
}));
} else if (flattenedSelection.kind === 'Condition') {
if (selection.kind !== 'Condition') {
throw createCompilerError("FlattenTransform: Expected a Condition, got a '".concat(selection.kind, "'"), [selection.loc]);
}
flattenedSelections.set(nodeIdentifier, (0, _objectSpread2["default"])({}, flattenedSelection, {
selections: mergeSelections(flattenedSelection, selection, state, type)
}));
} else if (flattenedSelection.kind === 'FragmentSpread') {// Ignore duplicate fragment spreads.
} else if (flattenedSelection.kind === 'MatchField' || flattenedSelection.kind === 'MatchBranch') {// Ignore duplicate matches that select the same fragments and
// modules (encoded in the identifier)
// Also ignore incremental data placeholders
} else if (flattenedSelection.kind === 'Defer') {
if (selection.kind !== 'Defer') {
throw createCompilerError("FlattenTransform: Expected a Defer, got a '".concat(selection.kind, "'"), [selection.loc]);
}
flattenedSelections.set(nodeIdentifier, (0, _objectSpread2["default"])({
kind: 'Defer'
}, flattenedSelection, {
selections: mergeSelections(flattenedSelection, selection, state, type)
}));
} else if (flattenedSelection.kind === 'Stream') {
if (selection.kind !== 'Stream') {
throw createCompilerError("FlattenTransform: Expected a Stream, got a '".concat(selection.kind, "'"), [selection.loc]);
}
flattenedSelections.set(nodeIdentifier, (0, _objectSpread2["default"])({
kind: 'Stream'
}, flattenedSelection, {
selections: mergeSelections(flattenedSelection, selection, state, type)
}));
} else if (flattenedSelection.kind === 'LinkedField') {
if (selection.kind !== 'LinkedField') {
throw createCompilerError("FlattenTransform: Expected a LinkedField, got a '".concat(selection.kind, "'"), [selection.loc]);
} // Note: arguments are intentionally reversed to avoid rebuilds
assertUniqueArgsForAlias(selection, flattenedSelection);
flattenedSelections.set(nodeIdentifier, (0, _objectSpread2["default"])({
kind: 'LinkedField'
}, flattenedSelection, {
handles: mergeHandles(flattenedSelection, selection),
selections: mergeSelections(flattenedSelection, selection, state, selection.type)
}));
} else if (flattenedSelection.kind === 'ScalarField') {
if (selection.kind !== 'ScalarField') {
throw createCompilerError("FlattenTransform: Expected a ScalarField, got a '".concat(selection.kind, "'"), [selection.loc]);
} // Note: arguments are intentionally reversed to avoid rebuilds
assertUniqueArgsForAlias(selection, flattenedSelection);
flattenedSelections.set(nodeIdentifier, (0, _objectSpread2["default"])({
kind: 'ScalarField'
}, flattenedSelection, {
// Note: arguments are intentionally reversed to avoid rebuilds
handles: mergeHandles(selection, flattenedSelection)
}));
} else {
flattenedSelection.kind;
throw createCompilerError("FlattenTransform: Unknown kind '".concat(flattenedSelection.kind, "'"));
}
});
return hasFlattened;
}
/**
* @private
*/
function mergeSelections(nodeA, nodeB, state, type) {
var flattenedSelections = new Map();
flattenSelectionsInto(flattenedSelections, nodeA, state, type);
flattenSelectionsInto(flattenedSelections, nodeB, state, type);
return Array.from(flattenedSelections.values());
}
/**
* @private
* TODO(T19327202) This is redundant with OverlappingFieldsCanBeMergedRule once
* it can be enabled.
*/
function assertUniqueArgsForAlias(field, otherField) {
if (!areEqualFields(field, otherField)) {
var _field$alias;
throw createUserError('Expected all fields on the same parent with ' + "the name or alias '".concat((_field$alias = field.alias) !== null && _field$alias !== void 0 ? _field$alias : field.name, "' to have the same name and arguments."), [field.loc, otherField.loc]);
}
}
/**
* @private
*/
function shouldFlattenInlineFragment(fragment, state, type) {
return state.flattenInlineFragments || fragment.typeCondition.name === getRawType(type).name || state.flattenAbstractTypes && isAbstractType(fragment.typeCondition);
}
/**
* @private
*
* Verify that two fields are equal in all properties other than their
* selections.
*/
function areEqualFields(thisField, thatField) {
return thisField.kind === thatField.kind && thisField.name === thatField.name && thisField.alias === thatField.alias && areEqualArgs(thisField.args, thatField.args);
}
/**
* Verify that two sets of arguments are equivalent - same argument names
* and values. Notably this ignores the types of arguments and values, which
* may not always be inferred identically.
*/
function areEqualArgs(thisArgs, thatArgs) {
return thisArgs.length === thatArgs.length && thisArgs.every(function (thisArg, index) {
var thatArg = thatArgs[index];
return thisArg.name === thatArg.name && thisArg.value.kind === thatArg.value.kind && thisArg.value.variableName === thatArg.value.variableName && areEqual(thisArg.value.value, thatArg.value.value);
});
}
/**
* @private
*/
function mergeHandles(nodeA, nodeB) {
if (!nodeA.handles) {
return nodeB.handles;
}
if (!nodeB.handles) {
return nodeA.handles;
}
var uniqueItems = new Map();
nodeA.handles.concat(nodeB.handles).forEach(function (item) {
return uniqueItems.set(item.name + item.key, item);
});
return Array.from(uniqueItems.values());
}
function transformWithOptions(options) {
return function flattenTransform(context) {
return flattenTransformImpl(context, options);
};
}
module.exports = {
transformWithOptions: transformWithOptions
};