Skip to content
Closed
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
1 change: 1 addition & 0 deletions docs/guides/2-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Other options include:
--show-documentation-url show documentation url in output result [boolean] [default: false]
-v, --verbose increase verbosity [boolean]
-q, --quiet no logging - output only [boolean]
--html-include-json-path add json path in html format result [boolean] [default: false]
```

The Spectral CLI supports loading documents as YAML or JSON, and validation of OpenAPI v2/v3 documents via the built-in ruleset.
Expand Down
18 changes: 12 additions & 6 deletions packages/cli/src/commands/lint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@ const lintCommand: CommandModule = {
description: 'no logging - output only',
type: 'boolean',
},
'html-include-json-path': {
description: 'add json path in html format report',
type: 'boolean',
default: false,
},
}),

async handler(args) {
Expand All @@ -181,11 +186,13 @@ const lintCommand: CommandModule = {
ignoreUnknownFormat,
failOnUnmatchedGlobs,
showDocumentationUrl,
htmlIncludeJsonPath,
...config
} = args as unknown as ILintConfig & {
documents: Array<number | string>;
failSeverity: FailSeverity;
displayOnlyFailures: boolean;
htmlIncludeJsonPath: boolean;
};

try {
Expand All @@ -211,12 +218,11 @@ const lintCommand: CommandModule = {

await Promise.all(
format.map(f => {
const formattedOutput = formatOutput(
linterResult.results,
f,
{ failSeverity: getDiagnosticSeverity(failSeverity) },
linterResult.resolvedRuleset,
);
const formatterOptions = {
failSeverity: getDiagnosticSeverity(failSeverity),
...(f === OutputFormat.HTML && { htmlFormatterOptions: { includeJsonPath: htmlIncludeJsonPath } }),
};
const formattedOutput = formatOutput(linterResult.results, f, formatterOptions, linterResult.resolvedRuleset);
return writeOutput(formattedOutput, output?.[f] ?? '<stdout>');
}),
);
Expand Down
79 changes: 74 additions & 5 deletions packages/formatters/src/__tests__/html.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,19 @@ describe('HTML formatter', () => {
test('should display proper severity levels', () => {
const result = parse(html(mixedErrors, { failSeverity: DiagnosticSeverity.Error }));
const table = result.querySelector('table tbody');
expect(table.innerHTML.trim()).toEqual(`<tr class="bg-error" data-group="f-0">
<th colspan="4">
expect(table.innerHTML.trim()).toEqual(expectedResult);
});
test('should display proper severity levels with jsonPath', () => {
const result = parse(
html(mixedErrors, { failSeverity: DiagnosticSeverity.Error, htmlFormatterOptions: { includeJsonPath: true } }),
);
const table = result.querySelector('table tbody');
expect(table.innerHTML.trim()).toEqual(expectedResultWithJsonPath);
});
});

const expectedResult = `<tr class="bg-error" data-group="f-0">
<th colspan="5">
[+] /home/Stoplight/spectral/src/__tests__/__fixtures__/petstore.oas3.json
<span>6 problems (1 error, 1 warning, 3 infos, 1 hint)</span>
</th>
Expand All @@ -19,41 +30,99 @@ describe('HTML formatter', () => {
<td class="severity clr-hint">hint</td>
<td>Info object should contain \`contact\` object.</td>
<td></td>
<td></td>
</tr>

<tr style="display:none" class="f-0">
<td>3:10</td>
<td class="severity clr-warning">warning</td>
<td>OpenAPI object info \`description\` must be present and non-empty string.</td>
<td></td>
<td></td>
</tr>

<tr style="display:none" class="f-0">
<td>5:14</td>
<td class="severity clr-error">error</td>
<td>Info must contain Stoplight</td>
<td></td>
<td></td>
</tr>

<tr style="display:none" class="f-0">
<td>17:13</td>
<td class="severity clr-information">information</td>
<td>Operation \`description\` must be present and non-empty string.</td>
<td></td>
<td></td>
</tr>

<tr style="display:none" class="f-0">
<td>64:14</td>
<td class="severity clr-information">information</td>
<td>Operation \`description\` must be present and non-empty string.</td>
<td></td>
<td></td>
</tr>

<tr style="display:none" class="f-0">
<td>86:13</td>
<td class="severity clr-information">information</td>
<td>Operation \`description\` must be present and non-empty string.</td>
<td></td>
</tr>`);
});
});
<td></td>
</tr>`;

const expectedResultWithJsonPath = `<tr class="bg-error" data-group="f-0">
<th colspan="5">
[+] /home/Stoplight/spectral/src/__tests__/__fixtures__/petstore.oas3.json
<span>6 problems (1 error, 1 warning, 3 infos, 1 hint)</span>
</th>
</tr>
<tr style="display:none" class="f-0">
<td>3:10</td>
<td class="severity clr-hint">hint</td>
<td>Info object should contain \`contact\` object.</td>
<td>info</td>
<td></td>
</tr>

<tr style="display:none" class="f-0">
<td>3:10</td>
<td class="severity clr-warning">warning</td>
<td>OpenAPI object info \`description\` must be present and non-empty string.</td>
<td>info</td>
<td></td>
</tr>

<tr style="display:none" class="f-0">
<td>5:14</td>
<td class="severity clr-error">error</td>
<td>Info must contain Stoplight</td>
<td>info.title</td>
<td></td>
</tr>

<tr style="display:none" class="f-0">
<td>17:13</td>
<td class="severity clr-information">information</td>
<td>Operation \`description\` must be present and non-empty string.</td>
<td>paths./pets.get</td>
<td></td>
</tr>

<tr style="display:none" class="f-0">
<td>64:14</td>
<td class="severity clr-information">information</td>
<td>Operation \`description\` must be present and non-empty string.</td>
<td>paths./pets.post</td>
<td></td>
</tr>

<tr style="display:none" class="f-0">
<td>86:13</td>
<td class="severity clr-information">information</td>
<td>Operation \`description\` must be present and non-empty string.</td>
<td>paths./pets/{petId}.get</td>
<td></td>
</tr>`;
1 change: 1 addition & 0 deletions packages/formatters/src/html/html-template-message.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
<td><%= line %>:<%= character %></td>
<td class="severity clr-<%= severity %>"><%= severity %></td>
<td><%- message %></td>
<td><%- jsonpath %></td>
<td><% if(documentationUrl) { %><a href="<%- documentationUrl %>" target="_blank">documentation</a><% } %></td>
</tr>
2 changes: 1 addition & 1 deletion packages/formatters/src/html/html-template-result.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<tr class="bg-<%- color %>" data-group="f-<%- index %>">
<th colspan="4">
<th colspan="5">
[+] <%- filePath %>
<span><%- summary %></span>
</th>
Expand Down
12 changes: 7 additions & 5 deletions packages/formatters/src/html/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type { IRuleResult } from '@stoplight/spectral-core';
import { Formatter } from '../types';
import { getHighestSeverity, getSeverityName, getSummary, getSummaryForSource, groupBySource } from '../utils';
import templates from './templates';
import { printPath, PrintStyle } from '@stoplight/spectral-runtime';

// ------------------------------------------------------------------------------
// Helpers
Expand All @@ -37,7 +38,7 @@ const pageTemplate = template(templates['html-template-page.html']);
const messageTemplate = template(templates['html-template-message.html']);
const resultTemplate = template(templates['html-template-result.html']);

function renderMessages(messages: IRuleResult[], parentIndex: number): string {
function renderMessages(messages: IRuleResult[], parentIndex: number, includeJsonPath: boolean): string {
return messages
.map(message => {
const line = message.range.start.line + 1;
Expand All @@ -50,13 +51,14 @@ function renderMessages(messages: IRuleResult[], parentIndex: number): string {
severity: getSeverityName(message.severity),
message: message.message,
code: message.code,
jsonpath: includeJsonPath ? printPath(message.path, PrintStyle.Dot) : '',
documentationUrl: message.documentationUrl,
});
})
.join('\n');
}

function renderResults(groupedResults: Dictionary<IRuleResult[]>): string {
function renderResults(groupedResults: Dictionary<IRuleResult[]>, includeJsonPath = false): string {
return Object.keys(groupedResults)
.map(
(source, index) =>
Expand All @@ -68,7 +70,7 @@ function renderResults(groupedResults: Dictionary<IRuleResult[]>): string {
: getSeverityName(getHighestSeverity(groupedResults[source])),
filePath: source,
summary: getSummaryForSource(groupedResults[source]),
}) + renderMessages(groupedResults[source], index),
}) + renderMessages(groupedResults[source], index, includeJsonPath),
)
.join('\n');
}
Expand All @@ -77,14 +79,14 @@ function renderResults(groupedResults: Dictionary<IRuleResult[]>): string {
// Public Interface
// ------------------------------------------------------------------------------

export const html: Formatter = results => {
export const html: Formatter = (results, options) => {
const color = results.length === 0 ? 'success' : getSeverityName(getHighestSeverity(results));
const groupedResults = groupBySource(results);

return pageTemplate({
date: new Date(),
color,
summary: getSummary(groupedResults),
results: renderResults(groupedResults),
results: renderResults(groupedResults, options.htmlFormatterOptions?.includeJsonPath),
});
};
5 changes: 5 additions & 0 deletions packages/formatters/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { ISpectralDiagnostic, Ruleset } from '@stoplight/spectral-core';
import type { DiagnosticSeverity } from '@stoplight/types';

export type HtmlFormatterOptions = {
includeJsonPath: boolean;
};

export type FormatterOptions = {
failSeverity: DiagnosticSeverity;
htmlFormatterOptions?: HtmlFormatterOptions;
};

export type FormatterContext = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ info:
<table>
<tbody>
<tr class="bg-warning" data-group="f-0">
<th colspan="4">
<th colspan="5">
[+] {document}
<span>3 problems (0 errors, 3 warnings, 0 infos, 0 hints)</span>
</th>
Expand All @@ -165,6 +165,7 @@ info:
<td>1:1</td>
<td class="severity clr-warning">warning</td>
<td>&quot;servers&quot; must be present and non-empty array.</td>
<td></td>
<td><a href="https://www.example.com/docs/api-servers.md" target="_blank">documentation</a></td>
</tr>

Expand All @@ -173,12 +174,14 @@ info:
<td class="severity clr-warning">warning</td>
<td>Info object must have a &quot;contact&quot; object.</td>
<td></td>
<td></td>
</tr>

<tr style="display:none" class="f-0">
<td>2:6</td>
<td class="severity clr-warning">warning</td>
<td>Info &quot;description&quot; must be present and non-empty string.</td>
<td></td>
<td><a href="https://www.example.com/docs/info-description.md" target="_blank">documentation</a></td>
</tr>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ info:
<table>
<tbody>
<tr class="bg-warning" data-group="f-0">
<th colspan="4">
<th colspan="5">
[+] {document}
<span>3 problems (0 errors, 3 warnings, 0 infos, 0 hints)</span>
</th>
Expand All @@ -164,20 +164,23 @@ info:
<td class="severity clr-warning">warning</td>
<td>&quot;servers&quot; must be present and non-empty array.</td>
<td></td>
<td></td>
</tr>

<tr style="display:none" class="f-0">
<td>2:6</td>
<td class="severity clr-warning">warning</td>
<td>Info object must have a &quot;contact&quot; object.</td>
<td></td>
<td></td>
</tr>

<tr style="display:none" class="f-0">
<td>2:6</td>
<td class="severity clr-warning">warning</td>
<td>Info &quot;description&quot; must be present and non-empty string.</td>
<td></td>
<td></td>
</tr>

</tbody>
Expand Down
1 change: 1 addition & 0 deletions test-harness/scenarios/formats/too-few-outputs.scenario
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ Options:
--show-documentation-url show documentation url in output result [boolean] [default: false]
-v, --verbose increase verbosity [boolean]
-q, --quiet no logging - output only [boolean]
--html-include-json-path add json path in html format report [boolean] [default: false]

The number of outputs must match the number of formats
1 change: 1 addition & 0 deletions test-harness/scenarios/formats/too-many-outputs.scenario
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ Options:
--show-documentation-url show documentation url in output result [boolean] [default: false]
-v, --verbose increase verbosity [boolean]
-q, --quiet no logging - output only [boolean]
--html-include-json-path add json path in html format report [boolean] [default: false]

The number of outputs must match the number of formats
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ Options:
--show-documentation-url show documentation url in output result [boolean] [default: false]
-v, --verbose increase verbosity [boolean]
-q, --quiet no logging - output only [boolean]
--html-include-json-path add json path in html format report [boolean] [default: false]

Missing outputs for the following formats: html
1 change: 1 addition & 0 deletions test-harness/scenarios/help-no-document.scenario
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@ Options:
--show-documentation-url show documentation url in output result [boolean] [default: false]
-v, --verbose increase verbosity [boolean]
-q, --quiet no logging - output only [boolean]
--html-include-json-path add json path in html format report [boolean] [default: false]

No documents provided.
1 change: 1 addition & 0 deletions test-harness/scenarios/strict-options.scenario
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@ Options:
--show-documentation-url show documentation url in output result [boolean] [default: false]
-v, --verbose increase verbosity [boolean]
-q, --quiet no logging - output only [boolean]
--html-include-json-path add json path in html format report [boolean] [default: false]

Unknown arguments: i, p