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 2 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
166 changes: 142 additions & 24 deletions lib/generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,67 +136,174 @@ class Generator {
});
}

// eslint-disable-next-line sonarjs/cognitive-complexity
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this 🙂


/**
* Generates files from a given template and an AsyncAPIDocument object.
*
* @async
* @example
* generator
* .generate(myAsyncAPIdocument)
* .then(() => {
* console.log('Done!');
* })
* .catch(console.error);
*
* @example <caption>Using async/await</caption>
* try {
* await generator.generate(myAsyncAPIdocument);
* console.log('Done!');
* } catch (e) {
* console.error(e);
* }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can leave the different examples, no need to remove it 🙂

*
* @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<void>} A Promise that resolves when the generation is completed.
*/
// eslint-disable-next-line sonarjs/cognitive-complexity

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No new lines between the jsdoc and function 🙂

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

// Setup the output based on the configured options
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for this.

/**
* 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.
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect indentation 🙂

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 +312,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.
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect indentation

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.