UNPKG

@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
"use strict"; 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; }