From bd023efa1c7074cbf3a09e78d7c67cfc9f3e3aa6 Mon Sep 17 00:00:00 2001 From: Swastik Singh Date: Sun, 8 Oct 2023 19:05:47 +0530 Subject: [PATCH 1/3] refactor: generate function refactored to reduce complexity --- lib/generator.js | 104 +++++++++++++++++++++++++++++++++++++++++++++- package-lock.json | 11 +++-- 2 files changed, 110 insertions(+), 5 deletions(-) diff --git a/lib/generator.js b/lib/generator.js index dd5bff8b4..cf8ade40f 100644 --- a/lib/generator.js +++ b/lib/generator.js @@ -160,7 +160,7 @@ class Generator { * @return {Promise} */ // eslint-disable-next-line sonarjs/cognitive-complexity - async generate(asyncapiDocument, parseOptions = {}) { + /* async generate(asyncapiDocument, parseOptions = {}) { const isAlreadyParsedDocument = isAsyncAPIDocument(asyncapiDocument); const isParsableCompatible = asyncapiDocument && typeof asyncapiDocument === 'string'; if (!isAlreadyParsedDocument && !isParsableCompatible) { @@ -207,6 +207,108 @@ class Generator { await this.generateDirectoryStructure(this.asyncapi); await this.launchHook('generate:after'); } + } */ + + // Main method for generating code based on AsyncAPI document + async generate(asyncapiDocument, parseOptions = {}) { + this.validateAsyncAPIDocument(asyncapiDocument); + this.setupOutput(); + this.setLogLevel(); + + const templateInfo = await this.installAndSetupTemplate(); + await this.configureTemplateWorkflow(parseOptions); + await this.handleEntrypoint(); + await this.executeAfterHook(); + } + + // Validate the AsyncAPI document + validateAsyncAPIDocument(asyncapiDocument) { + const isAlreadyParsedDocument = isAsyncAPIDocument(asyncapiDocument); + const isParsableCompatible = asyncapiDocument && typeof asyncapiDocument === 'string'; + + if (!isAlreadyParsedDocument && !isParsableCompatible) { + throw new Error('Parameter "asyncapiDocument" must be a non-empty string or an already parsed AsyncAPI document.'); + } + + this.asyncapi = this.originalAsyncAPI = asyncapiDocument; + } + + // Setup the output based on the configured options + setupOutput() { + if (this.output === 'fs') { + this.setupFSOutput(); + } else if (this.output === 'string' && this.entrypoint === undefined) { + throw new Error('Parameter entrypoint is required when using output = "string"'); + } + } + + // Setup file system output + async setupFSOutput() { + // Create directory if not exists + xfs.mkdirpSync(this.targetDir); + + // Verify target directory if forceWrite is not enabled + if (!this.forceWrite) { + await this.verifyTargetDir(this.targetDir); + } + } + + // Set log level based on debug option + setLogLevel() { + if (this.debug) log.setLevel('debug'); + } + + // Install and setup the template + async installAndSetupTemplate() { + const { name: templatePkgName, path: templatePkgPath } = await this.installTemplate(this.install); + + this.templateDir = templatePkgPath; + this.templateName = templatePkgName; + this.templateContentDir = path.resolve(this.templateDir, TEMPLATE_CONTENT_DIRNAME); + + await this.loadTemplateConfig(); + + return { templatePkgName, templatePkgPath }; + } + + // Configure the template workflow + async configureTemplateWorkflow(parseOptions) { + // Parse input and validate template configuration + await this.parseInput(this.asyncapi, parseOptions); + validateTemplateConfig(this.templateConfig, this.templateParams, this.asyncapi); + await this.configureTemplate(); + + if (!isReactTemplate(this.templateConfig)) { + await registerFilters(this.nunjucks, this.templateConfig, this.templateDir, FILTERS_DIRNAME); + } + + await registerHooks(this.hooks, this.templateConfig, this.templateDir, HOOKS_DIRNAME); + await this.launchHook('generate:before'); + } + + // Handle the entrypoint logic + async handleEntrypoint() { + if (this.entrypoint) { + const entrypointPath = path.resolve(this.templateContentDir, this.entrypoint); + + if (!(await exists(entrypointPath))) { + throw new Error(`Template entrypoint "${entrypointPath}" couldn't be found.`); + } + + if (this.output === 'fs') { + await this.generateFile(this.asyncapi, path.basename(entrypointPath), path.dirname(entrypointPath)); + await this.launchHook('generate:after'); + } else if (this.output === 'string') { + return this.renderFile(this.asyncapi, entrypointPath); + } + } else { + await this.generateDirectoryStructure(this.asyncapi); + } + } + + // Execute the after-hook + async executeAfterHook() { + await this.launchHook('generate:after'); } /** diff --git a/package-lock.json b/package-lock.json index 8721c9b93..b135420dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,6 +58,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -369,6 +370,7 @@ "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.20.tgz", "integrity": "sha512-Y6jd1ahLubuYweD/zJH+vvOY141v4f9igNQAQ+MBgq9JlHS2iTsZKn1aMsb3vGccZsXI16VzTBw52Xx0DWmtnA==", + "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.13", @@ -398,6 +400,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "bin": { "semver": "bin/semver.js" } @@ -3321,7 +3324,7 @@ "version": "7.20.2", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.2.tgz", "integrity": "sha512-pNpr1T1xLUc2l3xJKuPtsEky3ybxN3m4fJkknfIpTCTfIZCDW57oAg+EfCgIIp2rvCe0Wn++/FfodDS4YXxBwA==", - "devOptional": true, + "dev": true, "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -3334,7 +3337,7 @@ "version": "7.6.5", "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.5.tgz", "integrity": "sha512-h9yIuWbJKdOPLJTbmSpPzkF67e659PbQDba7ifWm5BJ8xTv+sDmS7rFmywkWOvXedGTivCdeGSIIX8WLcRTz8w==", - "devOptional": true, + "dev": true, "dependencies": { "@babel/types": "^7.0.0" } @@ -3343,7 +3346,7 @@ "version": "7.4.2", "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.2.tgz", "integrity": "sha512-/AVzPICMhMOMYoSx9MoKpGDKdBRsIXMNByh1PXSZoa+v6ZoLa8xxtsT/uLQ/NJm0XVAWl/BvId4MlDeXJaeIZQ==", - "devOptional": true, + "dev": true, "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" @@ -3353,7 +3356,7 @@ "version": "7.20.2", "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.2.tgz", "integrity": "sha512-ojlGK1Hsfce93J0+kn3H5R73elidKUaZonirN33GSmgTUMpzI/MIFfSpF3haANe3G1bEBS9/9/QEqwTzwqFsKw==", - "devOptional": true, + "dev": true, "dependencies": { "@babel/types": "^7.20.7" } From 4df68bc404f30784dc1039d91327d2d4f5364836 Mon Sep 17 00:00:00 2001 From: Swastik Singh Date: Mon, 9 Oct 2023 17:52:53 +0530 Subject: [PATCH 2/3] refactor: generate function refactored to reduce cognitive complexity --- lib/generator.js | 206 +++++++++++++++++++++++++---------------------- 1 file changed, 111 insertions(+), 95 deletions(-) diff --git a/lib/generator.js b/lib/generator.js index cf8ade40f..7cc18a33a 100644 --- a/lib/generator.js +++ b/lib/generator.js @@ -136,104 +136,62 @@ class Generator { }); } + // eslint-disable-next-line sonarjs/cognitive-complexity + /** * Generates files from a given template and an AsyncAPIDocument object. * + * @async * @example - * generator - * .generate(myAsyncAPIdocument) - * .then(() => { - * console.log('Done!'); - * }) - * .catch(console.error); - * - * @example Using async/await - * try { - * await generator.generate(myAsyncAPIdocument); - * console.log('Done!'); - * } catch (e) { - * console.error(e); - * } - * - * @param {AsyncAPIDocument | string} asyncapiDocument AsyncAPIDocument object to use as source. - * @param {Object} [parseOptions={}] AsyncAPI Parser parse options. Check out {@link https://www.github.com/asyncapi/parser-js|@asyncapi/parser} for more information. Remember to use the right options to the right parser depending on the template you are using. - * @return {Promise} + * await generator.generate(myAsyncAPIdocument); + * console.log('Done!'); + * + * @param {AsyncAPIDocument | string} asyncapiDocument - AsyncAPIDocument object to use as source. + * @param {Object} [parseOptions={}] - AsyncAPI Parser parse options. + * Check out {@link https://www.github.com/asyncapi/parser-js|@asyncapi/parser} for more information. + * Remember to use the right options for the right parser depending on the template you are using. + * @return {Promise} A Promise that resolves when the generation is completed. */ - // eslint-disable-next-line sonarjs/cognitive-complexity - /* async generate(asyncapiDocument, parseOptions = {}) { - const isAlreadyParsedDocument = isAsyncAPIDocument(asyncapiDocument); - const isParsableCompatible = asyncapiDocument && typeof asyncapiDocument === 'string'; - if (!isAlreadyParsedDocument && !isParsableCompatible) { - throw new Error('Parameter "asyncapiDocument" must be a non-empty string or an already parsed AsyncAPI document.'); - } - this.asyncapi = this.originalAsyncAPI = asyncapiDocument; - - if (this.output === 'fs') { - xfs.mkdirpSync(this.targetDir); - if (!this.forceWrite) await this.verifyTargetDir(this.targetDir); - } else if (this.output === 'string' && this.entrypoint === undefined) { - throw new Error('Parameter entrypoint is required when using output = "string"'); - } - - if (this.debug) log.setLevel('debug'); - - const { name: templatePkgName, path: templatePkgPath } = await this.installTemplate(this.install); - this.templateDir = templatePkgPath; - this.templateName = templatePkgName; - this.templateContentDir = path.resolve(this.templateDir, TEMPLATE_CONTENT_DIRNAME); - await this.loadTemplateConfig(); - - await this.parseInput(this.asyncapi, parseOptions); - - validateTemplateConfig(this.templateConfig, this.templateParams, this.asyncapi); - await this.configureTemplate(); - - if (!isReactTemplate(this.templateConfig)) { - await registerFilters(this.nunjucks, this.templateConfig, this.templateDir, FILTERS_DIRNAME); - } - await registerHooks(this.hooks, this.templateConfig, this.templateDir, HOOKS_DIRNAME); - await this.launchHook('generate:before'); - - if (this.entrypoint) { - const entrypointPath = path.resolve(this.templateContentDir, this.entrypoint); - if (!(await exists(entrypointPath))) throw new Error(`Template entrypoint "${entrypointPath}" couldn't be found.`); - if (this.output === 'fs') { - await this.generateFile(this.asyncapi, path.basename(entrypointPath), path.dirname(entrypointPath)); - await this.launchHook('generate:after'); - } else if (this.output === 'string') { - return this.renderFile(this.asyncapi, entrypointPath); - } - } else { - await this.generateDirectoryStructure(this.asyncapi); - await this.launchHook('generate:after'); - } - } */ - // Main method for generating code based on AsyncAPI document async generate(asyncapiDocument, parseOptions = {}) { this.validateAsyncAPIDocument(asyncapiDocument); this.setupOutput(); this.setLogLevel(); - - const templateInfo = await this.installAndSetupTemplate(); + + await this.installAndSetupTemplate(); await this.configureTemplateWorkflow(parseOptions); await this.handleEntrypoint(); await this.executeAfterHook(); } - - // Validate the AsyncAPI document + + /** + * Validates the provided AsyncAPI document. + * + * @param {*} asyncapiDocument - The AsyncAPI document to be validated. + * @throws {Error} Throws an error if the document is not valid. + * @since 10/9/2023 - 4:26:33 PM + */ validateAsyncAPIDocument(asyncapiDocument) { const isAlreadyParsedDocument = isAsyncAPIDocument(asyncapiDocument); const isParsableCompatible = asyncapiDocument && typeof asyncapiDocument === 'string'; - + if (!isAlreadyParsedDocument && !isParsableCompatible) { throw new Error('Parameter "asyncapiDocument" must be a non-empty string or an already parsed AsyncAPI document.'); } - + this.asyncapi = this.originalAsyncAPI = asyncapiDocument; } - + // Setup the output based on the configured options + /** + * Sets up the output configuration based on the specified output type. + * + * @example + * const generator = new Generator(); + * generator.setupOutput(); + * + * @throws {Error} If 'output' is set to 'string' without providing 'entrypoint'. + */ setupOutput() { if (this.output === 'fs') { this.setupFSOutput(); @@ -241,60 +199,111 @@ class Generator { throw new Error('Parameter entrypoint is required when using output = "string"'); } } - - // Setup file system output + + /** + * Sets up the file system (FS) output configuration. + * + * This function creates the target directory if it does not exist and verifies + * the target directory if forceWrite is not enabled. + * + * @async + * @returns {Promise} A promise that fulfills when the setup is complete. + * + * @throws {Error} If verification of the target directory fails and forceWrite is not enabled. + */ async setupFSOutput() { // Create directory if not exists xfs.mkdirpSync(this.targetDir); - + // Verify target directory if forceWrite is not enabled - if (!this.forceWrite) { + if (!this.forceWrite) { await this.verifyTargetDir(this.targetDir); } } - - // Set log level based on debug option + + /** + * Sets the log level based on the debug option. + * + * If the debug option is enabled, the log level is set to 'debug'. + * + * @returns {void} + */ setLogLevel() { if (this.debug) log.setLevel('debug'); } - - // Install and setup the template + + /** + * Installs and sets up the template for code generation. + * + * This function installs the specified template using the provided installation option, + * sets up the necessary directory paths, loads the template configuration, and returns + * information about the installed template. + * + * @async + * @returns {Promise<{ templatePkgName: string, templatePkgPath: string }>} + * A promise that resolves to an object containing the name and path of the installed template. + */ async installAndSetupTemplate() { const { name: templatePkgName, path: templatePkgPath } = await this.installTemplate(this.install); - + this.templateDir = templatePkgPath; this.templateName = templatePkgName; this.templateContentDir = path.resolve(this.templateDir, TEMPLATE_CONTENT_DIRNAME); - + await this.loadTemplateConfig(); - + return { templatePkgName, templatePkgPath }; } - - // Configure the template workflow + + /** + * Configures the template workflow based on provided parsing options. + * + * This function performs the following steps: + * 1. Parses the input AsyncAPI document using the specified parse options. + * 2. Validates the template configuration and parameters. + * 3. Configures the template based on the parsed AsyncAPI document. + * 4. Registers filters, hooks, and launches the 'generate:before' hook if applicable. + * + * @async + * @param {*} parseOptions - Options for parsing the AsyncAPI document. + * @returns {Promise} A promise that resolves when the configuration is completed. + */ async configureTemplateWorkflow(parseOptions) { // Parse input and validate template configuration await this.parseInput(this.asyncapi, parseOptions); validateTemplateConfig(this.templateConfig, this.templateParams, this.asyncapi); await this.configureTemplate(); - + if (!isReactTemplate(this.templateConfig)) { await registerFilters(this.nunjucks, this.templateConfig, this.templateDir, FILTERS_DIRNAME); } - + await registerHooks(this.hooks, this.templateConfig, this.templateDir, HOOKS_DIRNAME); await this.launchHook('generate:before'); } - - // Handle the entrypoint logic + + /** + * Handles the logic for the template entrypoint. + * + * If an entrypoint is specified: + * - Resolves the absolute path of the entrypoint file. + * - Throws an error if the entrypoint file doesn't exist. + * - Generates a file or renders content based on the output type. + * - Launches the 'generate:after' hook if the output is 'fs'. + * + * If no entrypoint is specified, generates the directory structure. + * + * @async + * @returns {Promise} A promise that resolves when the entrypoint logic is completed. + */ async handleEntrypoint() { if (this.entrypoint) { const entrypointPath = path.resolve(this.templateContentDir, this.entrypoint); - + if (!(await exists(entrypointPath))) { throw new Error(`Template entrypoint "${entrypointPath}" couldn't be found.`); } - + if (this.output === 'fs') { await this.generateFile(this.asyncapi, path.basename(entrypointPath), path.dirname(entrypointPath)); await this.launchHook('generate:after'); @@ -306,7 +315,14 @@ class Generator { } } - // Execute the after-hook + /** + * Executes the 'generate:after' hook. + * + * Launches the after-hook to perform additional actions after code generation. + * + * @async + * @returns {Promise} A promise that resolves when the after-hook execution is completed. + */ async executeAfterHook() { await this.launchHook('generate:after'); } From 4023882bff47cbf02716c2824a1f8ed5e94697ce Mon Sep 17 00:00:00 2001 From: Swastik Singh Date: Mon, 9 Oct 2023 23:23:44 +0530 Subject: [PATCH 3/3] indentations fixed --- lib/generator.js | 48 ++++++++++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/lib/generator.js b/lib/generator.js index 7cc18a33a..0203f3a64 100644 --- a/lib/generator.js +++ b/lib/generator.js @@ -136,8 +136,6 @@ class Generator { }); } - // eslint-disable-next-line sonarjs/cognitive-complexity - /** * Generates files from a given template and an AsyncAPIDocument object. * @@ -145,6 +143,22 @@ class Generator { * @example * await generator.generate(myAsyncAPIdocument); * console.log('Done!'); + * + * @example + * generator + * .generate(myAsyncAPIdocument) + * .then(() => { + * console.log('Done!'); + * }) + * .catch(console.error); + * + * @example Using async/await + * try { + * await generator.generate(myAsyncAPIdocument); + * console.log('Done!'); + * } catch (e) { + * console.error(e); + * } * * @param {AsyncAPIDocument | string} asyncapiDocument - AsyncAPIDocument object to use as source. * @param {Object} [parseOptions={}] - AsyncAPI Parser parse options. @@ -152,7 +166,6 @@ class Generator { * Remember to use the right options for the right parser depending on the template you are using. * @return {Promise} A Promise that resolves when the generation is completed. */ - async generate(asyncapiDocument, parseOptions = {}) { this.validateAsyncAPIDocument(asyncapiDocument); this.setupOutput(); @@ -182,7 +195,6 @@ class Generator { this.asyncapi = this.originalAsyncAPI = asyncapiDocument; } - // Setup the output based on the configured options /** * Sets up the output configuration based on the specified output type. * @@ -227,22 +239,22 @@ class Generator { * If the debug option is enabled, the log level is set to 'debug'. * * @returns {void} - */ + */ setLogLevel() { if (this.debug) log.setLevel('debug'); } /** - * Installs and sets up the template for code generation. - * - * This function installs the specified template using the provided installation option, - * sets up the necessary directory paths, loads the template configuration, and returns - * information about the installed template. - * - * @async - * @returns {Promise<{ templatePkgName: string, templatePkgPath: string }>} - * A promise that resolves to an object containing the name and path of the installed template. - */ + * Installs and sets up the template for code generation. + * + * This function installs the specified template using the provided installation option, + * sets up the necessary directory paths, loads the template configuration, and returns + * information about the installed template. + * + * @async + * @returns {Promise<{ templatePkgName: string, templatePkgPath: string }>} + * A promise that resolves to an object containing the name and path of the installed template. + */ async installAndSetupTemplate() { const { name: templatePkgName, path: templatePkgPath } = await this.installTemplate(this.install); @@ -267,7 +279,7 @@ class Generator { * @async * @param {*} parseOptions - Options for parsing the AsyncAPI document. * @returns {Promise} A promise that resolves when the configuration is completed. - */ + */ async configureTemplateWorkflow(parseOptions) { // Parse input and validate template configuration await this.parseInput(this.asyncapi, parseOptions); @@ -295,7 +307,7 @@ class Generator { * * @async * @returns {Promise} A promise that resolves when the entrypoint logic is completed. - */ + */ async handleEntrypoint() { if (this.entrypoint) { const entrypointPath = path.resolve(this.templateContentDir, this.entrypoint); @@ -322,7 +334,7 @@ class Generator { * * @async * @returns {Promise} A promise that resolves when the after-hook execution is completed. - */ + */ async executeAfterHook() { await this.launchHook('generate:after'); }