514 lines
18 KiB
JavaScript
514 lines
18 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 _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
|
|
|
var babelGenerator = require("@babel/generator")["default"];
|
|
|
|
var FlattenTransform = require("./FlattenTransform");
|
|
|
|
var IRVisitor = require("./GraphQLIRVisitor");
|
|
|
|
var Profiler = require("./GraphQLCompilerProfiler");
|
|
|
|
var RelayMaskTransform = require("./RelayMaskTransform");
|
|
|
|
var RelayMatchTransform = require("./RelayMatchTransform");
|
|
|
|
var RelayRefetchableFragmentTransform = require("./RelayRefetchableFragmentTransform");
|
|
|
|
var RelayRelayDirectiveTransform = require("./RelayRelayDirectiveTransform");
|
|
|
|
var invariant = require("fbjs/lib/invariant");
|
|
|
|
var nullthrows = require("nullthrows");
|
|
|
|
var t = require("@babel/types");
|
|
|
|
var _require = require("./GraphQLSchemaUtils"),
|
|
isAbstractType = _require.isAbstractType;
|
|
|
|
var _require2 = require("./RelayFlowBabelFactories"),
|
|
anyTypeAlias = _require2.anyTypeAlias,
|
|
exactObjectTypeAnnotation = _require2.exactObjectTypeAnnotation,
|
|
exportType = _require2.exportType,
|
|
importTypes = _require2.importTypes,
|
|
intersectionTypeAnnotation = _require2.intersectionTypeAnnotation,
|
|
lineComments = _require2.lineComments,
|
|
readOnlyArrayOfType = _require2.readOnlyArrayOfType,
|
|
readOnlyObjectTypeProperty = _require2.readOnlyObjectTypeProperty,
|
|
unionTypeAnnotation = _require2.unionTypeAnnotation;
|
|
|
|
var _require3 = require("./RelayFlowTypeTransformers"),
|
|
transformScalarType = _require3.transformScalarType,
|
|
transformInputType = _require3.transformInputType;
|
|
|
|
var _require4 = require("graphql"),
|
|
GraphQLInputObjectType = _require4.GraphQLInputObjectType,
|
|
GraphQLNonNull = _require4.GraphQLNonNull;
|
|
|
|
function generate(node, options) {
|
|
var ast = IRVisitor.visit(node, createVisitor(options));
|
|
return babelGenerator(ast).code;
|
|
}
|
|
|
|
function makeProp(_ref, state, unmasked, concreteType) {
|
|
var key = _ref.key,
|
|
schemaName = _ref.schemaName,
|
|
value = _ref.value,
|
|
conditional = _ref.conditional,
|
|
nodeType = _ref.nodeType,
|
|
nodeSelections = _ref.nodeSelections;
|
|
|
|
if (nodeType) {
|
|
value = transformScalarType(nodeType, state, selectionsToBabel([Array.from(nullthrows(nodeSelections).values())], state, unmasked));
|
|
}
|
|
|
|
if (schemaName === '__typename' && concreteType) {
|
|
value = t.stringLiteralTypeAnnotation(concreteType);
|
|
}
|
|
|
|
var typeProperty = readOnlyObjectTypeProperty(key, value);
|
|
|
|
if (conditional) {
|
|
typeProperty.optional = true;
|
|
}
|
|
|
|
return typeProperty;
|
|
}
|
|
|
|
var isTypenameSelection = function isTypenameSelection(selection) {
|
|
return selection.schemaName === '__typename';
|
|
};
|
|
|
|
var hasTypenameSelection = function hasTypenameSelection(selections) {
|
|
return selections.some(isTypenameSelection);
|
|
};
|
|
|
|
var onlySelectsTypename = function onlySelectsTypename(selections) {
|
|
return selections.every(isTypenameSelection);
|
|
};
|
|
|
|
function selectionsToBabel(selections, state, unmasked, refTypeName) {
|
|
var baseFields = new Map();
|
|
var byConcreteType = {};
|
|
flattenArray(selections).forEach(function (selection) {
|
|
var concreteType = selection.concreteType;
|
|
|
|
if (concreteType) {
|
|
var _byConcreteType$concr;
|
|
|
|
byConcreteType[concreteType] = (_byConcreteType$concr = byConcreteType[concreteType]) !== null && _byConcreteType$concr !== void 0 ? _byConcreteType$concr : [];
|
|
byConcreteType[concreteType].push(selection);
|
|
} else {
|
|
var previousSel = baseFields.get(selection.key);
|
|
baseFields.set(selection.key, previousSel ? mergeSelection(selection, previousSel) : selection);
|
|
}
|
|
});
|
|
var types = [];
|
|
|
|
if (Object.keys(byConcreteType).length && onlySelectsTypename(Array.from(baseFields.values())) && (hasTypenameSelection(Array.from(baseFields.values())) || Object.keys(byConcreteType).every(function (type) {
|
|
return hasTypenameSelection(byConcreteType[type]);
|
|
}))) {
|
|
(function () {
|
|
var typenameAliases = new Set();
|
|
|
|
var _loop = function _loop(concreteType) {
|
|
types.push(groupRefs((0, _toConsumableArray2["default"])(Array.from(baseFields.values())).concat((0, _toConsumableArray2["default"])(byConcreteType[concreteType]))).map(function (selection) {
|
|
if (selection.schemaName === '__typename') {
|
|
typenameAliases.add(selection.key);
|
|
}
|
|
|
|
return makeProp(selection, state, unmasked, concreteType);
|
|
}));
|
|
};
|
|
|
|
for (var concreteType in byConcreteType) {
|
|
_loop(concreteType);
|
|
} // It might be some other type then the listed concrete types. Ideally, we
|
|
// would set the type to diff(string, set of listed concrete types), but
|
|
// this doesn't exist in Flow at the time.
|
|
|
|
|
|
types.push(Array.from(typenameAliases).map(function (typenameAlias) {
|
|
var otherProp = readOnlyObjectTypeProperty(typenameAlias, t.stringLiteralTypeAnnotation('%other'));
|
|
otherProp.leadingComments = lineComments("This will never be '%other', but we need some", 'value in case none of the concrete values match.');
|
|
return otherProp;
|
|
}));
|
|
})();
|
|
} else {
|
|
var selectionMap = selectionsToMap(Array.from(baseFields.values()));
|
|
|
|
for (var concreteType in byConcreteType) {
|
|
selectionMap = mergeSelections(selectionMap, selectionsToMap(byConcreteType[concreteType].map(function (sel) {
|
|
return (0, _objectSpread2["default"])({}, sel, {
|
|
conditional: true
|
|
});
|
|
})));
|
|
}
|
|
|
|
var selectionMapValues = groupRefs(Array.from(selectionMap.values())).map(function (sel) {
|
|
return isTypenameSelection(sel) && sel.concreteType ? makeProp((0, _objectSpread2["default"])({}, sel, {
|
|
conditional: false
|
|
}), state, unmasked, sel.concreteType) : makeProp(sel, state, unmasked);
|
|
});
|
|
types.push(selectionMapValues);
|
|
}
|
|
|
|
return unionTypeAnnotation(types.map(function (props) {
|
|
if (refTypeName) {
|
|
props.push(readOnlyObjectTypeProperty('$refType', t.genericTypeAnnotation(t.identifier(refTypeName))));
|
|
}
|
|
|
|
return unmasked ? t.objectTypeAnnotation(props) : exactObjectTypeAnnotation(props);
|
|
}));
|
|
}
|
|
|
|
function mergeSelection(a, b) {
|
|
if (!a) {
|
|
return (0, _objectSpread2["default"])({}, b, {
|
|
conditional: true
|
|
});
|
|
}
|
|
|
|
return (0, _objectSpread2["default"])({}, a, {
|
|
nodeSelections: a.nodeSelections ? mergeSelections(a.nodeSelections, nullthrows(b.nodeSelections)) : null,
|
|
conditional: a.conditional && b.conditional
|
|
});
|
|
}
|
|
|
|
function mergeSelections(a, b) {
|
|
var merged = new Map();
|
|
var _iteratorNormalCompletion = true;
|
|
var _didIteratorError = false;
|
|
var _iteratorError = undefined;
|
|
|
|
try {
|
|
for (var _iterator = a.entries()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
|
|
var _step$value = _step.value,
|
|
key = _step$value[0],
|
|
value = _step$value[1];
|
|
merged.set(key, value);
|
|
}
|
|
} catch (err) {
|
|
_didIteratorError = true;
|
|
_iteratorError = err;
|
|
} finally {
|
|
try {
|
|
if (!_iteratorNormalCompletion && _iterator["return"] != null) {
|
|
_iterator["return"]();
|
|
}
|
|
} finally {
|
|
if (_didIteratorError) {
|
|
throw _iteratorError;
|
|
}
|
|
}
|
|
}
|
|
|
|
var _iteratorNormalCompletion2 = true;
|
|
var _didIteratorError2 = false;
|
|
var _iteratorError2 = undefined;
|
|
|
|
try {
|
|
for (var _iterator2 = b.entries()[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
|
|
var _step2$value = _step2.value,
|
|
key = _step2$value[0],
|
|
value = _step2$value[1];
|
|
merged.set(key, mergeSelection(a.get(key), value));
|
|
}
|
|
} catch (err) {
|
|
_didIteratorError2 = true;
|
|
_iteratorError2 = err;
|
|
} finally {
|
|
try {
|
|
if (!_iteratorNormalCompletion2 && _iterator2["return"] != null) {
|
|
_iterator2["return"]();
|
|
}
|
|
} finally {
|
|
if (_didIteratorError2) {
|
|
throw _iteratorError2;
|
|
}
|
|
}
|
|
}
|
|
|
|
return merged;
|
|
}
|
|
|
|
function isPlural(node) {
|
|
return Boolean(node.metadata && node.metadata.plural);
|
|
}
|
|
|
|
function createVisitor(options) {
|
|
var state = {
|
|
customScalars: options.customScalars,
|
|
enumsHasteModule: options.enumsHasteModule,
|
|
existingFragmentNames: options.existingFragmentNames,
|
|
generatedFragments: new Set(),
|
|
generatedInputObjectTypes: {},
|
|
optionalInputFields: options.optionalInputFields,
|
|
usedEnums: {},
|
|
usedFragments: new Set(),
|
|
useHaste: options.useHaste,
|
|
useSingleArtifactDirectory: options.useSingleArtifactDirectory,
|
|
noFutureProofEnums: options.noFutureProofEnums
|
|
};
|
|
var hasMatchField = false;
|
|
return {
|
|
leave: {
|
|
Root: function Root(node) {
|
|
var inputVariablesType = generateInputVariablesType(node, state);
|
|
var inputObjectTypes = generateInputObjectTypes(state);
|
|
var responseType = exportType("".concat(node.name, "Response"), selectionsToBabel(node.selections, state, false));
|
|
var operationType = exportType(node.name, exactObjectTypeAnnotation([t.objectTypeProperty(t.identifier('variables'), t.genericTypeAnnotation(t.identifier("".concat(node.name, "Variables")))), t.objectTypeProperty(t.identifier('response'), t.genericTypeAnnotation(t.identifier("".concat(node.name, "Response"))))]));
|
|
var importedTypes = [];
|
|
|
|
if (hasMatchField) {
|
|
importedTypes.push('MatchPointer');
|
|
}
|
|
|
|
return t.program((0, _toConsumableArray2["default"])(getFragmentImports(state)).concat((0, _toConsumableArray2["default"])(getEnumDefinitions(state)), [importedTypes.length ? importTypes(importedTypes, 'relay-runtime') : null], (0, _toConsumableArray2["default"])(inputObjectTypes), [inputVariablesType, responseType, operationType]).filter(Boolean));
|
|
},
|
|
Fragment: function Fragment(node) {
|
|
var selections = flattenArray(node.selections);
|
|
var numConecreteSelections = selections.filter(function (s) {
|
|
return s.concreteType;
|
|
}).length;
|
|
selections = selections.map(function (selection) {
|
|
if (numConecreteSelections <= 1 && isTypenameSelection(selection) && !isAbstractType(node.type)) {
|
|
return [(0, _objectSpread2["default"])({}, selection, {
|
|
concreteType: node.type.toString()
|
|
})];
|
|
}
|
|
|
|
return [selection];
|
|
});
|
|
state.generatedFragments.add(node.name);
|
|
var refTypeName = getRefTypeName(node.name);
|
|
var refType = t.declareExportDeclaration(t.declareOpaqueType(t.identifier(refTypeName), null, t.genericTypeAnnotation(t.identifier('FragmentReference'))));
|
|
var unmasked = node.metadata && node.metadata.mask === false;
|
|
var baseType = selectionsToBabel(selections, state, unmasked, unmasked ? undefined : refTypeName);
|
|
var type = isPlural(node) ? readOnlyArrayOfType(baseType) : baseType;
|
|
var importedTypes = ['FragmentReference'];
|
|
|
|
if (hasMatchField) {
|
|
importedTypes.push('MatchPointer');
|
|
}
|
|
|
|
return t.program((0, _toConsumableArray2["default"])(getFragmentImports(state)).concat((0, _toConsumableArray2["default"])(getEnumDefinitions(state)), [importTypes(importedTypes, 'relay-runtime'), refType, exportType(node.name, type)]));
|
|
},
|
|
InlineFragment: function InlineFragment(node) {
|
|
var typeCondition = node.typeCondition;
|
|
return flattenArray(node.selections).map(function (typeSelection) {
|
|
return isAbstractType(typeCondition) ? (0, _objectSpread2["default"])({}, typeSelection, {
|
|
conditional: true
|
|
}) : (0, _objectSpread2["default"])({}, typeSelection, {
|
|
concreteType: typeCondition.toString()
|
|
});
|
|
});
|
|
},
|
|
Condition: function Condition(node) {
|
|
return flattenArray(node.selections).map(function (selection) {
|
|
return (0, _objectSpread2["default"])({}, selection, {
|
|
conditional: true
|
|
});
|
|
});
|
|
},
|
|
ScalarField: function ScalarField(node) {
|
|
var _node$alias;
|
|
|
|
return [{
|
|
key: (_node$alias = node.alias) !== null && _node$alias !== void 0 ? _node$alias : node.name,
|
|
schemaName: node.name,
|
|
value: transformScalarType(node.type, state)
|
|
}];
|
|
},
|
|
LinkedField: function LinkedField(node) {
|
|
var _node$alias2;
|
|
|
|
return [{
|
|
key: (_node$alias2 = node.alias) !== null && _node$alias2 !== void 0 ? _node$alias2 : node.name,
|
|
schemaName: node.name,
|
|
nodeType: node.type,
|
|
nodeSelections: selectionsToMap(flattenArray(node.selections))
|
|
}];
|
|
},
|
|
MatchField: function MatchField(node) {
|
|
var _node$alias3;
|
|
|
|
hasMatchField = true;
|
|
return [{
|
|
key: (_node$alias3 = node.alias) !== null && _node$alias3 !== void 0 ? _node$alias3 : node.name,
|
|
schemaName: node.name,
|
|
value: t.nullableTypeAnnotation(t.genericTypeAnnotation(t.identifier('MatchPointer')))
|
|
}];
|
|
},
|
|
FragmentSpread: function FragmentSpread(node) {
|
|
state.usedFragments.add(node.name);
|
|
return [{
|
|
key: '__fragments_' + node.name,
|
|
ref: node.name
|
|
}];
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
function selectionsToMap(selections) {
|
|
var map = new Map();
|
|
selections.forEach(function (selection) {
|
|
var previousSel = map.get(selection.key);
|
|
map.set(selection.key, previousSel ? mergeSelection(previousSel, selection) : selection);
|
|
});
|
|
return map;
|
|
}
|
|
|
|
function flattenArray(arrayOfArrays) {
|
|
var result = [];
|
|
arrayOfArrays.forEach(function (array) {
|
|
return result.push.apply(result, (0, _toConsumableArray2["default"])(array));
|
|
});
|
|
return result;
|
|
}
|
|
|
|
function generateInputObjectTypes(state) {
|
|
return Object.keys(state.generatedInputObjectTypes).map(function (typeIdentifier) {
|
|
var inputObjectType = state.generatedInputObjectTypes[typeIdentifier];
|
|
!(typeof inputObjectType !== 'string') ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayCompilerFlowGenerator: Expected input object type to have been' + ' defined before calling `generateInputObjectTypes`') : invariant(false) : void 0;
|
|
return exportType(typeIdentifier, inputObjectType);
|
|
});
|
|
}
|
|
|
|
function generateInputVariablesType(node, state) {
|
|
return exportType("".concat(node.name, "Variables"), exactObjectTypeAnnotation(node.argumentDefinitions.map(function (arg) {
|
|
var property = t.objectTypeProperty(t.identifier(arg.name), transformInputType(arg.type, state));
|
|
|
|
if (!(arg.type instanceof GraphQLNonNull)) {
|
|
property.optional = true;
|
|
}
|
|
|
|
return property;
|
|
})));
|
|
}
|
|
|
|
function groupRefs(props) {
|
|
var result = [];
|
|
var refs = [];
|
|
props.forEach(function (prop) {
|
|
if (prop.ref) {
|
|
refs.push(prop.ref);
|
|
} else {
|
|
result.push(prop);
|
|
}
|
|
});
|
|
|
|
if (refs.length > 0) {
|
|
var value = intersectionTypeAnnotation(refs.map(function (ref) {
|
|
return t.genericTypeAnnotation(t.identifier(getRefTypeName(ref)));
|
|
}));
|
|
result.push({
|
|
key: '$fragmentRefs',
|
|
conditional: false,
|
|
value: value
|
|
});
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function getFragmentImports(state) {
|
|
var imports = [];
|
|
|
|
if (state.usedFragments.size > 0) {
|
|
var usedFragments = Array.from(state.usedFragments).sort();
|
|
var _iteratorNormalCompletion3 = true;
|
|
var _didIteratorError3 = false;
|
|
var _iteratorError3 = undefined;
|
|
|
|
try {
|
|
for (var _iterator3 = usedFragments[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
|
|
var usedFragment = _step3.value;
|
|
var refTypeName = getRefTypeName(usedFragment);
|
|
|
|
if (!state.generatedFragments.has(usedFragment)) {
|
|
if (state.useHaste && state.existingFragmentNames.has(usedFragment)) {
|
|
// TODO(T22653277) support non-haste environments when importing
|
|
// fragments
|
|
imports.push(importTypes([refTypeName], usedFragment + '.graphql'));
|
|
} else if (state.useSingleArtifactDirectory && state.existingFragmentNames.has(usedFragment)) {
|
|
imports.push(importTypes([refTypeName], './' + usedFragment + '.graphql'));
|
|
} else {
|
|
imports.push(anyTypeAlias(refTypeName));
|
|
}
|
|
}
|
|
}
|
|
} catch (err) {
|
|
_didIteratorError3 = true;
|
|
_iteratorError3 = err;
|
|
} finally {
|
|
try {
|
|
if (!_iteratorNormalCompletion3 && _iterator3["return"] != null) {
|
|
_iterator3["return"]();
|
|
}
|
|
} finally {
|
|
if (_didIteratorError3) {
|
|
throw _iteratorError3;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return imports;
|
|
}
|
|
|
|
function getEnumDefinitions(_ref2) {
|
|
var enumsHasteModule = _ref2.enumsHasteModule,
|
|
usedEnums = _ref2.usedEnums,
|
|
noFutureProofEnums = _ref2.noFutureProofEnums;
|
|
var enumNames = Object.keys(usedEnums).sort();
|
|
|
|
if (enumNames.length === 0) {
|
|
return [];
|
|
}
|
|
|
|
if (enumsHasteModule) {
|
|
return [importTypes(enumNames, enumsHasteModule)];
|
|
}
|
|
|
|
return enumNames.map(function (name) {
|
|
var values = usedEnums[name].getValues().map(function (_ref3) {
|
|
var value = _ref3.value;
|
|
return value;
|
|
});
|
|
values.sort();
|
|
|
|
if (!noFutureProofEnums) {
|
|
values.push('%future added value');
|
|
}
|
|
|
|
return exportType(name, t.unionTypeAnnotation(values.map(function (value) {
|
|
return t.stringLiteralTypeAnnotation(value);
|
|
})));
|
|
});
|
|
}
|
|
|
|
function getRefTypeName(name) {
|
|
return "".concat(name, "$ref");
|
|
}
|
|
|
|
var FLOW_TRANSFORMS = [RelayRelayDirectiveTransform.transform, RelayMaskTransform.transform, RelayMatchTransform.transform, FlattenTransform.transformWithOptions({}), RelayRefetchableFragmentTransform.transform];
|
|
module.exports = {
|
|
generate: Profiler.instrument(generate, 'RelayFlowGenerator.generate'),
|
|
transforms: FLOW_TRANSFORMS
|
|
}; |