From a05ccf3dcbe7a46ba296a9882a9728c4ce6efb24 Mon Sep 17 00:00:00 2001 From: Affankhushnod1994 Date: Tue, 19 Mar 2024 18:20:01 +0700 Subject: [PATCH] Support suppressed lint message (#163) * Bump ESLint version * Add logic to add ESLint SuppresedLintMessage * revert eslint version * Map SuppressedMessages to messages with severity = 0 * Make SuppressedMessages in ESLintLog optional. * Add Unit Test for parsing severity and fix import in ESLintParser.ts --------- Co-authored-by: akhushnood --- sample/eslint/eslint-output.json | 17 ++++++++- src/Parser/@types/ESLintLog.ts | 2 ++ src/Parser/@types/SuppressedESLintIssue.ts | 6 ++++ src/Parser/@types/SuprressionInfo.ts | 4 +++ src/Parser/ESLintParser.spec.ts | 40 ++++++++++++++++++++-- src/Parser/ESLintParser.ts | 12 +++++++ 6 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 src/Parser/@types/SuppressedESLintIssue.ts create mode 100644 src/Parser/@types/SuprressionInfo.ts diff --git a/sample/eslint/eslint-output.json b/sample/eslint/eslint-output.json index fa67d7b..e15c3ee 100644 --- a/sample/eslint/eslint-output.json +++ b/sample/eslint/eslint-output.json @@ -2,6 +2,7 @@ { "filePath": "C:\\src\\github.com\\codeleague\\codecoach\\src\\app.constants.ts", "messages": [], + "suppressedMessages": [], "errorCount": 0, "warningCount": 0, "fixableErrorCount": 0, @@ -31,6 +32,20 @@ "endColumn": 30 } ], + "suppressedMessages": [ + { + "ruleId": "@typescript-eslint/no-unused-vars", + "severity": 1, + "message": "'supContent' is defined but never used.", + "line": 30, + "column": 10, + "nodeType": "Identifier", + "messageId": "unusedVar", + "endLine": 30, + "endColumn": 30, + "suppressions": [{ "kind": "directive", "justification": "" }] + } + ], "errorCount": 1, "warningCount": 1, "fixableErrorCount": 0, @@ -38,4 +53,4 @@ "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 {\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 {\r\n await File.writeFileHelper(Config.app.logFilePath, JSON.stringify(logs, null, 2));\r\n }\r\n}\r\n\r\nnew App(", "usedDeprecatedRules": [] } -] \ No newline at end of file +] diff --git a/src/Parser/@types/ESLintLog.ts b/src/Parser/@types/ESLintLog.ts index f78a4bd..f42fd69 100644 --- a/src/Parser/@types/ESLintLog.ts +++ b/src/Parser/@types/ESLintLog.ts @@ -1,8 +1,10 @@ import { ESLintIssue } from './ESLintIssue'; +import { SuppressedESLintIssue } from './SuppressedESLintIssue'; export type ESLintLog = { filePath: string; messages: ESLintIssue[]; + suppressedMessages?: SuppressedESLintIssue[]; errorCount: number; warningCount: number; fixableErrorCount: number; diff --git a/src/Parser/@types/SuppressedESLintIssue.ts b/src/Parser/@types/SuppressedESLintIssue.ts new file mode 100644 index 0000000..2ec6ad4 --- /dev/null +++ b/src/Parser/@types/SuppressedESLintIssue.ts @@ -0,0 +1,6 @@ +import { ESLintIssue } from './ESLintIssue'; +import { SuppressionInfo } from './SuprressionInfo'; + +export type SuppressedESLintIssue = ESLintIssue & { + suppressions: SuppressionInfo[]; +}; diff --git a/src/Parser/@types/SuprressionInfo.ts b/src/Parser/@types/SuprressionInfo.ts new file mode 100644 index 0000000..9754d84 --- /dev/null +++ b/src/Parser/@types/SuprressionInfo.ts @@ -0,0 +1,4 @@ +export type SuppressionInfo = { + kind: string; + justification: string; +}; diff --git a/src/Parser/ESLintParser.spec.ts b/src/Parser/ESLintParser.spec.ts index 9fcde3a..9237897 100644 --- a/src/Parser/ESLintParser.spec.ts +++ b/src/Parser/ESLintParser.spec.ts @@ -16,6 +16,7 @@ describe('ESLintParser', () => { column: 8, }, ], + suppressedMessages: [], errorCount: 1, warningCount: 0, fixableErrorCount: 0, @@ -38,6 +39,19 @@ describe('ESLintParser', () => { endColumn: 30, }, ], + suppressedMessages: [ + { + ruleId: '@typescript-eslint/no-unused-vars', + severity: 1, + message: "'content' is defined but never used.", + line: 24, + column: 15, + nodeType: 'Identifier', + messageId: 'unusedVar', + endLine: 24, + endColumn: 30, + }, + ], errorCount: 1, warningCount: 1, fixableErrorCount: 0, @@ -51,7 +65,7 @@ describe('ESLintParser', () => { it('Should parse correctly', () => { const result = new ESLintParser(cwd).parse(mockedContentString); - expect(result).toHaveLength(2); + expect(result).toHaveLength(3); expect(result[0]).toEqual({ ruleId: '', @@ -76,6 +90,18 @@ describe('ESLintParser', () => { valid: true, type: 'eslint', }); + + expect(result[2]).toEqual({ + ruleId: '@typescript-eslint/no-unused-vars', + source: `src/app.ts`, + severity: LintSeverity.ignore, + line: 24, + lineOffset: 15, + msg: `'content' is defined but never used.`, + log: JSON.stringify({ ...mockedContent[1].messages[0], severity: 0 }), + valid: true, + type: 'eslint', + }); }); it('Should do nothing if put empty string', () => { @@ -87,10 +113,20 @@ describe('ESLintParser', () => { const result = new ESLintParser(cwd).parse(mockedContentString); const valid = result.filter((el) => el.valid); const invalid = result.filter((el) => !el.valid); - expect(valid).toHaveLength(1); + expect(valid).toHaveLength(2); expect(invalid).toHaveLength(1); }); + it('Should parse with severity correctly', () => { + const result = new ESLintParser(cwd).parse(mockedContentString); + const resultWithError = result.filter((el) => el.severity === LintSeverity.error); + const resultWithWarning = result.filter((el) => el.severity === LintSeverity.warning); + const ignoredResult = result.filter((el) => el.severity === LintSeverity.ignore); + expect(resultWithError).toHaveLength(1); + expect(resultWithWarning).toHaveLength(1); + expect(ignoredResult).toHaveLength(1); + }); + it('Should throw error if the line not match the rule', () => { expect(() => new ESLintParser(cwd).parse(':')).toThrow(); }); diff --git a/src/Parser/ESLintParser.ts b/src/Parser/ESLintParser.ts index 1277aa6..2e2801f 100644 --- a/src/Parser/ESLintParser.ts +++ b/src/Parser/ESLintParser.ts @@ -13,6 +13,18 @@ export class ESLintParser extends Parser { const logs = JSON.parse(content) as ESLintLog[]; return logs .filter((log) => log.messages.length !== 0) + .concat( + logs + .filter((log) => log.suppressedMessages?.length !== 0) + .map((log) => { + const messages = + log.suppressedMessages?.map((msg) => ({ + ...msg, + severity: 0, + })) ?? []; + return { ...log, messages: messages }; + }), + ) .flatMap((log) => { const source = getRelativePath(this.cwd, log.filePath); return log.messages.map((msg) => ESLintParser.toLintItem(msg, source));