From 41b736ba4edc779b4e88934ab2fa1512d98c8564 Mon Sep 17 00:00:00 2001 From: MeilCli Date: Mon, 10 May 2021 19:29:31 +0900 Subject: [PATCH] feature: add junit compatibility of cpplint --- README.md | 2 +- __test__/transformer/junit.test.ts | 38 +++++++ data/cpplint/command.md | 3 + data/cpplint/test.cpp | 3 + data/junit_cpplint.xml | 4 + documents/transformer/junit.md | 1 + src/transformer/junit.ts | 23 +++-- src/transformer/junit/convert.ts | 3 +- .../junit/junit-handler-cpplint.ts | 99 +++++++++++++++++++ 9 files changed, 168 insertions(+), 8 deletions(-) create mode 100644 data/cpplint/command.md create mode 100644 data/cpplint/test.cpp create mode 100644 data/junit_cpplint.xml create mode 100644 src/transformer/junit/junit-handler-cpplint.ts diff --git a/README.md b/README.md index c71ffae5..eb8b9dcd 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Current supporting lint file format: - markdownlint - checkstyle - junit - - compatibility: eslint, textlint + - compatibility: eslint, textlint, cpplint ### Feature request Now, this action is WIP. Features are not enough and should improve about transformer and operator. If you have nice idea, please send as issue:heart: diff --git a/__test__/transformer/junit.test.ts b/__test__/transformer/junit.test.ts index 63fe5495..ae200731 100644 --- a/__test__/transformer/junit.test.ts +++ b/__test__/transformer/junit.test.ts @@ -67,3 +67,41 @@ test("transformTextlint", async () => { level: "warning", } as LintResult); }); + +test("transformCpplint", async () => { + const text = fs.readFileSync("data/junit_cpplint.xml", "utf-8"); + const transformer = new JunitTransformer(); + const result = transformer.parse(text); + + expect(result.length).toBe(3); + expect(result[0]).toMatchObject({ + path: "data/cpplint/test.cpp", + rule: "legal/copyright", + message: `No copyright message found. You should have a line: "Copyright [year] "`, + startLine: 1, + endLine: undefined, + startColumn: undefined, + endColumn: undefined, + level: "failure", + } as LintResult); + expect(result[1]).toMatchObject({ + path: "data/cpplint/test.cpp", + rule: "whitespace/braces", + message: `Missing space before {`, + startLine: 1, + endLine: undefined, + startColumn: undefined, + endColumn: undefined, + level: "failure", + } as LintResult); + expect(result[2]).toMatchObject({ + path: "data/cpplint/test.cpp", + rule: "whitespace/ending_newline", + message: `Could not find a newline character at the end of the file.`, + startLine: 3, + endLine: undefined, + startColumn: undefined, + endColumn: undefined, + level: "failure", + } as LintResult); +}); diff --git a/data/cpplint/command.md b/data/cpplint/command.md new file mode 100644 index 00000000..f9b48efd --- /dev/null +++ b/data/cpplint/command.md @@ -0,0 +1,3 @@ +1. `pip install cpplint` +1. `cpplint --output junit data/cpplint/test.cpp` +1. write stdout to `data/junit_cpplint.xml` \ No newline at end of file diff --git a/data/cpplint/test.cpp b/data/cpplint/test.cpp new file mode 100644 index 00000000..eeb66078 --- /dev/null +++ b/data/cpplint/test.cpp @@ -0,0 +1,3 @@ +void method(){ + println("test"); +} \ No newline at end of file diff --git a/data/junit_cpplint.xml b/data/junit_cpplint.xml new file mode 100644 index 00000000..5b6810fa --- /dev/null +++ b/data/junit_cpplint.xml @@ -0,0 +1,4 @@ + +0: No copyright message found. You should have a line: "Copyright [year] <Copyright Owner>" [legal/copyright] [5] +1: Missing space before { [whitespace/braces] [5] +3: Could not find a newline character at the end of the file. [whitespace/ending_newline] [5] \ No newline at end of file diff --git a/documents/transformer/junit.md b/documents/transformer/junit.md index 18f70b0e..d0fb44e9 100644 --- a/documents/transformer/junit.md +++ b/documents/transformer/junit.md @@ -14,6 +14,7 @@ The compatibility is not needed because this action has standard resolver. But, Now compatibility: - eslint - textlint +- cpplint ## Option ### Input diff --git a/src/transformer/junit.ts b/src/transformer/junit.ts index 34d7d9fe..4f2547d9 100644 --- a/src/transformer/junit.ts +++ b/src/transformer/junit.ts @@ -8,7 +8,7 @@ import { JunitTestSuite, JunitTestCase, JunitTestMessage } from "./junit/entity" import { convertJunitToLintResult } from "./junit/convert"; interface JunitResult { - testsuites: TestSuites[]; + testsuites: TestSuites[] | undefined; } interface TestSuites { @@ -25,8 +25,8 @@ interface TestSuite { interface TestCase { name: string; classname: string; - error: TestMessage[] | undefined; - failure: TestMessage[] | undefined; + error: TestMessage[] | string | undefined; + failure: TestMessage[] | string | undefined; } interface TestMessage { @@ -44,8 +44,16 @@ export class JunitTransformer extends Transformer { attrValueProcessor: (value, _) => he.decode(value), }) as JunitResult; const junitTestSuites: JunitTestSuite[] = []; - for (const testSuites of junitResult.testsuites) { - junitTestSuites.push(...this.parseTestSuites(testSuites.testsuite)); + if (junitResult.testsuites != undefined) { + for (const testSuites of junitResult.testsuites) { + junitTestSuites.push(...this.parseTestSuites(testSuites.testsuite)); + } + } else { + // for cpplint + const testSuites = (junitResult as unknown) as TestSuites; + if (testSuites.testsuite != undefined) { + junitTestSuites.push(...this.parseTestSuites(testSuites.testsuite)); + } } return convertJunitToLintResult(junitTestSuites); } @@ -82,10 +90,13 @@ export class JunitTransformer extends Transformer { return result; } - private parseTestMessages(testMessages: TestMessage[] | undefined): JunitTestMessage[] { + private parseTestMessages(testMessages: TestMessage[] | string | undefined): JunitTestMessage[] { if (testMessages == undefined) { return []; } + if (typeof testMessages == "string") { + return [{ message: testMessages, body: testMessages }]; + } const result: JunitTestMessage[] = []; for (const testMessage of testMessages) { result.push({ diff --git a/src/transformer/junit/convert.ts b/src/transformer/junit/convert.ts index 316cf5d5..5ccb3306 100644 --- a/src/transformer/junit/convert.ts +++ b/src/transformer/junit/convert.ts @@ -3,8 +3,9 @@ import { JunitTestSuite } from "./entity"; import { JunitHandler } from "./junit-handler"; import { DefaultJunitHandler } from "./junit-handler-default"; import { EslintJunitHandler } from "./junit-handler-eslint"; +import { CpplintJunitHandler } from "./junit-handler-cpplint"; -const handlers: JunitHandler[] = [new EslintJunitHandler(), new DefaultJunitHandler()]; +const handlers: JunitHandler[] = [new EslintJunitHandler(), new CpplintJunitHandler(), new DefaultJunitHandler()]; export function convertJunitToLintResult(testSuites: JunitTestSuite[]): LintResult[] { for (const handler of handlers) { diff --git a/src/transformer/junit/junit-handler-cpplint.ts b/src/transformer/junit/junit-handler-cpplint.ts new file mode 100644 index 00000000..03003dd7 --- /dev/null +++ b/src/transformer/junit/junit-handler-cpplint.ts @@ -0,0 +1,99 @@ +import { JunitTestSuite, JunitTestCase } from "./entity"; +import { LintResult } from "../../lint-result"; +import { JunitHandler } from "./junit-handler"; +import * as he from "he"; + +export class CpplintJunitHandler implements JunitHandler { + match(testSuites: JunitTestSuite[]): boolean { + if (testSuites.length == 0) { + return false; + } + return testSuites[0].name == "cpplint"; + } + + handle(testSuites: JunitTestSuite[]): LintResult[] { + const result: LintResult[] = []; + this.handleTestSuites(result, testSuites); + return result; + } + + private handleTestSuites(result: LintResult[], testSuites: JunitTestSuite[]) { + for (const testSuite of testSuites) { + this.handleTestSuite(result, testSuite); + } + } + + private handleTestSuite(result: LintResult[], testSuite: JunitTestSuite) { + this.handleTestCases(result, testSuite.testCases); + this.handleTestSuites(result, testSuite.testSuites); + } + + private handleTestCases(result: LintResult[], testCases: JunitTestCase[]) { + for (const testCase of testCases) { + this.handleTestCase(result, testCase); + } + } + + private handleTestCase(result: LintResult[], testCase: JunitTestCase) { + for (const failure of testCase.failures) { + for (const message of this.parseBody(failure.body)) { + result.push({ + path: testCase.name, + message: message[1], + level: message[3] == 5 ? "failure" : 3 <= message[3] ? "warning" : "notice", + rule: message[2], + startLine: message[0], + startColumn: undefined, + endLine: undefined, + endColumn: undefined, + }); + } + } + for (const error of testCase.errors) { + for (const message of this.parseBody(error.body)) { + result.push({ + path: testCase.name, + message: message[1], + level: message[3] == 5 ? "failure" : 3 <= message[3] ? "warning" : "notice", + rule: message[2], + startLine: message[0], + startColumn: undefined, + endLine: undefined, + endColumn: undefined, + }); + } + } + } + + private parseBody(body: string): [number, string, string, number][] { + const result: [number, string, string, number][] = []; + for (const line of body.split(/(\r\n)|\n|\r/g)) { + if (line == undefined || line.length == 0) { + continue; + } + + const rawStartLine = line.replace(/^(\d+):\s(.+)\s\[(.+?)\]\s\[(\d)\]$/, "$1"); + const rawMessage = line.replace(/^(\d+):\s(.+)\s\[(.+?)\]\s\[(\d)\]$/, "$2"); + const rawRule = line.replace(/^(\d+):\s(.+)\s\[(.+?)\]\s\[(\d)\]$/, "$3"); + const rawConfidence = line.replace(/^(\d+):\s(.+)\s\[(.+?)\]\s\[(\d)\]$/, "$4"); + if ( + rawStartLine.length == 0 || + rawMessage.length == 0 || + rawRule.length == 0 || + rawConfidence.length == 0 + ) { + continue; + } + const startLine = parseInt(rawStartLine); + const message = he.decode(rawMessage); + const rule = he.decode(rawRule); + const confidence = parseInt(rawConfidence); + if (Number.isInteger(startLine) == false || Number.isInteger(confidence) == false) { + continue; + } + + result.push([startLine == 0 ? 1 : startLine, message, rule, confidence]); + } + return result; + } +}