@graphql-codegen/near-operation-file-preset
Version:
GraphQL Code Generator preset for generating operation code near the operation file
155 lines (154 loc) • 7.22 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.defineFilepathSubfolder = defineFilepathSubfolder;
exports.appendFileNameToFilePath = appendFileNameToFilePath;
exports.analyzeFragmentUsage = analyzeFragmentUsage;
const tslib_1 = require("tslib");
const path_1 = require("path");
const graphql_1 = require("graphql");
const parse_filepath_1 = tslib_1.__importDefault(require("parse-filepath"));
const visitor_plugin_common_1 = require("@graphql-codegen/visitor-plugin-common");
function defineFilepathSubfolder(baseFilePath, folder) {
const parsedPath = (0, parse_filepath_1.default)(baseFilePath);
return (0, path_1.join)(parsedPath.dir, folder, parsedPath.base).replace(/\\/g, '/');
}
function appendFileNameToFilePath(baseFilePath, fileName, extension) {
const parsedPath = (0, parse_filepath_1.default)(baseFilePath);
const name = fileName || parsedPath.name;
return (0, path_1.join)(parsedPath.dir, name + extension).replace(/\\/g, '/');
}
/**
* Analyzes fragment usage in a GraphQL document.
* Returns information about which fragments are used and which specific types they're used with.
*/
function analyzeFragmentUsage(documentNode, fragmentRegistry, schema) {
const localFragments = getLocalFragments(documentNode);
const fragmentsInUse = extractExternalFragmentsInUse(documentNode, fragmentRegistry, localFragments);
const usedFragmentTypes = analyzeFragmentTypeUsage(documentNode, fragmentRegistry, schema, localFragments, fragmentsInUse);
const operationFragments = collectOperationFragments(documentNode, fragmentRegistry, localFragments);
return { fragmentsInUse, usedFragmentTypes, operationFragments };
}
/**
* External fragments transitively reachable from the document's operations (graphQLTag operations
* interpolate all of these). Steps through local fragments too, since an operation may reach an
* external fragment via a local one.
*/
function collectOperationFragments(documentNode, fragmentRegistry, localFragments) {
const reachable = new Set();
const visited = new Set();
const collect = (node) => {
(0, graphql_1.visit)(node, {
FragmentSpread: ({ name: { value } }) => {
var _a, _b;
if (fragmentRegistry[value])
reachable.add(value);
if (visited.has(value))
return;
visited.add(value);
const fragmentNode = (_b = (_a = fragmentRegistry[value]) === null || _a === void 0 ? void 0 : _a.node) !== null && _b !== void 0 ? _b : localFragments.get(value);
if (fragmentNode)
collect(fragmentNode);
},
});
};
for (const definition of documentNode.definitions) {
if (definition.kind === graphql_1.Kind.OPERATION_DEFINITION)
collect(definition);
}
return reachable;
}
/**
* Get all fragment definitions that are local to this document, keyed by name
*/
function getLocalFragments(documentNode) {
const localFragments = new Map();
(0, graphql_1.visit)(documentNode, {
FragmentDefinition: node => {
localFragments.set(node.name.value, node);
},
});
return localFragments;
}
function extractExternalFragmentsInUse(documentNode, fragmentNameToFile, localFragment, result = {}, level = 0) {
// Then, look for all used fragments in this document
(0, graphql_1.visit)(documentNode, {
FragmentSpread: node => {
if (!localFragment.has(node.name.value) &&
(result[node.name.value] === undefined || level < result[node.name.value])) {
result[node.name.value] = level;
if (fragmentNameToFile[node.name.value]) {
extractExternalFragmentsInUse(fragmentNameToFile[node.name.value].node, fragmentNameToFile, localFragment, result, level + 1);
}
}
},
});
return result;
}
/**
* Analyze which specific types each fragment is used with (for polymorphic fragments)
*/
function analyzeFragmentTypeUsage(documentNode, fragmentRegistry, schema, localFragments, fragmentsInUse) {
const usedFragmentTypes = {};
const typeInfo = new graphql_1.TypeInfo(schema);
(0, graphql_1.visit)(documentNode, (0, graphql_1.visitWithTypeInfo)(typeInfo, {
Field: (node) => {
if (!node.selectionSet)
return;
const fieldType = typeInfo.getType();
if (!fieldType)
return;
const baseType = getBaseType(fieldType);
if ((0, graphql_1.isObjectType)(baseType) || (0, graphql_1.isInterfaceType)(baseType) || (0, graphql_1.isUnionType)(baseType)) {
analyzeSelectionSetTypeContext(node.selectionSet, baseType.name, usedFragmentTypes, fragmentRegistry, schema, localFragments);
}
},
}));
const result = {};
// Fill in missing types for multi-type fragments
for (const fragmentName in fragmentsInUse) {
const fragment = fragmentRegistry[fragmentName];
if (!fragment || fragment.possibleTypes.length <= 1)
continue;
const usedTypes = usedFragmentTypes[fragmentName];
result[fragmentName] = (usedTypes === null || usedTypes === void 0 ? void 0 : usedTypes.size) > 0 ? Array.from(usedTypes) : fragment.possibleTypes;
}
return result;
}
/**
* Analyze fragment usage within a specific selection set and type context
*/
function analyzeSelectionSetTypeContext(selectionSet, currentTypeName, usedFragmentTypes, fragmentRegistry, schema, localFragments) {
var _a, _b;
var _c;
const { spreads, inlines } = (0, visitor_plugin_common_1.separateSelectionSet)(selectionSet.selections);
// Process fragment spreads in this type context
for (const spread of spreads) {
if (localFragments.has(spread.name.value))
continue;
const fragment = fragmentRegistry[spread.name.value];
if (!fragment || fragment.possibleTypes.length <= 1)
continue;
const currentType = schema.getType(currentTypeName);
if (!currentType)
continue;
const possibleTypes = (0, visitor_plugin_common_1.getPossibleTypes)(schema, currentType).map(t => t.name);
const matchingTypes = possibleTypes.filter(type => fragment.possibleTypes.includes(type));
if (matchingTypes.length > 0) {
const typeSet = ((_a = usedFragmentTypes[_c = spread.name.value]) !== null && _a !== void 0 ? _a : (usedFragmentTypes[_c] = new Set()));
matchingTypes.forEach(type => typeSet.add(type));
}
}
// Process inline fragments
for (const inline of inlines) {
if (((_b = inline.typeCondition) === null || _b === void 0 ? void 0 : _b.name.value) && inline.selectionSet) {
analyzeSelectionSetTypeContext(inline.selectionSet, inline.typeCondition.name.value, usedFragmentTypes, fragmentRegistry, schema, localFragments);
}
}
}
function getBaseType(type) {
let baseType = type;
while ('ofType' in baseType && baseType.ofType) {
baseType = baseType.ofType;
}
return baseType;
}