Skip to content

Commit

Permalink
feat(core): add documentUrl to JS api and cli formatters
Browse files Browse the repository at this point in the history
  • Loading branch information
dweber019 committed Jun 9, 2024
1 parent d2b465c commit 6e15f43
Show file tree
Hide file tree
Showing 33 changed files with 1,044 additions and 6 deletions.
1 change: 1 addition & 0 deletions docs/guides/2-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Other options include:
-D, --display-only-failures only output results equal to or greater than --fail-severity [boolean] [default: false]
--ignore-unknown-format do not warn about unmatched formats [boolean] [default: false]
--fail-on-unmatched-globs fail on unmatched glob patterns [boolean] [default: false]
--show-documentation-url show documentation url in output result [boolean] [default: false]
-v, --verbose increase verbosity [boolean]
-q, --quiet no logging - output only [boolean]
```
Expand Down
10 changes: 8 additions & 2 deletions packages/cli/src/commands/__tests__/lint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ describe('lint', () => {
output: { stylish: '<stdout>' },
ignoreUnknownFormat: false,
failOnUnmatchedGlobs: false,
showDocumentationUrl: false,
});
});
});
Expand All @@ -94,6 +95,7 @@ describe('lint', () => {
output: { stylish: '<stdout>' },
ignoreUnknownFormat: false,
failOnUnmatchedGlobs: false,
showDocumentationUrl: false,
});
});

Expand All @@ -106,6 +108,7 @@ describe('lint', () => {
output: { stylish: '<stdout>' },
ignoreUnknownFormat: false,
failOnUnmatchedGlobs: false,
showDocumentationUrl: false,
});
});

Expand All @@ -118,6 +121,7 @@ describe('lint', () => {
output: { json: '<stdout>' },
ignoreUnknownFormat: false,
failOnUnmatchedGlobs: false,
showDocumentationUrl: false,
});
});

Expand Down Expand Up @@ -184,6 +188,7 @@ describe('lint', () => {
output: { stylish: '<stdout>' },
ignoreUnknownFormat: true,
failOnUnmatchedGlobs: false,
showDocumentationUrl: false,
});
});

Expand All @@ -195,6 +200,7 @@ describe('lint', () => {
output: { stylish: '<stdout>' },
ignoreUnknownFormat: false,
failOnUnmatchedGlobs: true,
showDocumentationUrl: false,
});
});

Expand Down Expand Up @@ -244,13 +250,13 @@ describe('lint', () => {
expect(process.stderr.write).nthCalledWith(2, `Error #1: ${chalk.red('some unhandled exception')}\n`);
expect(process.stderr.write).nthCalledWith(
3,
expect.stringContaining(`packages/cli/src/commands/__tests__/lint.test.ts:236`),
expect.stringContaining(`packages/cli/src/commands/__tests__/lint.test.ts:242`),
);

expect(process.stderr.write).nthCalledWith(4, `Error #2: ${chalk.red('another one')}\n`);
expect(process.stderr.write).nthCalledWith(
5,
expect.stringContaining(`packages/cli/src/commands/__tests__/lint.test.ts:237`),
expect.stringContaining(`packages/cli/src/commands/__tests__/lint.test.ts:243`),
);

expect(process.stderr.write).nthCalledWith(6, `Error #3: ${chalk.red('original exception')}\n`);
Expand Down
15 changes: 15 additions & 0 deletions packages/cli/src/commands/lint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ const lintCommand: CommandModule = {
type: 'boolean',
default: false,
},
'show-documentation-url': {
description: 'show documentation url in output result',
type: 'boolean',
default: false,
},
verbose: {
alias: 'v',
description: 'increase verbosity',
Expand All @@ -175,6 +180,7 @@ const lintCommand: CommandModule = {
encoding,
ignoreUnknownFormat,
failOnUnmatchedGlobs,
showDocumentationUrl,
...config
} = args as unknown as ILintConfig & {
documents: Array<number | string>;
Expand All @@ -189,6 +195,7 @@ const lintCommand: CommandModule = {
encoding,
ignoreUnknownFormat,
failOnUnmatchedGlobs,
showDocumentationUrl,
ruleset,
stdinFilepath,
...pick<Partial<ILintConfig>, keyof ILintConfig>(config, ['verbose', 'quiet', 'resolver']),
Expand All @@ -198,6 +205,10 @@ const lintCommand: CommandModule = {
linterResult.results = filterResultsBySeverity(linterResult.results, failSeverity);
}

if (!showDocumentationUrl) {
linterResult.results = removeDocumentationUrlFromResults(linterResult.results);
}

await Promise.all(
format.map(f => {
const formattedOutput = formatOutput(
Expand Down Expand Up @@ -279,6 +290,10 @@ const filterResultsBySeverity = (results: IRuleResult[], failSeverity: FailSever
return results.filter(r => r.severity <= diagnosticSeverity);
};

const removeDocumentationUrlFromResults = (results: IRuleResult[]): IRuleResult[] => {
return results.map(r => ({ ...r, documentationUrl: undefined }));
};

export const severeEnoughToFail = (results: IRuleResult[], failSeverity: FailSeverity): boolean => {
const diagnosticSeverity = getDiagnosticSeverity(failSeverity);
return results.some(r => r.severity <= diagnosticSeverity);
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/services/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface ILintConfig {
stdinFilepath?: string;
ignoreUnknownFormat: boolean;
failOnUnmatchedGlobs: boolean;
showDocumentationUrl: boolean;
verbose?: boolean;
quiet?: boolean;
}
1 change: 1 addition & 0 deletions packages/core/src/runner/lintNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ function processTargetResults(
severity,
...(source !== null ? { source } : null),
range,
documentationUrl: rule.documentationUrl ?? undefined,
});
}
}
1 change: 1 addition & 0 deletions packages/core/src/types/spectral.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface IRunOpts {
export interface ISpectralDiagnostic extends IDiagnostic {
path: JsonPath;
code: string | number;
documentationUrl?: string;
}

export type IRuleResult = ISpectralDiagnostic;
Expand Down
6 changes: 6 additions & 0 deletions packages/formatters/src/__tests__/html.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,36 +18,42 @@ describe('HTML formatter', () => {
<td>3:10</td>
<td class="severity clr-hint">hint</td>
<td>Info object should contain \`contact\` object.</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>
</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>
</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>
</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>
</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>`);
});
});
4 changes: 3 additions & 1 deletion packages/formatters/src/github-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ export const githubActions: Formatter = results => {
// FIXME: Use replaceAll instead after removing Node.js 14 support.
const message = result.message.replace(/\n/g, '%0A');

return `::${OUTPUT_TYPES[result.severity]} ${paramsString}::${message}`;
return `::${OUTPUT_TYPES[result.severity]} ${paramsString}::${message}${
result.documentationUrl ? `::${result.documentationUrl}` : ''
}`;
})
.join('\n');
};
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,4 +2,5 @@
<td><%= line %>:<%= character %></td>
<td class="severity clr-<%= severity %>"><%= severity %></td>
<td><%- message %></td>
<td><% if(documentationUrl) { %><a href="<%- documentationUrl %>" target="_blank">documentation</a><% } %></td>
</tr>
1 change: 1 addition & 0 deletions packages/formatters/src/html/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ function renderMessages(messages: IRuleResult[], parentIndex: number): string {
severity: getSeverityName(message.severity),
message: message.message,
code: message.code,
documentationUrl: message.documentationUrl,
});
})
.join('\n');
Expand Down
7 changes: 7 additions & 0 deletions packages/formatters/src/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,20 @@ import { Formatter } from './types';

export const json: Formatter = results => {
const outputJson = results.map(result => {
let documentationUrlObject = {};
if (result.documentationUrl) {
documentationUrlObject = {
documentationUrl: result.documentationUrl,
};
}
return {
code: result.code,
path: result.path,
message: result.message,
severity: result.severity,
range: result.range,
source: result.source,
...documentationUrlObject,
};
});
return JSON.stringify(outputJson, null, '\t');
Expand Down
3 changes: 3 additions & 0 deletions packages/formatters/src/junit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ export const junit: Formatter = (results, { failSeverity }) => {
output += `line ${result.range.start.line + 1}, col ${result.range.start.character + 1}, `;
output += `${prepareForCdata(result.message)} (${result.code}) `;
output += `at path ${prepareForCdata(path)}`;
if (result.documentationUrl) {
output += `, ${result.documentationUrl}`;
}
output += ']]>';
output += `</failure>`;
output += '</testcase>\n';
Expand Down
5 changes: 5 additions & 0 deletions packages/formatters/src/pretty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ export const pretty: Formatter = results => {
{ text: chalk[color].bold(result.code), padding: PAD_TOP0_LEFT2, width: COLUMNS[2] },
{ text: chalk.gray(result.message), padding: PAD_TOP0_LEFT2, width: COLUMNS[3] },
{ text: chalk.cyan(printPath(result.path, PrintStyle.Dot)), padding: PAD_TOP0_LEFT2 },
{
text: chalk.gray(result.documentationUrl ?? ''),
padding: PAD_TOP0_LEFT2,
width: result.documentationUrl ? undefined : 0.1,
},
);
ui.div();
});
Expand Down
2 changes: 1 addition & 1 deletion packages/formatters/src/sarif.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const sarif: Formatter = (results, _, ctx) => {
const severity: DiagnosticSeverity = result.severity || DiagnosticSeverity.Error;
sarifResultBuilder.initSimple({
level: OUTPUT_TYPES[severity] || 'error',
messageText: result.message,
messageText: result.documentationUrl ? `${result.message} -- ${result.documentationUrl}` : result.message,
ruleId: result.code.toString(),
fileUri: relative(process.cwd(), result.source ?? '').replace(/\\/g, '/'),
startLine: result.range.start.line + 1,
Expand Down
1 change: 1 addition & 0 deletions packages/formatters/src/stylish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export const stylish: Formatter = results => {
result.code ?? '',
result.message,
printPath(result.path, PrintStyle.Dot),
result.documentationUrl ?? '',
]);

output += `${table(pathTableData, {
Expand Down
3 changes: 2 additions & 1 deletion packages/formatters/src/teamcity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ function inspectionType(result: IRuleResult & { source: string }): string {
const code = escapeString(result.code);
const severity = getSeverityName(result.severity);
const message = escapeString(result.message);
return `##teamcity[inspectionType category='openapi' id='${code}' name='${code}' description='${severity} -- ${message}']`;
const documentationUrl = result.documentationUrl ? ` -- ${escapeString(result.documentationUrl)}` : '';
return `##teamcity[inspectionType category='openapi' id='${code}' name='${code}' description='${severity} -- ${message}${documentationUrl}']`;
}

function inspection(result: IRuleResult & { source: string }): string {
Expand Down
3 changes: 2 additions & 1 deletion packages/formatters/src/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ function renderResults(results: IRuleResult[]): string {
const line = result.range.start.line + 1;
const character = result.range.start.character + 1;
const severity = getSeverityName(result.severity);
return `${result.source}:${line}:${character} ${severity} ${result.code} "${result.message}"`;
const documentationUrl = result.documentationUrl ? ` ${result.documentationUrl}` : '';
return `${result.source}:${line}:${character} ${severity} ${result.code} "${result.message}"${documentationUrl}`;
})
.join('\n');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ describe('asyncApi2DocumentSchema', () => {
).toEqual([
{
code: 'asyncapi-schema',
documentationUrl: 'https://meta.stoplight.io/docs/spectral/docs/reference/asyncapi-rules.md#asyncapi-schema',
message: '"info" property must have required property "title"',
path: ['info'],
severity: DiagnosticSeverity.Error,
Expand Down Expand Up @@ -131,13 +132,15 @@ describe('asyncApi2DocumentSchema', () => {
).toEqual([
{
code: 'asyncapi-schema',
documentationUrl: 'https://meta.stoplight.io/docs/spectral/docs/reference/asyncapi-rules.md#asyncapi-schema',
message: '"0" property type must be string',
path: ['channels', '/user/signedup', 'servers', '0'],
severity: DiagnosticSeverity.Error,
range: expect.any(Object),
},
{
code: 'asyncapi-schema',
documentationUrl: 'https://meta.stoplight.io/docs/spectral/docs/reference/asyncapi-rules.md#asyncapi-schema',
message: '"2" property type must be string',
path: ['channels', '/user/signedup', 'servers', '2'],
severity: DiagnosticSeverity.Error,
Expand Down Expand Up @@ -184,6 +187,7 @@ describe('asyncApi2DocumentSchema', () => {
).toEqual([
{
code: 'asyncapi-schema',
documentationUrl: 'https://meta.stoplight.io/docs/spectral/docs/reference/asyncapi-rules.md#asyncapi-schema',
message: '"kafka" property must have required property "url"',
path: ['components', 'servers', 'kafka'],
severity: DiagnosticSeverity.Error,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
====test====
Invalid document --output to a file, will show all the contents when the file is read
====document====
---
info:
version: 1.0.0
title: Stoplight
====asset:ruleset.json====
{
"rules": {
"api-servers": {
"documentationUrl": "https://www.example.com/docs/api-servers.md",
"description": "\"servers\" must be present and non-empty array.",
"recommended": true,
"given": "$",
"then": {
"field": "servers",
"function": "schema",
"functionOptions": {
"dialect": "draft7",
"schema": {
"items": {
"type": "object",
},
"minItems": 1,
"type": "array"
}
}
}
},
"info-contact": {
"description": "Info object must have a \"contact\" object.",
"recommended": true,
"type": "style",
"given": "$",
"then": {
"field": "info.contact",
"function": "truthy",
}
},
"info-description": {
"documentationUrl": "https://www.example.com/docs/info-description.md",
"description": "Info \"description\" must be present and non-empty string.",
"recommended": true,
"type": "style",
"given": "$",
"then": {
"field": "info.description",
"function": "truthy"
}
}
}
}
====command-nix====
{bin} lint {document} --ruleset "{asset:ruleset.json}" --format=text --output={asset:output.txt} --show-documentation-url > /dev/null; cat {asset:output.txt}
====command-win====
{bin} lint {document} --ruleset "{asset:ruleset.json}" --format=text --output={asset:output.txt} --show-documentation-url | Out-Null; cat {asset:output.txt}
====asset:output.txt====
====stdout====
{document}:1:1 warning api-servers ""servers" must be present and non-empty array." https://www.example.com/docs/api-servers.md
{document}:2:6 warning info-contact "Info object must have a "contact" object."
{document}:2:6 warning info-description "Info "description" must be present and non-empty string." https://www.example.com/docs/info-description.md

Loading

0 comments on commit 6e15f43

Please sign in to comment.