UNPKG

genezio

Version:

Command line utility to interact with Genezio infrastructure.

256 lines (255 loc) 12.5 kB
import path from "path"; import fs from "fs"; import { isCallExpression, isIdentifier, isClassDeclaration, isObjectExpression, isObjectProperty, isStringLiteral, isExportDefaultDeclaration, isExportNamedDeclaration, isNumericLiteral, } from "@babel/types"; import babel from "@babel/core"; import { createRequire } from "module"; import { debugLogger } from "../logging.js"; import { DecoratorExtractor } from "./baseDecoratorExtractor.js"; export class JsTsDecoratorExtractor extends DecoratorExtractor { fileFilter(cwd) { return (file) => { const folderPath = path.join(cwd, file.path); return ((file.extension === ".js" || file.extension === ".ts") && !file.path.includes("node_modules") && !file.path.includes(".git") && !fs.lstatSync(folderPath).isDirectory()); }; } async getDecoratorsFromFile(file) { const babelClasses = await this.getDecoratorsBabel(file); return [...babelClasses]; } async getDecoratorsBabel(file) { const inputCode = fs.readFileSync(file, "utf8"); const classes = []; const extractorFunction = function extract() { return { name: "extract-decorators", visitor: { ClassDeclaration(path) { if (path.node.decorators) { const info = { path: file, name: path.node.id?.name ?? "default", decorators: [], methods: [], }; for (const decorator of path.node.decorators) { if (isIdentifier(decorator.expression)) { info.decorators = [ ...(info.decorators ?? []), { name: decorator.expression.name }, ]; } else if (isCallExpression(decorator.expression) && isIdentifier(decorator.expression.callee)) { info.decorators = [ ...(info.decorators ?? []), { name: decorator.expression.callee.name, arguments: JsTsDecoratorExtractor.parseArguments(decorator.expression.arguments), }, ]; } } classes.push(info); } }, ClassMethod(path) { if (path.node.decorators) { const classDeclarationNode = path.context.parentPath.parentPath?.node; const className = isClassDeclaration(classDeclarationNode) ? (classDeclarationNode.id?.name ?? "defaultClass") : "defaultClass"; const methodIdentifierNode = path.node.key; const methodName = isIdentifier(methodIdentifierNode) ? methodIdentifierNode.name : "defaultMethod"; for (const decorator of path.node.decorators) { const info = { name: methodName, decorators: [], }; let existingClass = classes.find((c) => c.name === className); if (!existingClass) { existingClass = { path: file, name: className, decorators: [], methods: [], }; classes.push(existingClass); } if (isIdentifier(decorator.expression)) { info.decorators = [ ...(info.decorators ?? []), { name: decorator.expression.name }, ]; } else if (isCallExpression(decorator.expression) && isIdentifier(decorator.expression.callee)) { info.decorators = [ ...(info.decorators ?? []), { name: decorator.expression.callee.name, arguments: JsTsDecoratorExtractor.parseArguments(decorator.expression.arguments), }, ]; } existingClass.methods.push(info); } } }, }, }; }; const extractorFunctionComments = function extract() { return { name: "extract-comments", visitor: { ExportDeclaration(path) { if (isExportDefaultDeclaration(path.node) || isExportNamedDeclaration(path.node)) { const classDeclarationNode = path.node.declaration; if (isClassDeclaration(classDeclarationNode)) { const className = classDeclarationNode.id?.name ?? "default"; const comments = path.node.leadingComments; if (comments && comments.length > 0) { const lastComment = comments[comments.length - 1]; if (lastComment.value.includes("genezio: deploy") && lastComment.type === "CommentLine") { const classInfo = DecoratorExtractor.createGenezioClassInfo(className, file, lastComment.value); classes.push(classInfo); } } } } }, ClassDeclaration(path) { if (path.node.leadingComments) { const leadingComments = path.node.leadingComments; if (leadingComments.length > 0) { const lastComment = leadingComments[leadingComments.length - 1]; if (lastComment.value.includes("genezio: deploy") && lastComment.type === "CommentLine") { const className = path.node.id?.name ?? "default"; const classInfo = DecoratorExtractor.createGenezioClassInfo(className, file, lastComment.value); classes.push(classInfo); } } } }, ClassMethod(path) { const classDeclarationNode = path.context.parentPath.parentPath?.node; const className = isClassDeclaration(classDeclarationNode) ? (classDeclarationNode.id?.name ?? "defaultClass") : "defaultClass"; const methodIdentifierNode = path.node.key; const methodName = isIdentifier(methodIdentifierNode) ? methodIdentifierNode.name : "defaultMethod"; let existingClass = classes.find((c) => c.name === className); if (!existingClass) { existingClass = { path: file, name: className, decorators: [], methods: [], }; classes.push(existingClass); } if (path.node.leadingComments) { const leadingComments = path.node.leadingComments; if (leadingComments.length > 0) { const lastComment = leadingComments[leadingComments.length - 1]; if (lastComment.value.includes("genezio:") && lastComment.type === "CommentLine") { const methodInfo = DecoratorExtractor.createGenezioMethodInfo(methodName, lastComment.value); if (methodInfo) { existingClass.methods.push(methodInfo); } } } } }, }, }; }; const require = createRequire(import.meta.url); const packagePath = path.dirname(require.resolve("@babel/plugin-syntax-decorators")); const packageReactPath = path.dirname(require.resolve("@babel/preset-react")); await babel .transformAsync(inputCode, { presets: [ [require.resolve("@babel/preset-typescript"), { allowDeclareFields: true }], [packageReactPath], ], plugins: [ [packagePath, { version: "2023-05", decoratorsBeforeExport: false }], extractorFunction, ], filename: file, configFile: false, }) .catch((error) => { if (error.reasonCode == "MissingOneOfPlugins") { debugLogger.error(`Error while parsing the file ${file}`, error); return []; } else { throw error; } }); await babel .transformAsync(inputCode, { presets: [ [require.resolve("@babel/preset-typescript"), { allowDeclareFields: true }], [packageReactPath], ], plugins: [ [packagePath, { version: "2023-05", decoratorsBeforeExport: false }], extractorFunctionComments, ], filename: file, configFile: false, }) .catch((error) => { if (error.reasonCode == "MissingOneOfPlugins") { debugLogger.error(`Error while parsing the file ${file}`, error); return []; } else { throw error; } }); return classes; } static parseArguments(args) { if (!args) { return {}; } return args .map((arg) => { if (isObjectExpression(arg)) { return arg.properties.reduce((acc, curr) => { if (isObjectProperty(curr) && (isIdentifier(curr.key) || isStringLiteral(curr.key)) && (isStringLiteral(curr.value) || isNumericLiteral(curr.value))) { const key = isIdentifier(curr.key) ? curr.key.name : curr.key.value; return [...acc, { key, value: curr.value.value }]; } return [...acc]; }, []); } else { return undefined; } }) .filter((a) => a !== undefined) .flat() .reduce((acc, curr) => { acc[curr.key] = curr.value; return acc; }, {}); } }