Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: generate function refactored to reduce cognitive complexity #1047

Merged
merged 3 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 139 additions & 9 deletions lib/generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ class Generator {
/**
* Generates files from a given template and an AsyncAPIDocument object.
*
* @async
* @example
* await generator.generate(myAsyncAPIdocument);
* console.log('Done!');
*
* @example
* generator
* .generate(myAsyncAPIdocument)
Expand All @@ -155,48 +160,162 @@ class Generator {
* 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}
* @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<void>} A Promise that resolves when the generation is completed.
*/
// eslint-disable-next-line sonarjs/cognitive-complexity
async generate(asyncapiDocument, parseOptions = {}) {
this.validateAsyncAPIDocument(asyncapiDocument);
this.setupOutput();
this.setLogLevel();

await this.installAndSetupTemplate();
await this.configureTemplateWorkflow(parseOptions);
await this.handleEntrypoint();
await this.executeAfterHook();
}

/**
* 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;
}

/**
* 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') {
xfs.mkdirpSync(this.targetDir);
if (!this.forceWrite) await this.verifyTargetDir(this.targetDir);
this.setupFSOutput();
} else if (this.output === 'string' && this.entrypoint === undefined) {
throw new Error('Parameter entrypoint is required when using output = "string"');
}
}

/**
* 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<void>} 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) {
await this.verifyTargetDir(this.targetDir);
}
}

/**
* 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');
}

/**
* 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();

await this.parseInput(this.asyncapi, parseOptions);
return { templatePkgName, templatePkgPath };
}

/**
* 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<void>} 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');
}

/**
* 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<void>} 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 (!(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');
Expand All @@ -205,10 +324,21 @@ class Generator {
}
} else {
await this.generateDirectoryStructure(this.asyncapi);
await this.launchHook('generate:after');
}
}

/**
* Executes the 'generate:after' hook.
*
* Launches the after-hook to perform additional actions after code generation.
*
* @async
* @returns {Promise<void>} A promise that resolves when the after-hook execution is completed.
*/
async executeAfterHook() {
await this.launchHook('generate:after');
}

/**
* Parse the generator input based on the template `templateConfig.apiVersion` value.
*/
Expand Down
11 changes: 7 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.