feat: add markdown output format for lint command (#1559)

* feat: Add markdown output format for lint command

* feat: Tweak output and add a test

* fix: send markdown output to stdout to match existing json/checkstyle behaviour

* fix: Update snapshots

* fix: Check error count to determine success

* docs: Add markdown format docs to lint command page

* chore: Add changeset

* Apply suggestions from code review

Co-authored-by: Heather Cloward <heathercloward@gmail.com>

* docs: Update from review feedback

* fix: Add missing image, run prettier

* fix: Update test to match corrected code behaviour

---------

Co-authored-by: Heather Cloward <heathercloward@gmail.com>
This commit is contained in:
Lorna Jane Mitchell
2024-05-20 09:54:26 +01:00
committed by GitHub
parent dbbbad889c
commit 546d482b03
7 changed files with 120 additions and 14 deletions

View File

@@ -0,0 +1,6 @@
---
"@redocly/openapi-core": minor
"@redocly/cli": minor
---
Added support for the linting command to output markdown format.

View File

@@ -14,7 +14,7 @@ Options:
--help Show help. [boolean] --help Show help. [boolean]
--format Use a specific output format. --format Use a specific output format.
[choices: "stylish", "codeframe", "json", "checkstyle", "codeclimate", [choices: "stylish", "codeframe", "json", "checkstyle", "codeclimate",
"summary", "github-actions"] [default: "codeframe"] "summary", "markdown", "github-actions"] [default: "codeframe"]
--max-problems Reduce output to a maximum of N problems. --max-problems Reduce output to a maximum of N problems.
[number] [default: 100] [number] [default: 100]
--generate-ignore-file Generate an ignore file. [boolean] --generate-ignore-file Generate an ignore file. [boolean]

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -22,19 +22,19 @@ redocly lint --version
## Options ## Options
| Option | Type | Description | | Option | Type | Description |
| ---------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | ---------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| apis | [string] | Array of API description filenames that need to be linted. See [the Apis section](#apis) for more options. | | apis | [string] | Array of API description filenames that need to be linted. See [the Apis section](#apis) for more options. |
| --config | string | Specify path to the [configuration file](#custom-configuration-file). | | --config | string | Specify path to the [configuration file](#custom-configuration-file). |
| --extends | [string] | [Extend a specific configuration](#extend-configuration) (defaults or config file settings). | | --extends | [string] | [Extend a specific configuration](#extend-configuration) (defaults or config file settings). |
| --format | string | Format for the output.<br />**Possible values:** `codeframe`, `stylish`, `json`, `checkstyle`, `codeclimate`, `github-actions`, `summary`. Default value is `codeframe`. | | --format | string | Format for the output.<br />**Possible values:** `codeframe`, `stylish`, `json`, `checkstyle`, `codeclimate`, `github-actions`, `markdown`, `summary`. Default value is `codeframe`. |
| --generate-ignore-file | boolean | [Generate ignore file](#generate-ignore-file). | | --generate-ignore-file | boolean | [Generate ignore file](#generate-ignore-file). |
| --help | boolean | Show help. | | --help | boolean | Show help. |
| --lint-config | string | Specify the severity level for the configuration file. <br/> **Possible values:** `warn`, `error`, `off`. Default value is `warn`. | | --lint-config | string | Specify the severity level for the configuration file. <br/> **Possible values:** `warn`, `error`, `off`. Default value is `warn`. |
| --max-problems | integer | Truncate output to display the specified [maximum number of problems](#max-problems). Default value is 100. | | --max-problems | integer | Truncate output to display the specified [maximum number of problems](#max-problems). Default value is 100. |
| --skip-preprocessor | [string] | Ignore certain preprocessors. See the [Skip preprocessor or rule section](#skip-preprocessor-or-rule) below. | | --skip-preprocessor | [string] | Ignore certain preprocessors. See the [Skip preprocessor or rule section](#skip-preprocessor-or-rule) below. |
| --skip-rule | [string] | Ignore certain rules. See the [Skip preprocessor or rule section](#skip-preprocessor-or-rule) below. | | --skip-rule | [string] | Ignore certain rules. See the [Skip preprocessor or rule section](#skip-preprocessor-or-rule) below. |
| --version | boolean | Show version number. | | --version | boolean | Show version number. |
## Examples ## Examples
@@ -239,6 +239,30 @@ Use `redocly lint --format=github-actions` to have any encountered problem annot
::warning title=operation-operationId,file=museum-with-errors.yaml,line=16,endLine=16,col=5,endColumn=5::Operation object should contain `operationId` field. ::warning title=operation-operationId,file=museum-with-errors.yaml,line=16,endLine=16,col=5,endColumn=5::Operation object should contain `operationId` field.
``` ```
#### Markdown
Use `markdown` format output with the `lint` command to get a Markdown-ready output format.
This output format is useful if you want to report the outcomes to a document, or use the information in a GitHub Job Summary.
Running the `lint` command with `--format=markdown` produces output like the following example:
```bash
## Lint: ./museum-with-errors.yaml
| Severity | Location | Problem | Message |
|---|---|---|---|
| error | line 42:11 | [spec](https://redocly.com/docs/cli/rules/spec/) | Must contain at least one of the following fields: schema, content. |
| error | line 44:11 | [spec](https://redocly.com/docs/cli/rules/spec/) | Property `type` is not expected here. |
Validation failed
Errors: 2
```
The output includes a formatted table, and links to any built-in rules that identified problems.
An example is shown in the following screenshot.
![Output of the lint command, Markdown rendered as HTML](./images/lint-markdown.png)
### <a id="max-problems"></a>Limit the problem count ### <a id="max-problems"></a>Limit the problem count
With the `--max-problems` option, you can limit the number of problems displayed in the command output. If the number of detected problems exceeds the specified threshold, the remaining problems are hidden under the "spoiler message" that lets you know how many problems were hidden. With the `--max-problems` option, you can limit the number of problems displayed in the command output. If the number of detected problems exceeds the specified threshold, the remaining problems are hidden under the "spoiler message" that lets you know how many problems were hidden.

View File

@@ -408,6 +408,7 @@ yargs
'checkstyle', 'checkstyle',
'codeclimate', 'codeclimate',
'summary', 'summary',
'markdown',
'github-actions', 'github-actions',
] as ReadonlyArray<OutputFormat>, ] as ReadonlyArray<OutputFormat>,
default: 'codeframe' as OutputFormat, default: 'codeframe' as OutputFormat,

View File

@@ -107,4 +107,41 @@ describe('format', () => {
'::error title=spec,file=openapi.yaml,line=1,col=2,endLine=3,endColumn=4::message\n' '::error title=spec,file=openapi.yaml,line=1,col=2,endLine=3,endColumn=4::message\n'
); );
}); });
it('should format problems using markdown', () => {
const problems = [
{
ruleId: 'spec',
message: 'message',
severity: 'error' as const,
location: [
{
source: { absoluteRef: 'openapi.yaml' } as Source,
start: { line: 1, col: 2 },
end: { line: 3, col: 4 },
} as LocationObject,
],
suggest: [],
},
];
formatProblems(problems, {
format: 'markdown',
version: '1.0.0',
totals: getTotals(problems),
});
expect(output).toMatchInlineSnapshot(`
"## Lint: openapi.yaml
| Severity | Location | Problem | Message |
|---|---|---|---|
| error | line 1:2 | [spec](https://redocly.com/docs/cli/rules/spec/) | message |
Validation failed
Errors: 1
"
`);
});
}); });

View File

@@ -134,6 +134,33 @@ export function formatProblems(
} }
break; break;
} }
case 'markdown': {
const groupedByFile = groupByFiles(problems);
for (const [file, { fileProblems }] of Object.entries(groupedByFile)) {
output.write(`## Lint: ${isAbsoluteUrl(file) ? file : path.relative(cwd, file)}\n\n`);
output.write(`| Severity | Location | Problem | Message |\n`);
output.write(`|---|---|---|---|\n`);
for (let i = 0; i < fileProblems.length; i++) {
const problem = fileProblems[i];
output.write(`${formatMarkdown(problem)}\n`);
}
output.write('\n');
if (totals.errors > 0) {
output.write(`Validation failed\nErrors: ${totals.errors}\n`);
} else {
output.write('Validation successful\n');
}
if (totals.warnings > 0) {
output.write(`Warnings: ${totals.warnings}\n`);
}
output.write('\n');
}
break;
}
case 'checkstyle': { case 'checkstyle': {
const groupedByFile = groupByFiles(problems); const groupedByFile = groupByFiles(problems);
@@ -270,6 +297,17 @@ export function formatProblems(
)} ${severityName} ${problem.ruleId.padEnd(ruleIdPad)} ${problem.message}`; )} ${severityName} ${problem.ruleId.padEnd(ruleIdPad)} ${problem.message}`;
} }
function formatMarkdown(problem: OnlyLineColProblem) {
if (!SEVERITY_NAMES[problem.severity]) {
return 'Error not found severity. Please check your config file. Allowed values: `warn,error,off`';
}
const severityName = SEVERITY_NAMES[problem.severity].toLowerCase();
const { start } = problem.location[0];
return `| ${severityName} | line ${`${start.line}:${start.col}`} | [${
problem.ruleId
}](https://redocly.com/docs/cli/rules/${problem.ruleId}/) | ${problem.message} |`;
}
function formatCheckstyle(problem: OnlyLineColProblem) { function formatCheckstyle(problem: OnlyLineColProblem) {
const { line, col } = problem.location[0].start; const { line, col } = problem.location[0].start;
const severity = problem.severity == 'warn' ? 'warning' : 'error'; const severity = problem.severity == 'warn' ? 'warning' : 'error';