diff --git a/README.md b/README.md index 9e49ec3e5..70b2aa617 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ The IBM OpenAPI Validator lets you validate OpenAPI 3.x documents according to t - [Validator Output](#validator-output) * [Text](#text) * [JSON](#json) + * [CodeClimate](#codeclimate) - [Logging](#logging) - [Contributing](#contributing) - [License](#license) @@ -115,6 +116,7 @@ Options: -e, --errors-only include only errors in the output and skip warnings (default is false) -i, --ignore avoid validating (e.g. -i /dir1/ignore-file1.json --ignore /dir2/ignore-file2.yaml ...) (default is []) (default: []) -j, --json produce JSON output (default is text) + --codeclimate produce JSON output according to CodeClimate spec -l, --log-level set the log level for one or more loggers (e.g. -l root=info -l ibm-schema-description-exists=debug ...) (default: []) -n, --no-colors disable colorizing of the output (default is false) -r, --ruleset use Spectral ruleset contained in `` ("default" forces use of default IBM Cloud Validation Ruleset) @@ -482,9 +484,9 @@ module.exports = { Default -You can set the outputFormat configuration property to either text or json +You can set the outputFormat configuration property to either text, json or codeclimate to indicate the type of output you want the validator to produce. -This property corresponds to the -j/--json command-line option. +This property corresponds to the -j/--json/--codeclimate command-line option. text @@ -621,7 +623,7 @@ module.exports = { ## Validator Output The validator can produce output in either text or JSON format. The default is `text` output, and this can be -controlled with the `-j`/`--json` command-line option or `outputFormat` configuration property. +controlled with the `-j`/`--json`/`--codeclimate` command-line option or `outputFormat` configuration property. ### Text Here is an example of text output: @@ -753,6 +755,10 @@ Here is an example of JSON output: The JSON output is also affected by the `-s`/`--summary-only` and `-e`/`--errors-only` options as well as the `summaryOnly` and `errorsOnly` configuration properties. +### CodeClimate +When displaying CodeClimate JSON output, the validator will produce a null-byte separated stream of JSON objects +which complies with [the CodeClimate Output format](https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#output). + ## Logging The validator uses a *logger* for displaying messages on the console. The core validator uses a single logger named `root`, while each of the rules contained in the diff --git a/packages/validator/src/cli-validator/run-validator.js b/packages/validator/src/cli-validator/run-validator.js index a62d7c9bb..93cacfc98 100644 --- a/packages/validator/src/cli-validator/run-validator.js +++ b/packages/validator/src/cli-validator/run-validator.js @@ -18,6 +18,7 @@ const ext = require('./utils/file-extension-validator'); const preprocessFile = require('./utils/preprocess-file'); const print = require('./utils/print-results'); const { printJson } = require('./utils/json-results'); +const { printCCJson } = require('./utils/codeclimate-results'); const { runSpectral } = require('../spectral/spectral-validator'); const getCopyrightString = require('./utils/get-copyright-string'); @@ -72,7 +73,7 @@ async function runValidator(cliArgs, parseOptions = {}) { context.chalk = chalk; - if (context.config.outputFormat !== 'json') { + if (context.config.outputFormat === 'text') { console.log(getCopyrightString()); } @@ -157,7 +158,7 @@ async function runValidator(cliArgs, parseOptions = {}) { let originalFile; let input; - if (context.config.outputFormat != 'json') { + if (context.config.outputFormat === 'text') { console.log(''); console.log(chalk.underline(`Validation Results for ${validFile}:\n`)); } @@ -202,15 +203,15 @@ async function runValidator(cliArgs, parseOptions = {}) { // Check to see if we should be passing back a non-zero exit code. if (results.error.summary.total) { - // If we have any errors, then exit code 1 is returned. - exitCode = 1; + // If we have any errors, then exit code 1 is returned, except when running for codeclimate. + exitCode = context.config.outputFormat === 'codeclimate' ? 0 : 1; } // If the # of warnings exceeded the warnings limit, then this is an error. const numWarnings = results.warning.summary.total; const warningsLimit = context.config.limits.warnings; if (warningsLimit >= 0 && numWarnings > warningsLimit) { - exitCode = 1; + exitCode = context.config.outputFormat === 'codeclimate' ? 0 : 1; logger.error( `Number of warnings (${numWarnings}) exceeds warnings limit (${warningsLimit}).` ); @@ -219,6 +220,8 @@ async function runValidator(cliArgs, parseOptions = {}) { // Now print the results, either JSON or text. if (context.config.outputFormat === 'json') { printJson(context, results); + } else if (context.config.outputFormat === 'codeclimate') { + printCCJson(validFile, results); } else { if (results.hasResults) { print(context, results); diff --git a/packages/validator/src/cli-validator/utils/cli-options.js b/packages/validator/src/cli-validator/utils/cli-options.js index 328dd88fb..a0adf41ac 100644 --- a/packages/validator/src/cli-validator/utils/cli-options.js +++ b/packages/validator/src/cli-validator/utils/cli-options.js @@ -58,6 +58,10 @@ function createCLIOptions() { [] ) .option('-j, --json', 'produce JSON output (default is text)') + .option( + '--codeclimate', + 'produce JSON output according to CodeClimate spec' + ) .option( '-l, --log-level ', 'set the log level for one or more loggers (e.g. -l root=info -l ibm-schema-description-exists=debug ...) ', diff --git a/packages/validator/src/cli-validator/utils/codeclimate-results.js b/packages/validator/src/cli-validator/utils/codeclimate-results.js new file mode 100644 index 000000000..7a16846ff --- /dev/null +++ b/packages/validator/src/cli-validator/utils/codeclimate-results.js @@ -0,0 +1,47 @@ +/** + * Copyright 2023 IBM Corporation, Matthias Blümel. + * SPDX-License-Identifier: Apache2.0 + */ + +const each = require('lodash/each'); + +function printCCJson(validFile, results) { + const types = ['error', 'warning', 'info', 'hint']; + const ccTypeMap = { + error: 'critical', + warning: 'major', + info: 'minor', + hint: 'info', + }; + + types.forEach(type => { + each(results[type].results, result => { + let content; + if (result.path.length !== 0) { + let markdown = ''; + each(result.path, pathItem => { + markdown += '* ' + pathItem + '\n'; + }); + content = { body: markdown }; + } + const ccResult = { + type: 'issue', + check_name: result.rule, + description: result.message, + content: content, + categories: ['Style'], // required by codeclimate, ignored by gitlab; has to be defined by the rule. + location: { + path: validFile, + lines: { + begin: result.line, + end: result.line, + }, + }, + severity: ccTypeMap[type], + }; + console.log(JSON.stringify(ccResult) + '\0\n'); + }); + }); +} + +module.exports.printCCJson = printCCJson; diff --git a/packages/validator/src/cli-validator/utils/configuration-manager.js b/packages/validator/src/cli-validator/utils/configuration-manager.js index 8be68630a..6a5d58102 100644 --- a/packages/validator/src/cli-validator/utils/configuration-manager.js +++ b/packages/validator/src/cli-validator/utils/configuration-manager.js @@ -224,6 +224,10 @@ async function processArgs(args, cliParseOptions) { configObj.outputFormat = 'json'; } + if ('codeclimate' in opts) { + configObj.outputFormat = 'codeclimate'; + } + if ('ruleset' in opts) { configObj.ruleset = opts.ruleset; } diff --git a/packages/validator/src/schemas/config-file.yaml b/packages/validator/src/schemas/config-file.yaml index fa37fb835..eb2e2bfb9 100644 --- a/packages/validator/src/schemas/config-file.yaml +++ b/packages/validator/src/schemas/config-file.yaml @@ -59,6 +59,7 @@ properties: type: string enum: - json + - codeclimate - text default: text ruleset: