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

feat(formatters): add sarif formatter #2532

Merged
merged 12 commits into from
Sep 15, 2023
Merged
4 changes: 2 additions & 2 deletions docs/guides/2-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ Other options include:
[string] [choices: "utf8", "ascii", "utf-8", "utf16le", "ucs2", "ucs-2", "base64", "latin1"] [default: "utf8"]
-f, --format formatters to use for outputting results, more than one can be given joining them with
a comma
[string] [choices: "json", "stylish", "junit", "html", "text", "teamcity", "pretty", "github-actions"] [default:
"stylish"]
[string] [choices: "json", "stylish", "junit", "html", "text", "teamcity", "pretty", "github-actions", "sarif"]
[default: "stylish"]
-o, --output where to output results, can be a single file name, multiple "output.<format>" or
missing to print to stdout [string]
--stdin-filepath path to a file to pretend that stdin comes from [string]
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"scripts": {
"clean": "rimraf .cache packages/*/{dist,.cache}",
"prebuild": "yarn workspaces foreach run prebuild",
"build": "yarn prebuild && tsc --build ./tsconfig.build.json",
"build": "yarn prebuild && tsc --build ./tsconfig.build.json && yarn postbuild",
"postbuild": "yarn workspaces foreach run postbuild",
"prelint": "yarn workspaces foreach run prelint",
"lint": "yarn prelint && yarn lint.prettier && yarn lint.eslint",
"lint.fix": "yarn lint.prettier --write && yarn lint.eslint --fix",
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/__tests__/lint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ describe('lint', () => {
];

beforeEach(() => {
(lint as jest.Mock).mockResolvedValueOnce(results);
(lint as jest.Mock).mockResolvedValueOnce({ results: results, resolvedRuleset: {} });
(formatOutput as jest.Mock).mockReturnValueOnce('<formatted output>');
(writeOutput as jest.Mock).mockResolvedValueOnce(undefined);
});
Expand Down Expand Up @@ -148,7 +148,7 @@ describe('lint', () => {

it.each(['json', 'stylish'])('calls formatOutput with %s format', async format => {
await run(`lint -f ${format} ./__fixtures__/empty-oas2-document.json`);
expect(formatOutput).toBeCalledWith(results, format, { failSeverity: DiagnosticSeverity.Error });
expect(formatOutput).toBeCalledWith(results, format, { failSeverity: DiagnosticSeverity.Error }, expect.anything());
});

it('writes formatted output to a file', async () => {
Expand Down
15 changes: 10 additions & 5 deletions packages/cli/src/commands/lint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ const lintCommand: CommandModule = {
};

try {
let results = await lint(documents, {
const linterResult = await lint(documents, {
format,
output,
encoding,
Expand All @@ -194,18 +194,23 @@ const lintCommand: CommandModule = {
});

if (displayOnlyFailures) {
results = filterResultsBySeverity(results, failSeverity);
linterResult.results = filterResultsBySeverity(linterResult.results, failSeverity);
}

await Promise.all(
format.map(f => {
const formattedOutput = formatOutput(results, f, { failSeverity: getDiagnosticSeverity(failSeverity) });
const formattedOutput = formatOutput(
linterResult.results,
f,
{ failSeverity: getDiagnosticSeverity(failSeverity) },
linterResult.resolvedRuleset,
);
return writeOutput(formattedOutput, output?.[f] ?? '<stdout>');
}),
);

if (results.length > 0) {
process.exit(severeEnoughToFail(results, failSeverity) ? 1 : 0);
if (linterResult.results.length > 0) {
process.exit(severeEnoughToFail(linterResult.results, failSeverity) ? 1 : 0);
} else if (config.quiet !== true) {
const isErrorSeverity = getDiagnosticSeverity(failSeverity) === DiagnosticSeverity.Error;
process.stdout.write(
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/src/services/__tests__/linter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import AggregateError = require('es-aggregate-error');
import * as process from 'process';

import lintCommand from '../../commands/lint';
import { lint } from '../linter';
import { LinterResult, lint } from '../linter';

jest.mock('process');
jest.mock('../output');
Expand All @@ -20,7 +20,7 @@ const invalidRulesetPath = resolve(__dirname, '__fixtures__/ruleset-invalid.js')
const validRulesetPath = resolve(__dirname, '__fixtures__/ruleset-valid.js');
const validOas3SpecPath = resolve(__dirname, './__fixtures__/openapi-3.0-valid.yaml');

async function run(command: string) {
async function run(command: string): Promise<LinterResult['results']> {
const parser = yargs.command(lintCommand);
const { documents, ...opts } = await new Promise<any>((resolve, reject) => {
parser.parse(`${command} --ignore-unknown-format`, {}, (err, argv) => {
Expand All @@ -32,7 +32,7 @@ async function run(command: string) {
});
});

return lint(documents, opts);
return (await lint(documents, opts)).results;
}

describe('Linter service', () => {
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 @@ -12,6 +12,7 @@ export enum OutputFormat {
TEAMCITY = 'teamcity',
PRETTY = 'pretty',
GITHUB_ACTIONS = 'github-actions',
SARIF = 'sarif',
}

export interface ILintConfig {
Expand Down
14 changes: 11 additions & 3 deletions packages/cli/src/services/linter/linter.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
/* eslint-disable no-console */
import { Document, IRuleResult, Spectral } from '@stoplight/spectral-core';
import { Document, IRuleResult, Ruleset, Spectral } from '@stoplight/spectral-core';
import { readParsable, IFileReadOptions } from '@stoplight/spectral-runtime';
import * as Parsers from '@stoplight/spectral-parsers';
import { getRuleset, listFiles, segregateEntriesPerKind, readFileDescriptor } from './utils';
import { getResolver } from './utils/getResolver';
import { ILintConfig } from '../config';
import { CLIError } from '../../errors';

export async function lint(documents: Array<number | string>, flags: ILintConfig): Promise<IRuleResult[]> {
export interface LinterResult {
results: IRuleResult[];
resolvedRuleset: Ruleset;
}

export async function lint(documents: Array<number | string>, flags: ILintConfig): Promise<LinterResult> {
const spectral = new Spectral({
resolver: getResolver(flags.resolver),
});
Expand Down Expand Up @@ -48,7 +53,10 @@ export async function lint(documents: Array<number | string>, flags: ILintConfig
);
}

return results;
return {
results: results,
resolvedRuleset: ruleset,
};
}

const createDocument = async (
Expand Down
28 changes: 24 additions & 4 deletions packages/cli/src/services/output.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import * as process from 'process';
import { IRuleResult } from '@stoplight/spectral-core';
import { IRuleResult, Ruleset } from '@stoplight/spectral-core';
import { promises as fs } from 'fs';
import { html, json, junit, stylish, teamcity, text, pretty, githubActions } from '@stoplight/spectral-formatters';
import {
html,
json,
junit,
stylish,
teamcity,
text,
pretty,
githubActions,
sarif,
} from '@stoplight/spectral-formatters';
import type { Formatter, FormatterOptions } from '@stoplight/spectral-formatters';
import type { OutputFormat } from './config';
import { VERSION } from '../version';

const formatters: Record<OutputFormat, Formatter> = {
json,
Expand All @@ -14,10 +25,19 @@ const formatters: Record<OutputFormat, Formatter> = {
text,
teamcity,
'github-actions': githubActions,
sarif,
};

export function formatOutput(results: IRuleResult[], format: OutputFormat, formatOptions: FormatterOptions): string {
return formatters[format](results, formatOptions);
export function formatOutput(
results: IRuleResult[],
format: OutputFormat,
formatOptions: FormatterOptions,
ruleset: Ruleset,
): string {
return formatters[format](results, formatOptions, {
ruleset,
spectralVersion: VERSION,
});
}

export async function writeOutput(outputStr: string, outputFile: string): Promise<void> {
Expand Down
2 changes: 1 addition & 1 deletion test-harness/scenarios/help-no-document.scenario
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Options:
--version Show version number [boolean]
--help Show help [boolean]
-e, --encoding text encoding to use [string] [choices: "utf8", "ascii", "utf-8", "utf16le", "ucs2", "ucs-2", "base64", "latin1"] [default: "utf8"]
-f, --format formatters to use for outputting results, more than one can be given joining them with a comma [string] [choices: "json", "stylish", "junit", "html", "text", "teamcity", "pretty", "github-actions"] [default: "stylish"]
-f, --format formatters to use for outputting results, more than one can be given joining them with a comma [string] [choices: "json", "stylish", "junit", "html", "text", "teamcity", "pretty", "github-actions", "sarif"] [default: "stylish"]
-o, --output where to output results, can be a single file name, multiple "output.<format>" or missing to print to stdout [string]
--stdin-filepath path to a file to pretend that stdin comes from [string]
--resolver path to custom json-ref-resolver instance [string]
Expand Down
2 changes: 1 addition & 1 deletion test-harness/scenarios/strict-options.scenario
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Options:
--version Show version number [boolean]
--help Show help [boolean]
-e, --encoding text encoding to use [string] [choices: "utf8", "ascii", "utf-8", "utf16le", "ucs2", "ucs-2", "base64", "latin1"] [default: "utf8"]
-f, --format formatters to use for outputting results, more than one can be given joining them with a comma [string] [choices: "json", "stylish", "junit", "html", "text", "teamcity", "pretty", "github-actions"] [default: "stylish"]
-f, --format formatters to use for outputting results, more than one can be given joining them with a comma [string] [choices: "json", "stylish", "junit", "html", "text", "teamcity", "pretty", "github-actions", "sarif"] [default: "stylish"]
-o, --output where to output results, can be a single file name, multiple "output.<format>" or missing to print to stdout [string]
--stdin-filepath path to a file to pretend that stdin comes from [string]
--resolver path to custom json-ref-resolver instance [string]
Expand Down
4 changes: 2 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2657,7 +2657,7 @@ __metadata:
"@stoplight/json": ~3.21.0
"@stoplight/path": 1.3.2
"@stoplight/spectral-core": ^1.18.3
"@stoplight/spectral-formatters": ^1.2.0
"@stoplight/spectral-formatters": ^1.3.0
"@stoplight/spectral-parsers": ^1.0.3
"@stoplight/spectral-ref-resolver": ^1.0.4
"@stoplight/spectral-ruleset-bundler": ^1.5.2
Expand Down Expand Up @@ -2731,7 +2731,7 @@ __metadata:
languageName: unknown
linkType: soft

"@stoplight/spectral-formatters@^1.2.0, @stoplight/spectral-formatters@workspace:packages/formatters":
"@stoplight/spectral-formatters@^1.3.0, @stoplight/spectral-formatters@workspace:packages/formatters":
version: 0.0.0-use.local
resolution: "@stoplight/spectral-formatters@workspace:packages/formatters"
dependencies:
Expand Down