Skip to content

Commit c7c9102

Browse files
encXpdujtipiya
and
pdujtipiya
authored
Add output file format options (#153)
* Add new config: output format * Add output formatter * Add fingerprint * code style update * Update doc * Add ref * Rename log -> lintItem * Add test yay * Rename LogSeverity -> LintSeverity * Fix naming for AndroidLintStyleParser * fix styling --------- Co-authored-by: pdujtipiya <[email protected]>
1 parent ae8e86d commit c7c9102

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+365
-228
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ Will do the same thing.
103103
| `failOnWarnings` | no | `true` or `false` | Fail the job when warnings are found |
104104
| `dryRun` | no | `true` or `false` | Running CodeCoach without reporting to VCS |
105105
| `suppressRules` | no | `rule-group-1/.*` `rule-id-1` `rule-id-2` | Rule IDs that CodeCoach will still comment but no longer treated as errors or warnings |
106+
| `outputFormat` | no | `default`, `gitlab` | Output file format |
106107

107108

108109
##### `--buildLogFile` or `-f`

sample/eslint/eslint-output.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"warningCount": 1,
3636
"fixableErrorCount": 0,
3737
"fixableWarningCount": 0,
38-
"source": "#!/usr/bin/env node\r\n\r\nimport { Config, ProjectType } from './Config';\r\nimport { File } from './File';\r\nimport { Log } from './Logger';\r\nimport { CSharpParser, LogType, Parser, TSLintParser } from './Parser';\r\nimport { GitHub, GitHubPRService, VCS } from './Provider';\r\n\r\nclass App {\r\n private readonly parser: Parser;\r\n private readonly vcs: VCS\r\n\r\n constructor() {\r\n this.parser = App.setProjectType(Config.app.projectType);\r\n const githubPRService = new GitHubPRService(\r\n Config.provider.token,\r\n Config.provider.repoUrl,\r\n Config.provider.prId,\r\n );\r\n this.vcs = new GitHub(githubPRService);\r\n }\r\n\r\n async start() {\r\n const logs = await this.parseBuildData(Config.app.buildLogFiles);\r\n Log.info('Build data parsing completed');\r\n\r\n await this.vcs.report(logs);\r\n Log.info('Report to VCS completed');\r\n\r\n await App.writeLogToFile(logs);\r\n Log.info('Write output completed');\r\n }\r\n\r\n private static setProjectType(type: ProjectType): Parser {\r\n switch (type) {\r\n case ProjectType.csharp:\r\n return new CSharpParser(Config.app.cwd);\r\n case ProjectType.tslint:\r\n return new TSLintParser(Config.app.cwd);\r\n }\r\n }\r\n\r\n private async parseBuildData(files: string[]): Promise<LogType[]> {\r\n const parserTasks = files.map(async (file) => {\r\n const content = await File.readFileHelper(file);\r\n this.parser.withContent(content);\r\n });\r\n\r\n await Promise.all(parserTasks);\r\n\r\n return this.parser.getLogs();\r\n }\r\n\r\n private static async writeLogToFile(logs: LogType[]): Promise<void> {\r\n await File.writeFileHelper(Config.app.logFilePath, JSON.stringify(logs, null, 2));\r\n }\r\n}\r\n\r\nnew App(",
38+
"source": "#!/usr/bin/env node\r\n\r\nimport { Config, ProjectType } from './Config';\r\nimport { File } from './File';\r\nimport { Log } from './Logger';\r\nimport { CSharpParser, LintItem, Parser, TSLintParser } from './Parser';\r\nimport { GitHub, GitHubPRService, VCS } from './Provider';\r\n\r\nclass App {\r\n private readonly parser: Parser;\r\n private readonly vcs: VCS\r\n\r\n constructor() {\r\n this.parser = App.setProjectType(Config.app.projectType);\r\n const githubPRService = new GitHubPRService(\r\n Config.provider.token,\r\n Config.provider.repoUrl,\r\n Config.provider.prId,\r\n );\r\n this.vcs = new GitHub(githubPRService);\r\n }\r\n\r\n async start() {\r\n const logs = await this.parseBuildData(Config.app.buildLogFiles);\r\n Log.info('Build data parsing completed');\r\n\r\n await this.vcs.report(logs);\r\n Log.info('Report to VCS completed');\r\n\r\n await App.writeLogToFile(logs);\r\n Log.info('Write output completed');\r\n }\r\n\r\n private static setProjectType(type: ProjectType): Parser {\r\n switch (type) {\r\n case ProjectType.csharp:\r\n return new CSharpParser(Config.app.cwd);\r\n case ProjectType.tslint:\r\n return new TSLintParser(Config.app.cwd);\r\n }\r\n }\r\n\r\n private async parseBuildData(files: string[]): Promise<LintItem[]> {\r\n const parserTasks = files.map(async (file) => {\r\n const content = await File.readFileHelper(file);\r\n this.parser.withContent(content);\r\n });\r\n\r\n await Promise.all(parserTasks);\r\n\r\n return this.parser.getLogs();\r\n }\r\n\r\n private static async writeLogToFile(items: LintItem[]): Promise<void> {\r\n await File.writeFileHelper(Config.app.logFilePath, JSON.stringify(logs, null, 2));\r\n }\r\n}\r\n\r\nnew App(",
3939
"usedDeprecatedRules": []
4040
}
4141
]

sample/git-diff/deletesection.diff

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
Log.debug(`Commit SHA ${commitId}`);
44
Log.debug('Touched files', touchedFiles);
5-
- Log.debug('Touched file log', touchedFileLog);
5+
- Log.debug('Touched file log', touchedFileItem);
66

77
const reviewResults = await Promise.all(
8-
touchedFileLog.map((log) => this.toCreateReviewComment(commitId, log)),
8+
touchedFileItem.map((log) => this.toCreateReviewComment(commitId, log)),
99
@@ -83,10 +82,12 @@ ${issuesTableContent}
1010
log.source,
1111
log.line,

sample/git-diff/multi.diff

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import { getRelativePath } from '../Provider/utils/path.util';
55
-import { LogSeverity } from './@enums/log.severity.enum';
66
import { Parser } from './@interfaces/parser.interface';
7-
import { LogType } from './@types';
7+
import { LintItem } from './@types';
88
+import { mapSeverity } from './utils/dotnetSeverityMap';
99
import { splitByLine } from './utils/lineBreak.util';
1010

src/AnalyzerBot/@interfaces/IAnalyzerBot.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import { LogType } from '../../Parser';
1+
import { LintItem } from '../../Parser';
22
import { Comment } from '../@types/CommentTypes';
33
import { Diff } from '../../Git/@types/PatchTypes';
44

55
export interface IAnalyzerBot {
6-
touchedFileLog: LogType[];
6+
touchedFileItem: LintItem[];
77
comments: Comment[];
88
nError: number;
99
nWarning: number;
1010

11-
analyze(logs: LogType[], touchedDiff: Diff[]): void;
11+
analyze(items: LintItem[], touchedDiff: Diff[]): void;
1212

1313
shouldGenerateOverview(): boolean;
1414

src/AnalyzerBot/AnalyzerBot.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ describe('AnalyzerBot', () => {
2121
const diff = [mockTouchDiff];
2222
const analyzer = new AnalyzerBot(config);
2323

24-
describe('.touchedFileLog', () => {
24+
describe('.touchedFileItem', () => {
2525
it('should return only logs that are in touchedDiff', () => {
2626
analyzer.analyze(logs, diff);
27-
expect(analyzer.touchedFileLog).toEqual([touchFileError, touchFileWarning]);
27+
expect(analyzer.touchedFileItem).toEqual([touchFileError, touchFileWarning]);
2828
});
2929
});
3030

src/AnalyzerBot/AnalyzerBot.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { LogSeverity, LogType } from '../Parser';
1+
import { LintSeverity, LintItem } from '../Parser';
22
import { Diff } from '../Git/@types/PatchTypes';
33
import { onlyIn, onlySeverity } from './utils/filter.util';
44
import { groupComments } from './utils/commentUtil';
@@ -8,18 +8,18 @@ import { Comment } from './@types/CommentTypes';
88
import { IAnalyzerBot } from './@interfaces/IAnalyzerBot';
99

1010
export class AnalyzerBot implements IAnalyzerBot {
11-
touchedFileLog: LogType[];
11+
touchedFileItem: LintItem[];
1212
comments: Comment[];
1313
nError: number;
1414
nWarning: number;
1515

1616
constructor(private readonly config: AnalyzerBotConfig) {}
1717

18-
analyze(logs: LogType[], touchedDiff: Diff[]): void {
19-
this.touchedFileLog = logs
20-
.filter(onlySeverity(LogSeverity.error, LogSeverity.warning))
18+
analyze(items: LintItem[], touchedDiff: Diff[]): void {
19+
this.touchedFileItem = items
20+
.filter(onlySeverity(LintSeverity.error, LintSeverity.warning))
2121
.filter(onlyIn(touchedDiff));
22-
this.comments = groupComments(this.touchedFileLog, this.config.suppressRules);
22+
this.comments = groupComments(this.touchedFileItem, this.config.suppressRules);
2323
this.nError = this.comments.reduce((sum, comment) => sum + comment.errors, 0);
2424
this.nWarning = this.comments.reduce((sum, comment) => sum + comment.warnings, 0);
2525
}

src/AnalyzerBot/utils/commentUtil.spec.ts

+11-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { LogSeverity, LogType } from '../../Parser';
1+
import { LintSeverity, LintItem } from '../../Parser';
22
import {
33
file1TouchLine,
44
file2TouchLine,
@@ -9,10 +9,10 @@ import {
99
import { groupComments } from './commentUtil';
1010

1111
describe('groupComments', () => {
12-
const logs: LogType[] = [touchFileError, touchFileWarning];
12+
const items: LintItem[] = [touchFileError, touchFileWarning];
1313

14-
it('returns comments based on lint logs', () => {
15-
const comments = groupComments(logs, []);
14+
it('returns comments based on lint items', () => {
15+
const comments = groupComments(items, []);
1616
expect(comments).toEqual([
1717
{
1818
file: mockTouchFile,
@@ -35,14 +35,14 @@ describe('groupComments', () => {
3535
]);
3636
});
3737

38-
it('group multiple logs on the same line to the same comment', () => {
38+
it('group multiple items on the same line to the same comment', () => {
3939
const comments = groupComments(
4040
[
41-
...logs,
41+
...items,
4242
{
4343
...touchFileError,
4444
msg: 'additional warning',
45-
severity: LogSeverity.warning,
45+
severity: LintSeverity.warning,
4646
lineOffset: 33,
4747
},
4848
],
@@ -74,11 +74,11 @@ describe('groupComments', () => {
7474
it('suppress errors and warnings according to provided suppressRules', () => {
7575
const comments = groupComments(
7676
[
77-
...logs,
77+
...items,
7878
{
7979
...touchFileError,
8080
msg: 'additional warning',
81-
severity: LogSeverity.warning,
81+
severity: LintSeverity.warning,
8282
lineOffset: 33,
8383
ruleId: 'UNIMPORTANT_RULE2',
8484
},
@@ -115,11 +115,11 @@ describe('groupComments', () => {
115115
it('support regexp in suppressRules', () => {
116116
const comments = groupComments(
117117
[
118-
...logs,
118+
...items,
119119
{
120120
...touchFileError,
121121
msg: 'additional warning',
122-
severity: LogSeverity.warning,
122+
severity: LintSeverity.warning,
123123
lineOffset: 33,
124124
ruleId: 'UNIMPORTANT_RULE/RULE2',
125125
},

src/AnalyzerBot/utils/commentUtil.ts

+27-20
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1-
import { LogSeverity, LogType } from '../../Parser';
1+
import { LintSeverity, LintItem } from '../../Parser';
22
import { Comment, CommentStructure } from '../@types/CommentTypes';
33
import { MessageUtil } from './message.util';
44

5-
export function groupComments(logs: LogType[], suppressRules: Array<string>): Comment[] {
6-
const commentMap = logs.reduce((state: CommentStructure, log) => {
7-
const { source: file, line, nLines } = log;
5+
export function groupComments(
6+
items: LintItem[],
7+
suppressRules: Array<string>,
8+
): Comment[] {
9+
const commentMap = items.reduce((state: CommentStructure, item) => {
10+
const { source: file, line, nLines } = item;
811

912
if (!line) return state;
1013

1114
const currentComment = getOrInitComment(state, file, line, nLines);
12-
const updatedComment = updateComment(currentComment, log, suppressRules);
15+
const updatedComment = updateComment(currentComment, item, suppressRules);
1316
return updateCommentStructure(state, updatedComment);
1417
}, {});
1518

@@ -35,8 +38,12 @@ function getOrInitComment(
3538
);
3639
}
3740

38-
function buildText(currentComment: Comment, log: LogType, isSuppressed: boolean): string {
39-
const { severity, msg } = log;
41+
function buildText(
42+
currentComment: Comment,
43+
item: LintItem,
44+
isSuppressed: boolean,
45+
): string {
46+
const { severity, msg } = item;
4047
const { text: currentText } = currentComment;
4148
const msgWithSuppression = isSuppressed ? `(SUPPRESSED) ${msg}` : msg;
4249
const text = MessageUtil.createMessageWithEmoji(msgWithSuppression, severity);
@@ -45,43 +52,43 @@ function buildText(currentComment: Comment, log: LogType, isSuppressed: boolean)
4552

4653
function calculateErrors(
4754
currentComment: Comment,
48-
log: LogType,
55+
item: LintItem,
4956
isSuppressed: boolean,
5057
): number {
5158
if (isSuppressed) return currentComment.errors;
52-
const { severity } = log;
53-
return currentComment.errors + (severity === LogSeverity.error ? 1 : 0);
59+
const { severity } = item;
60+
return currentComment.errors + (severity === LintSeverity.error ? 1 : 0);
5461
}
5562

5663
function calculateWarnings(
5764
currentComment: Comment,
58-
log: LogType,
65+
item: LintItem,
5966
isSuppressed: boolean,
6067
): number {
6168
if (isSuppressed) return currentComment.warnings;
62-
const { severity } = log;
63-
return currentComment.warnings + (severity === LogSeverity.warning ? 1 : 0);
69+
const { severity } = item;
70+
return currentComment.warnings + (severity === LintSeverity.warning ? 1 : 0);
6471
}
6572

6673
function calculateSuppresses(currentComment: Comment, isSuppressed: boolean): number {
6774
return currentComment.suppresses + (isSuppressed ? 1 : 0);
6875
}
6976

70-
function shouldBeSuppressed(log: LogType, suppressRules: Array<string>): boolean {
77+
function shouldBeSuppressed(item: LintItem, suppressRules: Array<string>): boolean {
7178
const suppressRegexps: Array<RegExp> = suppressRules.map((rule) => new RegExp(rule));
72-
return suppressRegexps.some((regexp) => regexp.test(log.ruleId));
79+
return suppressRegexps.some((regexp) => regexp.test(item.ruleId));
7380
}
7481

7582
function updateComment(
7683
currentComment: Comment,
77-
log: LogType,
84+
item: LintItem,
7885
suppressRules: Array<string>,
7986
): Comment {
80-
const isSuppressed = shouldBeSuppressed(log, suppressRules);
87+
const isSuppressed = shouldBeSuppressed(item, suppressRules);
8188
return {
82-
text: buildText(currentComment, log, isSuppressed),
83-
errors: calculateErrors(currentComment, log, isSuppressed),
84-
warnings: calculateWarnings(currentComment, log, isSuppressed),
89+
text: buildText(currentComment, item, isSuppressed),
90+
errors: calculateErrors(currentComment, item, isSuppressed),
91+
warnings: calculateWarnings(currentComment, item, isSuppressed),
8592
suppresses: calculateSuppresses(currentComment, isSuppressed),
8693
file: currentComment.file,
8794
line: currentComment.line,

src/AnalyzerBot/utils/filter.util.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { LogSeverity, LogType } from '../../Parser';
1+
import { LintSeverity, LintItem } from '../../Parser';
22
import { Diff } from '../../Git/@types/PatchTypes';
33

4-
export const onlyIn = (diffs: Diff[]) => (log: LogType): boolean =>
4+
export const onlyIn = (diffs: Diff[]) => (item: LintItem): boolean =>
55
diffs.some(
66
(d) =>
7-
d.file === log.source &&
8-
d.patch.some((p) => !log.line || (log.line >= p.from && log.line <= p.to)),
7+
d.file === item.source &&
8+
d.patch.some((p) => !item.line || (item.line >= p.from && item.line <= p.to)),
99
);
10-
export const onlySeverity = (...severities: LogSeverity[]) => (log: LogType): boolean =>
11-
severities.includes(log.severity);
10+
export const onlySeverity = (...severities: LintSeverity[]) => (
11+
item: LintItem,
12+
): boolean => severities.includes(item.severity);

src/AnalyzerBot/utils/message.util.spec.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import { LogSeverity } from '../../Parser';
1+
import { LintSeverity } from '../../Parser';
22
import { MessageUtil } from './message.util';
33

44
describe('createMessageWithEmoji', () => {
55
it('should parsed log message to Emoji correctly', () => {
66
// ¯\_(ツ)_/¯
77
const msg = 'test';
88

9-
expect(MessageUtil.createMessageWithEmoji(msg, LogSeverity.error)).toBe(
9+
expect(MessageUtil.createMessageWithEmoji(msg, LintSeverity.error)).toBe(
1010
`:rotating_light: ${msg}`,
1111
);
12-
expect(MessageUtil.createMessageWithEmoji(msg, LogSeverity.warning)).toBe(
12+
expect(MessageUtil.createMessageWithEmoji(msg, LintSeverity.warning)).toBe(
1313
`:warning: ${msg}`,
1414
);
1515
});

src/AnalyzerBot/utils/message.util.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
import { LogSeverity } from '../../Parser';
1+
import { LintSeverity } from '../../Parser';
22

33
const EMOJI_ERROR = ':rotating_light:';
44
const EMOJI_WARNING = ':warning:';
55

66
export class MessageUtil {
7-
static createMessageWithEmoji(msg: string, severity: LogSeverity): string {
7+
static createMessageWithEmoji(msg: string, severity: LintSeverity): string {
88
let emoji = '';
99
switch (severity) {
10-
case LogSeverity.error:
10+
case LintSeverity.error:
1111
emoji = EMOJI_ERROR;
1212
break;
13-
case LogSeverity.warning:
13+
case LintSeverity.warning:
1414
emoji = EMOJI_WARNING;
1515
break;
1616
}
@@ -24,11 +24,11 @@ export class MessageUtil {
2424
const issueCountMsg = this.pluralize('issue', nOfErrors + nOfWarnings);
2525
const errorMsg = MessageUtil.createMessageWithEmoji(
2626
MessageUtil.pluralize('error', nOfErrors),
27-
LogSeverity.error,
27+
LintSeverity.error,
2828
);
2929
const warningMsg = MessageUtil.createMessageWithEmoji(
3030
MessageUtil.pluralize('warning', nOfWarnings),
31-
LogSeverity.warning,
31+
LintSeverity.warning,
3232
);
3333

3434
return `## CodeCoach reports ${issueCountMsg}

src/Config/@enums/outputFormat.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export enum OutputFormat {
2+
default = 'default',
3+
gitlab = 'gitlab',
4+
}

src/Config/@types/configArgument.ts

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export type ConfigArgument = {
1111
gitlabToken: string;
1212
buildLogFile: BuildLogFile[];
1313
output: string; // =logFilePath
14+
outputFormat: 'default' | 'gitlab';
1415
removeOldComment: boolean;
1516
failOnWarnings: boolean;
1617
suppressRules: string[];

src/Config/Config.spec.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ const mockGitLabProjectId = 1234;
99
const mockGitLabMrIid = 69;
1010
const mockGitLabToken = 'mockGitLabToken';
1111

12-
const mockLogType = 'dotnetbuild';
12+
const mockLintItem = 'dotnetbuild';
1313
const mockLogFile = './sample/dotnetbuild/build.content';
1414
const mockLogCwd = '/repo/src';
15-
const mockBuildLogFile = `${mockLogType};${mockLogFile};${mockLogCwd}`;
15+
const mockBuildLogFile = `${mockLintItem};${mockLogFile};${mockLogCwd}`;
1616
const mockOutput = './tmp/out.json';
1717

1818
const GITHUB_ENV_ARGS = [
@@ -64,7 +64,7 @@ describe('Config parsing Test', () => {
6464

6565
const validateBuildLog = (buildLog: BuildLogFile[]) => {
6666
expect(buildLog).toHaveLength(1);
67-
expect(buildLog[0].type).toBe(mockLogType);
67+
expect(buildLog[0].type).toBe(mockLintItem);
6868
expect(buildLog[0].path).toBe(mockLogFile);
6969
expect(buildLog[0].cwd).toBe(mockLogCwd);
7070
};

src/Config/Config.ts

+5
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ and <cwd> is build root directory (optional (Will use current context as cwd)).
9292
type: 'string',
9393
default: DEFAULT_OUTPUT_FILE,
9494
})
95+
.option('outputFormat', {
96+
describe: 'Output format',
97+
choices: ['default', 'gitlab'],
98+
default: 'default',
99+
})
95100
.option('removeOldComment', {
96101
alias: 'r',
97102
type: 'boolean',

0 commit comments

Comments
 (0)