From f841eb43c5b3e8e8739ebbede9fd05733644b099 Mon Sep 17 00:00:00 2001 From: yogur Date: Mon, 4 Aug 2025 19:46:28 +0200 Subject: [PATCH 1/4] Implement diff-aware PR reporting --- README.md | 1 + action.yml | 5 ++ src/filter.js | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.js | 10 +++- 4 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 src/filter.js diff --git a/README.md b/README.md index b0f0dff..96e2c55 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,7 @@ Ensure that you're using the { + const changedLines = []; + + // Skip files without patches (like binary files or deleted files) + if (!file.patch) { + changedFiles[file.filename] = changedLines; + return; + } + + // Parse each hunk in the patch to find added/modified lines + const hunkRegex = /@@\s+-\d+(?:,\d+)?\s+\+(\d+)(?:,(\d+))?\s+@@/g; + let match; + + while ((match = hunkRegex.exec(file.patch)) !== null) { + const start = parseInt(match[1]); + const count = match[2] ? parseInt(match[2]) : 1; + + // Only include lines that were added/modified (count > 0) + if (count > 0) { + for (let i = start; i < start + count; i++) { + changedLines.push(i); + } + } + } + + // Remove duplicates and sort + changedFiles[file.filename] = [...new Set(changedLines)].sort( + (a, b) => a - b + ); + }); + + return changedFiles; +} + +function filterResultsByChangedFiles(results, changedFiles) { + console.log("Filtering results for diff-aware reporting..."); + + const filteredResults = JSON.parse(JSON.stringify(results)); // Deep clone + const filteredQueries = []; + + // Filter findings based on changed files and lines + for (const query of results.queries) { + const filteredFiles = []; + + for (const file of query.files) { + const fileName = file.file_name; + const fileLine = file.line; + + // Check if this file was changed in the PR + if (changedFiles.hasOwnProperty(fileName)) { + const changedLines = changedFiles[fileName]; + + // If no specific lines changed (e.g., new file), include all findings + // Otherwise, only include findings on changed lines + if (changedLines.length === 0 || changedLines.includes(fileLine)) { + filteredFiles.push(file); + } + } + } + + // If this query has findings in changed files/lines, include it + if (filteredFiles.length > 0) { + const filteredQuery = { ...query, files: filteredFiles }; + filteredQueries.push(filteredQuery); + } + } + + // Update the filtered results + filteredResults.queries = filteredQueries; + + // Recalculate counters based on filtered findings + const newSeverityCounters = {}; + let totalCounter = 0; + + // Initialize severity counters + for (const severity of [ + "CRITICAL", + "HIGH", + "MEDIUM", + "LOW", + "INFO", + "TRACE", + ]) { + newSeverityCounters[severity] = 0; + } + + // Count findings by severity + filteredQueries.forEach((query) => { + const severity = query.severity.toUpperCase(); + const findingCount = query.files.length; + if (newSeverityCounters.hasOwnProperty(severity)) { + newSeverityCounters[severity] += findingCount; + } + totalCounter += findingCount; + }); + + filteredResults.severity_counters = newSeverityCounters; + filteredResults.total_counter = totalCounter; + + console.log( + `Filtered results: ${totalCounter} findings in changed files (originally ${results.total_counter})` + ); + + return filteredResults; +} + +async function applyDiffAwareFiltering(parsedResults, octokit, repo, prNumber) { + console.log("Diff-aware reporting enabled for PR #" + prNumber); + const prFiles = await fetchPRFiles(octokit, repo, prNumber); + if (prFiles.length > 0) { + const changedFiles = parseChangedLines(prFiles); + return filterResultsByChangedFiles(parsedResults, changedFiles); + } else { + console.log( + "No PR files found or error fetching files, using original results" + ); + return parsedResults; + } +} + +module.exports = { + applyDiffAwareFiltering, +}; diff --git a/src/main.js b/src/main.js index ef5fb35..cd1f085 100644 --- a/src/main.js +++ b/src/main.js @@ -1,5 +1,6 @@ const commenter = require("./commenter"); const annotator = require("./annotator"); +const filter = require("./filter"); const core = require("@actions/core"); const github = require("@actions/github"); const io = require("@actions/io"); @@ -50,6 +51,7 @@ async function main() { let enableAnnotations = process.env.INPUT_ENABLE_ANNOTATIONS; let enableComments = process.env.INPUT_ENABLE_COMMENTS; let enableJobsSummary = process.env.INPUT_ENABLE_JOBS_SUMMARY; + let enableDiffAwareReporting = process.env.INPUT_ENABLE_DIFF_AWARE_REPORTING; const commentsWithQueries = process.env.INPUT_COMMENTS_WITH_QUERIES; const excludedColumnsForCommentsWithQueries = process.env.INPUT_EXCLUDED_COLUMNS_FOR_COMMENTS_WITH_QUERIES.split(','); const outputPath = processOutputPath(process.env.INPUT_OUTPUT_PATH); @@ -75,8 +77,14 @@ async function main() { enableAnnotations = enableAnnotations ? enableAnnotations : "false" enableComments = enableComments ? enableComments : "false" enableJobsSummary = enableJobsSummary ? enableJobsSummary : "false" + enableDiffAwareReporting = enableDiffAwareReporting ? enableDiffAwareReporting : "false" - const parsedResults = readJSON(outputPath.resultsJSONFile); + let parsedResults = readJSON(outputPath.resultsJSONFile); + + if (enableDiffAwareReporting.toLocaleLowerCase() === "true" && prNumber) { + parsedResults = await filter.applyDiffAwareFiltering(parsedResults, octokit, repo, prNumber); + } + if (enableAnnotations.toLocaleLowerCase() === "true") { annotator.annotateChangesWithResults(parsedResults); } From 77967ea523260df3b9d3d1287b4e1df0b897f147 Mon Sep 17 00:00:00 2001 From: yogur Date: Tue, 5 Aug 2025 13:08:01 +0200 Subject: [PATCH 2/4] Set exit code to success if there are no findings in changed code --- src/main.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main.js b/src/main.js index cd1f085..81b1418 100644 --- a/src/main.js +++ b/src/main.js @@ -80,9 +80,16 @@ async function main() { enableDiffAwareReporting = enableDiffAwareReporting ? enableDiffAwareReporting : "false" let parsedResults = readJSON(outputPath.resultsJSONFile); + let finalExitCode = exitCode; if (enableDiffAwareReporting.toLocaleLowerCase() === "true" && prNumber) { parsedResults = await filter.applyDiffAwareFiltering(parsedResults, octokit, repo, prNumber); + + // If diff-aware filtering resulted in zero findings, override exit code to success + if (parsedResults.total_counter === 0) { + console.log("Diff-aware filtering resulted in zero findings. Setting workflow status to success."); + finalExitCode = "0"; + } } if (enableAnnotations.toLocaleLowerCase() === "true") { @@ -95,7 +102,7 @@ async function main() { await commenter.postJobSummary(parsedResults, commentsWithQueries.toLocaleLowerCase() === "true", excludedColumnsForCommentsWithQueries); } - setWorkflowStatus(exitCode); + setWorkflowStatus(finalExitCode); cleanupOutput(outputPath.resultsJSONFile, outputFormats); } catch (e) { console.error(e); From c4ee8f4650cd2526651d067ffc4339e833a2dcb8 Mon Sep 17 00:00:00 2001 From: yogur Date: Wed, 6 Aug 2025 14:00:35 +0200 Subject: [PATCH 3/4] Skip comment on no findings and diff-aware reporting enabled to reduce noise --- src/main.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main.js b/src/main.js index 81b1418..9be7842 100644 --- a/src/main.js +++ b/src/main.js @@ -96,7 +96,12 @@ async function main() { annotator.annotateChangesWithResults(parsedResults); } if (enableComments.toLocaleLowerCase() === "true") { - await commenter.postPRComment(parsedResults, repo, prNumber, octokit, commentsWithQueries.toLocaleLowerCase() === "true", excludedColumnsForCommentsWithQueries); + // Skip posting comment if diff-aware reporting is enabled and no findings in changed files + if (enableDiffAwareReporting.toLocaleLowerCase() === "true" && parsedResults.total_counter === 0) { + console.log("Diff-aware reporting: No findings in changed files, skipping PR comment"); + } else { + await commenter.postPRComment(parsedResults, repo, prNumber, octokit, commentsWithQueries.toLocaleLowerCase() === "true", excludedColumnsForCommentsWithQueries); + } } if (enableJobsSummary.toLocaleLowerCase() === "true") { await commenter.postJobSummary(parsedResults, commentsWithQueries.toLocaleLowerCase() === "true", excludedColumnsForCommentsWithQueries); From 9331c04a630a2b2d77a4d66f720f2327bff88de6 Mon Sep 17 00:00:00 2001 From: yogur Date: Thu, 21 Aug 2025 09:10:53 +0200 Subject: [PATCH 4/4] Rollback change: Skip comment on no findings and diff-aware reporting enabled --- src/main.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main.js b/src/main.js index 9be7842..81b1418 100644 --- a/src/main.js +++ b/src/main.js @@ -96,12 +96,7 @@ async function main() { annotator.annotateChangesWithResults(parsedResults); } if (enableComments.toLocaleLowerCase() === "true") { - // Skip posting comment if diff-aware reporting is enabled and no findings in changed files - if (enableDiffAwareReporting.toLocaleLowerCase() === "true" && parsedResults.total_counter === 0) { - console.log("Diff-aware reporting: No findings in changed files, skipping PR comment"); - } else { - await commenter.postPRComment(parsedResults, repo, prNumber, octokit, commentsWithQueries.toLocaleLowerCase() === "true", excludedColumnsForCommentsWithQueries); - } + await commenter.postPRComment(parsedResults, repo, prNumber, octokit, commentsWithQueries.toLocaleLowerCase() === "true", excludedColumnsForCommentsWithQueries); } if (enableJobsSummary.toLocaleLowerCase() === "true") { await commenter.postJobSummary(parsedResults, commentsWithQueries.toLocaleLowerCase() === "true", excludedColumnsForCommentsWithQueries);