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 code climate (GitLab) formatter #2648

Merged
merged 2 commits into from
Sep 13, 2024
Merged
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
18 changes: 9 additions & 9 deletions docs/guides/2-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,26 @@ spectral lint ./reference/**/*.oas*.{json,yml,yaml}
Other options include:

```
--version Show version number [boolean]
--help Show help [boolean]
--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 provided by using
multiple flags
[string] [choices: "json", "stylish", "junit", "html", "text", "teamcity", "pretty", "github-actions", "sarif", "markdown"]
[string] [choices: "json", "stylish", "junit", "html", "text", "teamcity", "pretty", "github-actions", "sarif", "markdown","gitlab"]
[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]
-r, --ruleset path/URL to a ruleset file [string]
-F, --fail-severity results of this level or above will trigger a failure exit code
[string] [choices: "error", "warn", "info", "hint"] [default: "error"]
-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]
-v, --verbose increase verbosity [boolean]
-q, --quiet no logging - output only [boolean]
[string] [choices: "error", "warn", "info", "hint"] [default: "error"]
-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]
-v, --verbose increase verbosity [boolean]
-q, --quiet no logging - output only [boolean]
```

The Spectral CLI supports loading documents as YAML or JSON, and validation of OpenAPI v2/v3 documents via the built-in ruleset.
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/services/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export enum OutputFormat {
PRETTY = 'pretty',
GITHUB_ACTIONS = 'github-actions',
SARIF = 'sarif',
CODE_CLIMATE = 'code-climate',
GITLAB = 'gitlab',
MARKDOWN = 'markdown',
}

Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/services/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
pretty,
githubActions,
sarif,
codeClimate,
markdown,
} from '@stoplight/spectral-formatters';
import type { Formatter, FormatterOptions } from '@stoplight/spectral-formatters';
Expand All @@ -27,6 +28,8 @@ const formatters: Record<OutputFormat, Formatter> = {
teamcity,
'github-actions': githubActions,
sarif,
'code-climate': codeClimate,
gitlab: codeClimate,
markdown,
};

Expand Down
2 changes: 2 additions & 0 deletions packages/formatters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ console.error(output);
- pretty
- github-actions
- sarif
- gitlab
- code-climate
89 changes: 89 additions & 0 deletions packages/formatters/src/__tests__/code-climate.jest.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { DiagnosticSeverity } from '@stoplight/types';
import type { IRuleResult } from '@stoplight/spectral-core';
import { codeClimate } from '../code-climate';

const cwd = process.cwd();
const results: IRuleResult[] = [
{
code: 'operation-description',
message: 'paths./pets.get.description is not truthy',
path: ['paths', '/pets', 'get', 'description'],
severity: 1,
source: `${cwd}/__tests__/fixtures/petstore.oas2.yaml`,
range: {
start: {
line: 60,
character: 8,
},
end: {
line: 71,
character: 60,
},
},
},
{
code: 'operation-tags',
message: 'paths./pets.get.tags is not truthy',
path: ['paths', '/pets', 'get', 'tags'],
severity: 1,
source: `${cwd}/__tests__/fixtures/petstore.oas2.yaml`,
range: {
start: {
line: 60,
character: 8,
},
end: {
line: 71,
character: 60,
},
},
},
];

describe('Code climate formatter', () => {
test('should include ranges', () => {
expect(JSON.parse(codeClimate(results, { failSeverity: DiagnosticSeverity.Error }))).toEqual([
expect.objectContaining({
location: {
path: '__tests__/fixtures/petstore.oas2.yaml',
positions: {
begin: {
line: 60,
column: 8,
},
end: {
line: 71,
column: 60,
},
},
},
}),
expect.objectContaining({
location: {
path: '__tests__/fixtures/petstore.oas2.yaml',
positions: {
begin: {
line: 60,
column: 8,
},
end: {
line: 71,
column: 60,
},
},
},
}),
]);
});

test('should include description', () => {
expect(JSON.parse(codeClimate(results, { failSeverity: DiagnosticSeverity.Error }))).toEqual([
expect.objectContaining({
description: 'paths./pets.get.description is not truthy',
}),
expect.objectContaining({
description: 'paths./pets.get.tags is not truthy',
}),
]);
});
});
69 changes: 69 additions & 0 deletions packages/formatters/src/code-climate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { DiagnosticSeverity } from '@stoplight/types';
import { Formatter } from './types';
import { relative } from '@stoplight/path';

/**
* @see https://github.com/codeclimate/platform/blob/690633cb2a08839a5bfa350ed925ddb6de55bbdc/spec/analyzers/SPEC.md#data-types
*/
interface CodeClimateIssue {
type: 'issue';
check_name: string;
description: string;
categories: CodeClimateIssueCategory[];
location: CodeClimateIssueLocation;
content?: { body: string };
trace?: CodeClimateIssueTrace;
remediation_points?: number;
severity?: CodeClimateIssueSeverity;
fingerprint?: string;
}
type CodeClimateIssueCategory =
| 'Bug Risk'
| 'Clarity'
| 'Compatibility'
| 'Complexity'
| 'Duplication'
| 'Performance'
| 'Security'
| 'Style';
interface CodeClimateIssueLocation {
path: string;
positions: {
begin: { line: number; column: number };
end: { line: number; column: number };
};
}
interface CodeClimateIssueTrace {
locations: CodeClimateIssueLocation[];
stackTrace: boolean;
}
type CodeClimateIssueSeverity = 'info' | 'minor' | 'major' | 'critical' | 'blocker';
const severityMap: Record<DiagnosticSeverity, CodeClimateIssueSeverity> = {
[DiagnosticSeverity.Error]: 'critical',
[DiagnosticSeverity.Warning]: 'major',
[DiagnosticSeverity.Information]: 'minor',
[DiagnosticSeverity.Hint]: 'info',
};

export const codeClimate: Formatter = results => {
const outputJson: CodeClimateIssue[] = results.map(result => {
const relPath = relative(process.cwd(), result.source ?? '').replace(/\\/g, '/');
const fingerprint = `${relPath}:${result.path.join('.')}:${result.code}`;
return {
type: 'issue' as const,
check_name: result.code.toString(),
description: result.message,
categories: ['Style'],
location: {
path: relPath,
positions: {
begin: { line: result.range.start.line, column: result.range.start.character },
end: { line: result.range.end.line, column: result.range.end.character },
},
},
severity: severityMap[result.severity],
fingerprint,
};
});
return JSON.stringify(outputJson, null, '\t');
};
1 change: 1 addition & 0 deletions packages/formatters/src/index.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export type { Formatter, FormatterOptions } from './index';
export { pretty } from './pretty';
export { githubActions } from './github-actions';
export { sarif } from './sarif';
export { codeClimate } from './code-climate';
4 changes: 4 additions & 0 deletions packages/formatters/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ export const githubActions: Formatter = () => {
export const sarif: Formatter = () => {
throw Error('sarif formatter is available only in Node.js');
};

export const codeClimate: Formatter = () => {
throw Error('sarif formatter is available only in Node.js');
};
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 provided by using multiple flags [string] [choices: "json", "stylish", "junit", "html", "text", "teamcity", "pretty", "github-actions", "sarif", "markdown"] [default: "stylish"]
-f, --format formatters to use for outputting results, more than one can be provided by using multiple flags [string] [choices: "json", "stylish", "junit", "html", "text", "teamcity", "pretty", "github-actions", "sarif", "code-climate", "gitlab", "markdown"] [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 provided by using multiple flags [string] [choices: "json", "stylish", "junit", "html", "text", "teamcity", "pretty", "github-actions", "sarif", "markdown"] [default: "stylish"]
-f, --format formatters to use for outputting results, more than one can be provided by using multiple flags [string] [choices: "json", "stylish", "junit", "html", "text", "teamcity", "pretty", "github-actions", "sarif", "code-climate", "gitlab", "markdown"] [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