diff --git a/action.yml b/action.yml index 4628284..3fd6822 100644 --- a/action.yml +++ b/action.yml @@ -20,5 +20,9 @@ inputs: required: false default: "." outputs: - issuesCount: - description: "Number of eslint violations found" + warning-count: + description: "Number of relevant warnings found" + error-count: + description: "Number of relevant errors found" + total-count: + description: "Number of relevant warnings and errors" diff --git a/dist/index.js b/dist/index.js index 5bb8e08..538ab90 100644 --- a/dist/index.js +++ b/dist/index.js @@ -26257,17 +26257,6 @@ async function run() { changedFiles = await (0, git_utils_1.detectChangedFiles)(compareSha); } console.debug("Changed files:", changedFiles); - // let changeRanges: ChangeRange[] = [] - // - // changedFiles.forEach(async (path) => { - // changeRanges.concat(await generateChangeRanges(path, compareSha)) - // }) - // - // console.debug("Change ranges:", changeRanges) - // let { stdout: lsOut } = await getExecOutput("ls node_modules") - // console.debug("LS out:", lsOut) - // await exec("ls node_modules/eslint/bin") - // TODO: only run on changed files let { stdout: eslintOut } = await (0, exec_1.getExecOutput)("npx eslint --format=json-with-metadata --no-warn-ignored", changedFiles, // Eslint will return exit code 1 if it finds linting problems, but that is // expected and we don't want to stop execution because of it. @@ -26277,28 +26266,110 @@ async function run() { let eslintResults = eslintJson.results?.map((resultObject) => new EslintResult(resultObject, compareSha)); await Promise.all(eslintResults.map((r) => r.asyncInitialize())); console.debug("Eslint results:", eslintResults); - console.debug("Eslint result messages:", eslintResults.map((r) => r.messages)); - console.debug("Eslint result change ranges:", eslintResults.map((r) => r.changeRanges)); - console.debug("Eslint result relevant messages:", eslintResults.map((r) => r.relevantMessages)); + // console.debug( + // "Eslint result messages:", + // eslintResults.map((r) => r.messages), + // ) + // console.debug( + // "Eslint result change ranges:", + // eslintResults.map((r) => r.changeRanges), + // ) + // console.debug( + // "Eslint result relevant messages:", + // eslintResults.map((r) => r.relevantMessages), + // ) + let isFailure = null; + let warningCount = 0; + let errorCount = 0; + eslintResults.forEach((r) => { + r.outputAnnotations(); + warningCount += r.relevantWarningCount; + errorCount += r.relevantErrorCount; + }); + let totalCount = warningCount + errorCount; + core.exportVariable("warning-count", warningCount); + core.exportVariable("error-count", errorCount); + core.exportVariable("total-count", totalCount); + let failureLevel = core.getInput("failure-level").toLowerCase(); + switch (failureLevel) { + case "warning": + if (warningCount > 0 || errorCount > 0) + isFailure = true; + break; + case "error": + if (errorCount > 0) + isFailure = true; + break; + default: + throw new Error("Unrecognized failure-level input"); + } + let exitCodeOnFailure = core.getInput("exit-code-on-failure").toLowerCase(); + if (isFailure && exitCodeOnFailure === "failure") { + core.setFailed(`Action failed because failure-level is ${failureLevel}, exit-code-on-failure is ${exitCodeOnFailure}, and there are ${warningCount} warning(s) and ${errorCount} error(s)`); + } } run(); class EslintResult { + relevantWarningCount = 0; + relevantErrorCount = 0; resultObject; compareSha; - // TODO: make private changeRanges; + relevantMessages = []; constructor(resultObject, compareSha) { this.resultObject = resultObject; this.compareSha = compareSha; } async asyncInitialize() { this.changeRanges = await (0, git_utils_1.generateChangeRanges)(this.resultObject.filePath, this.compareSha); + this.findRelevantMessages(); + this.calculateCounts(); + } + outputAnnotations() { + this.relevantMessages.forEach((msg) => { + let options = { + title: msg.ruleId, + file: this.repoFilePath, + startLine: msg.line, + endLine: msg.endLine, + startColumn: msg.column, + endColumn: msg.endColumn, + }; + switch (msg.severity) { + case 1: + core.warning(msg.message, options); + break; + case 2: + core.error(msg.message, options); + default: + break; + } + }); + } + findRelevantMessages() { + this.relevantMessages = this.messages.filter((m) => this.changeRanges?.some((changeRange) => changeRange.doesInclude(m.line))); + } + calculateCounts() { + this.relevantMessages.forEach((msg) => { + switch (msg.severity) { + case 1: + this.relevantWarningCount += 1; + break; + case 2: + this.relevantErrorCount += 1; + default: + break; + } + }); } get messages() { return this.resultObject.messages; } - get relevantMessages() { - return this.messages.filter((m) => this.changeRanges?.some((changeRange) => changeRange.doesInclude(m.line))); + get repoFilePath() { + let absoluteFolderPath = process.env.GITHUB_WORKSPACE; + if (!absoluteFolderPath) + throw new Error("process.env.GITHUB_WORKSPACE was empty"); + return this.resultObject.filePath.replace(absoluteFolderPath, ""); } } diff --git a/src/index.ts b/src/index.ts index f1fc2c8..e6fc9d1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,7 @@ import { detectChangedFilesInFolder, generateChangeRanges, } from "./git_utils" -import { exec, getExecOutput } from "@actions/exec" +import { getExecOutput } from "@actions/exec" async function run() { console.debug("hello world") @@ -41,19 +41,6 @@ async function run() { console.debug("Changed files:", changedFiles) - // let changeRanges: ChangeRange[] = [] - // - // changedFiles.forEach(async (path) => { - // changeRanges.concat(await generateChangeRanges(path, compareSha)) - // }) - // - // console.debug("Change ranges:", changeRanges) - - // let { stdout: lsOut } = await getExecOutput("ls node_modules") - // console.debug("LS out:", lsOut) - // await exec("ls node_modules/eslint/bin") - - // TODO: only run on changed files let { stdout: eslintOut } = await getExecOutput( "npx eslint --format=json-with-metadata --no-warn-ignored", changedFiles, @@ -71,18 +58,52 @@ async function run() { console.debug("Eslint results:", eslintResults) - console.debug( - "Eslint result messages:", - eslintResults.map((r) => r.messages), - ) - console.debug( - "Eslint result change ranges:", - eslintResults.map((r) => r.changeRanges), - ) - console.debug( - "Eslint result relevant messages:", - eslintResults.map((r) => r.relevantMessages), - ) + // console.debug( + // "Eslint result messages:", + // eslintResults.map((r) => r.messages), + // ) + // console.debug( + // "Eslint result change ranges:", + // eslintResults.map((r) => r.changeRanges), + // ) + // console.debug( + // "Eslint result relevant messages:", + // eslintResults.map((r) => r.relevantMessages), + // ) + + let isFailure = null + let warningCount = 0 + let errorCount = 0 + + eslintResults.forEach((r) => { + r.outputAnnotations() + warningCount += r.relevantWarningCount + errorCount += r.relevantErrorCount + }) + + let totalCount = warningCount + errorCount + core.exportVariable("warning-count", warningCount) + core.exportVariable("error-count", errorCount) + core.exportVariable("total-count", totalCount) + + let failureLevel = core.getInput("failure-level").toLowerCase() + switch (failureLevel) { + case "warning": + if (warningCount > 0 || errorCount > 0) isFailure = true + break + case "error": + if (errorCount > 0) isFailure = true + break + default: + throw new Error("Unrecognized failure-level input") + } + + let exitCodeOnFailure = core.getInput("exit-code-on-failure").toLowerCase() + if (isFailure && exitCodeOnFailure === "failure") { + core.setFailed( + `Action failed because failure-level is ${failureLevel}, exit-code-on-failure is ${exitCodeOnFailure}, and there are ${warningCount} warning(s) and ${errorCount} error(s)`, + ) + } } run() @@ -105,10 +126,12 @@ type ResultObject = { } type EslintResultInstance = InstanceType class EslintResult { + public relevantWarningCount: number = 0 + public relevantErrorCount: number = 0 private resultObject: ResultObject private compareSha: string - // TODO: make private - public changeRanges?: ChangeRange[] + private changeRanges?: ChangeRange[] + private relevantMessages: EslintMessage[] = [] constructor(resultObject: ResultObject, compareSha: string) { this.resultObject = resultObject @@ -120,15 +143,60 @@ class EslintResult { this.resultObject.filePath, this.compareSha, ) + this.findRelevantMessages() + this.calculateCounts() } - get messages() { - return this.resultObject.messages + outputAnnotations() { + this.relevantMessages.forEach((msg) => { + let options: core.AnnotationProperties = { + title: msg.ruleId, + file: this.repoFilePath, + startLine: msg.line, + endLine: msg.endLine, + startColumn: msg.column, + endColumn: msg.endColumn, + } + switch (msg.severity) { + case 1: + core.warning(msg.message, options) + break + case 2: + core.error(msg.message, options) + default: + break + } + }) } - get relevantMessages() { - return this.messages.filter((m) => + private findRelevantMessages() { + this.relevantMessages = this.messages.filter((m) => this.changeRanges?.some((changeRange) => changeRange.doesInclude(m.line)), ) } + + private calculateCounts() { + this.relevantMessages.forEach((msg) => { + switch (msg.severity) { + case 1: + this.relevantWarningCount += 1 + break + case 2: + this.relevantErrorCount += 1 + default: + break + } + }) + } + + private get messages() { + return this.resultObject.messages + } + + private get repoFilePath() { + let absoluteFolderPath = process.env.GITHUB_WORKSPACE + if (!absoluteFolderPath) + throw new Error("process.env.GITHUB_WORKSPACE was empty") + return this.resultObject.filePath.replace(absoluteFolderPath, "") + } }