UNPKG

@eddeee888/gcg-typescript-resolver-files

Version:

This [GraphQL Code Generator](https://www.the-guild.dev/graphql/codegen) plugin creates resolvers given GraphQL schema.

199 lines 10.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.postProcessFiles = void 0; const tslib_1 = require("tslib"); const path = tslib_1.__importStar(require("path")); const utils_1 = require("../utils"); const getVariableStatementWithExpectedIdentifier_1 = require("./getVariableStatementWithExpectedIdentifier"); const addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented_1 = require("./addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented"); const ensureEnumTypeResolversAreGenerated_1 = require("./ensureEnumTypeResolversAreGenerated"); const getImportStatementWithExpectedNamedImport_1 = require("./getImportStatementWithExpectedNamedImport"); /** * postProcessFiles does static analysis on existing files OR to-be-generated files * e.g. * - Make sure correct variables are exported * - Make sure object types have field resolvers if mapper type's field cannot be used as schema type's field */ const postProcessFiles = ({ config: { tsMorph: { project }, fixObjectTypeResolvers, }, result, }) => { const sourceFilesToProcess = []; // 1. Load resolver files into ts-morph project so we can run static analysis Object.entries(result.files).forEach(([filePath, file]) => { if (file.__filetype === 'file') { return; } const existingSourceFile = project.addSourceFileAtPathIfExists(filePath); if (existingSourceFile) { file.filesystem = { type: 'filesystem', contentUpdated: false, }; sourceFilesToProcess.push({ sourceFile: existingSourceFile, resolverFile: file, }); return; } // If cannot find existing source files, load files that need post-processing into sourceFilesToProcess if (file.__filetype === 'objectType') { const virtualSourceFile = project.createSourceFile(filePath, file.content); sourceFilesToProcess.push({ sourceFile: virtualSourceFile, resolverFile: file, }); } }); // `addedPropertyAssignmentNodes` is used to store added property assignments in object types // these property assigments are to be removed if there's no TypeScript error const addedPropertyAssignmentNodes = {}; sourceFilesToProcess.forEach(({ sourceFile, resolverFile }) => { const normalizedRelativePath = path.posix.relative((0, utils_1.cwd)(), sourceFile.getFilePath()); const { addedVariableStatement } = ensureExportedResolver(sourceFile, resolverFile); if (resolverFile.__filetype !== 'scalarResolver' || // For scalarResolver, only add `import { GraphQLScalarType } ...` if variable statement was added. // This is because user could have used custom scalar. If so, we don't want to add unnecessary import (resolverFile.__filetype === 'scalarResolver' && addedVariableStatement)) { ensureImportedType(sourceFile, resolverFile); } if (fixObjectTypeResolvers.object === 'smart' && resolverFile.__filetype === 'objectType') { (0, addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented_1.addObjectTypeResolversPropertyAssignmentNodesIfNotImplemented)({ addedPropertyAssignmentNodes, sourceFile, resolverFile, }); } if (fixObjectTypeResolvers.enum === 'smart' && resolverFile.__filetype === 'enumResolver') { (0, ensureEnumTypeResolversAreGenerated_1.ensureEnumTypeResolversAreGenerated)(sourceFile, resolverFile); } // Overwrite existing files with fixes result.files[normalizedRelativePath] = Object.assign(Object.assign({}, resolverFile), { content: sourceFile.getText() }); }); // 3. ensure object type's added property assignments are removed if there's no related TypeScript error // We do this only once at the project level instead of sourceFile level to speed up the process if (fixObjectTypeResolvers.object === 'smart') { project.getPreEmitDiagnostics().forEach((d) => { var _a; const filename = (_a = d.getSourceFile()) === null || _a === void 0 ? void 0 : _a.getFilePath().toString(); if (!filename || !addedPropertyAssignmentNodes[filename]) { return; } const lineNumberWithError = d.getLineNumber(); // If erroring on a recently added line, do not remove as user needs to implement it if (lineNumberWithError && addedPropertyAssignmentNodes[filename][lineNumberWithError]) { addedPropertyAssignmentNodes[filename][lineNumberWithError].__toBeRemoved = false; } }); Object.values(addedPropertyAssignmentNodes).forEach((addedNodes) => { Object.values(addedNodes).forEach(({ node, resolverFile, __toBeRemoved }) => { if (__toBeRemoved) { node.remove(); } else { // If found a property assignment that cannot be removed i.e. incompatible types between mapper vs schema types // Then we must mark the content as updated to be added to generate list resolverFile.filesystem.contentUpdated = true; } }); }); } // 4. Apply to result files the updated content done in step 2. and 3. above sourceFilesToProcess.forEach(({ sourceFile, resolverFile }) => { const normalizedRelativePath = path.posix.relative((0, utils_1.cwd)(), sourceFile.getFilePath()); // Overwrite existing files with fixes result.files[normalizedRelativePath] = Object.assign(Object.assign({}, resolverFile), { content: sourceFile.getText() }); }); }; exports.postProcessFiles = postProcessFiles; /** * Ensure correctly named resolvers are exported */ const ensureExportedResolver = (sourceFile, resolverFile) => { var _a; const { variableStatement, isExported } = (0, getVariableStatementWithExpectedIdentifier_1.getVariableStatementWithExpectedIdentifier)(sourceFile, resolverFile); /** * If we found the variable statement replace its type with the expected resolver type string * * This is because we change the type of the resolver in some cases: * 1. When `extend type <Object>` is used, we might change its original type to the picked version * e.g. `Book` might become `Pick<Book, 'title' | 'author'>` */ let ensureCorrectResolverType = undefined; if (variableStatement && ((_a = resolverFile.meta.resolverType) === null || _a === void 0 ? void 0 : _a.final)) { const variableDeclaration = variableStatement .getDeclarationList() .getDeclarations()[0]; const typeNode = variableDeclaration === null || variableDeclaration === void 0 ? void 0 : variableDeclaration.getTypeNode(); ensureCorrectResolverType = typeNode ? () => { const trimTypeString = (value) => value.replace(/[\n\r\s]/g, ''); const trimmedOriginalTypeString = trimTypeString(typeNode.getText()); typeNode.replaceWithText(resolverFile.meta.resolverType.final); // If the formatted type is the semantically the same but formatted differently from `resolverType.final` // e.g. // ``` // Pick< // BookResolvers, // | 'title' // | 'author' // >` // vs // `Pick<Book, 'author' | 'title'>` // ``` const newTypeStringVariants = 'otherVariants' in resolverFile.meta.resolverType ? [ resolverFile.meta.resolverType.final, ...resolverFile.meta.resolverType.otherVariants, ] : [resolverFile.meta.resolverType.final]; const trimmedNewTypeStringVariants = newTypeStringVariants.map(trimTypeString); if (!trimmedNewTypeStringVariants.find((newText) => newText === trimmedOriginalTypeString)) { resolverFile.filesystem.contentUpdated = true; } } : () => { variableDeclaration.setType(resolverFile.meta.resolverType.final); resolverFile.filesystem.contentUpdated = true; }; } // For non-scalarResolver, ensure correct type is imported // For scalarResolver, we don't need to add type to the variable statement for a few reasons: // - For cases when we need to create a new GraphQLScalarType, it infer the type from `new GraphQLScalarType` // - For cases when there's custom user logic, it's up to the user to import the correct type or call `new GraphQLScalarType` by themselves if (resolverFile.__filetype !== 'scalarResolver' && ensureCorrectResolverType) { ensureCorrectResolverType(); } if (!variableStatement) { // Did not find variable statement with expected identifier, add it to the end with a warning sourceFile.addStatements(resolverFile.meta.variableStatement); resolverFile.filesystem.contentUpdated = true; return { addedVariableStatement: true }; } else if (variableStatement && !isExported) { // If has identifier but not exported // Add export keyword to statement const isExpectedIdentifierExported = Boolean(sourceFile .getExportedDeclarations() .get(resolverFile.mainImportIdentifier)); if (!isExpectedIdentifierExported) { variableStatement.setIsExported(true); resolverFile.filesystem.contentUpdated = true; } // else, if identifier's been exported do nothing return { addedVariableStatement: false }; } return { addedVariableStatement: false }; }; /** * Ensure correctly imported resolves type generated by typescript-resolvers plugin */ const ensureImportedType = (sourceFile, resolverFile) => { const { importDeclaration } = (0, getImportStatementWithExpectedNamedImport_1.getImportStatementWithExpectedNamedImport)(sourceFile, resolverFile); if (!importDeclaration) { sourceFile.insertStatements(0, resolverFile.meta.resolverTypeImportDeclaration); resolverFile.filesystem.contentUpdated = true; } }; //# sourceMappingURL=postProcessFiles.js.map