@@ -36,14 +36,109 @@ jobs:
3636 echo "Changed files: $FILES"
3737
3838 if [ -n "$FILES" ]; then
39- echo "$FILES" | xargs ./tools/linter/zig-out/bin/linter > lint_results .json
39+ echo "$FILES" | xargs ./tools/linter/zig-out/bin/linter > lint_results_raw .json
4040 else
41- echo "[]" > lint_results .json
41+ echo "[]" > lint_results_raw .json
4242 fi
4343
4444 # Debug output
45- echo "Lint results:"
46- cat lint_results.json
45+ echo "Raw lint results:"
46+ cat lint_results_raw.json
47+
48+ - name : Filter lint results to changed lines
49+ uses : actions/github-script@v7
50+ with :
51+ script : |
52+ const fs = require('fs');
53+ const { execSync } = require('child_process');
54+
55+ if (!fs.existsSync('lint_results_raw.json')) {
56+ fs.writeFileSync('lint_results.json', '[]');
57+ console.log('No raw lint results found');
58+ return;
59+ }
60+
61+ const rawIssues = JSON.parse(fs.readFileSync('lint_results_raw.json', 'utf8'));
62+
63+ if (rawIssues.length === 0) {
64+ fs.writeFileSync('lint_results.json', '[]');
65+ console.log('No lint issues found');
66+ return;
67+ }
68+
69+ console.log(`Found ${rawIssues.length} total lint issue(s)`);
70+
71+ // Get diff with line numbers
72+ const baseSha = '${{ github.event.pull_request.base.sha }}';
73+ const headSha = '${{ github.event.pull_request.head.sha }}';
74+
75+ // Parse diff to get changed line numbers per file
76+ const changedLines = {};
77+
78+ for (const issue of rawIssues) {
79+ const file = issue.file;
80+
81+ if (!changedLines[file]) {
82+ try {
83+ // Get the diff for this specific file
84+ const diff = execSync(
85+ `git diff --unified=0 ${baseSha} ${headSha} -- "${file}"`,
86+ { encoding: 'utf8' }
87+ );
88+
89+ // Parse the diff to extract changed line numbers
90+ const lines = new Set();
91+ const diffLines = diff.split('\n');
92+
93+ for (const line of diffLines) {
94+ // Match the @@ -start,count +start,count @@ format
95+ const match = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/);
96+ if (match) {
97+ const startLine = parseInt(match[1]);
98+ const count = match[2] ? parseInt(match[2]) : 1;
99+
100+ // Add all changed lines in this hunk
101+ for (let i = 0; i < count; i++) {
102+ lines.add(startLine + i);
103+ }
104+ }
105+ }
106+
107+ changedLines[file] = lines;
108+ console.log(`File ${file}: ${lines.size} changed line(s)`);
109+ } catch (error) {
110+ console.log(`Could not get diff for ${file}: ${error.message}`);
111+ changedLines[file] = new Set();
112+ }
113+ }
114+ }
115+
116+ // Filter issues to only those on changed lines
117+ const filteredIssues = rawIssues.filter(issue => {
118+ const fileLines = changedLines[issue.file] || new Set();
119+ const isOnChangedLine = fileLines.has(issue.line);
120+ if (!isOnChangedLine) {
121+ console.log(`Filtered out: ${issue.file}:${issue.line} (not on changed line)`);
122+ }
123+ return isOnChangedLine;
124+ });
125+
126+ // Save filtered issues
127+ fs.writeFileSync('lint_results.json', JSON.stringify(filteredIssues, null, 2));
128+
129+ // Create summary of filtered issues
130+ const filtered = rawIssues.length - filteredIssues.length;
131+ if (filtered > 0) {
132+ console.log(`Filtered out ${filtered} issue(s) on unchanged lines`);
133+
134+ const filteredOut = rawIssues.filter(issue => !filteredIssues.includes(issue));
135+ const summary = filteredOut.map(i => `${i.file}:${i.line}: ${i.message}`).join('\n');
136+ fs.writeFileSync('filtered_issues.txt',
137+ `The following ${filtered} issue(s) exist but are not on lines changed in this PR:\n\n${summary}`
138+ );
139+ }
140+
141+ console.log(`Kept ${filteredIssues.length} issue(s) on changed lines for inline comments`);
47142
48143 - name : Post review with grouped comments
49144 uses : actions/github-script@v7
@@ -150,7 +245,11 @@ jobs:
150245 review.body && review.body.includes('<!-- lint-review -->')
151246 );
152247
153- if (!content || content === '[]') {
248+ // Check if there are filtered issues
249+ const hasFilteredIssues = fs.existsSync('filtered_issues.txt');
250+ const filteredContent = hasFilteredIssues ? fs.readFileSync('filtered_issues.txt', 'utf8') : '';
251+
252+ if ((!content || content === '[]') && !hasFilteredIssues) {
154253 console.log('No lint issues found');
155254
156255 // Dismiss all existing bot reviews and resolve their conversations
@@ -171,11 +270,11 @@ jobs:
171270 return;
172271 }
173272
174- const issues = JSON.parse(content);
273+ const issues = content && content !== '[]' ? JSON.parse(content) : [] ;
175274
176275 // Create hash of current issues to check if review needs updating
177276 const issuesHash = crypto.createHash('md5')
178- .update(JSON.stringify(issues.map(i => `${i.file}:${i.line}:${i.message}`)))
277+ .update(JSON.stringify(issues.map(i => `${i.file}:${i.line}:${i.message}`)) + filteredContent )
179278 .digest('hex')
180279 .substring(0, 8);
181280
@@ -204,7 +303,7 @@ jobs:
204303 }
205304 }
206305
207- // Prepare review comments
306+ // Prepare review comments (only for issues on changed lines)
208307 const reviewComments = [];
209308 const issuesByFile = {};
210309
@@ -226,26 +325,37 @@ jobs:
226325 const fileCount = Object.keys(issuesByFile).length;
227326
228327 let reviewBody = `## 🔍 Lint Results\n\n`;
229- reviewBody += `Found **${totalIssues}** issue${totalIssues !== 1 ? 's' : ''} in **${fileCount}** file${fileCount !== 1 ? 's' : ''}:\n\n`;
230328
231- for (const [file, fileIssues] of Object.entries(issuesByFile)) {
232- reviewBody += `- **${file}**: ${fileIssues.length} issue${fileIssues.length !== 1 ? 's' : ''}\n`;
329+ if (totalIssues > 0) {
330+ reviewBody += `Found **${totalIssues}** issue${totalIssues !== 1 ? 's' : ''} on changed lines in **${fileCount}** file${fileCount !== 1 ? 's' : ''}:\n\n`;
331+
332+ for (const [file, fileIssues] of Object.entries(issuesByFile)) {
333+ reviewBody += `- **${file}**: ${fileIssues.length} issue${fileIssues.length !== 1 ? 's' : ''}\n`;
334+ }
335+ }
336+
337+ // Add filtered issues summary if exists
338+ if (hasFilteredIssues) {
339+ reviewBody += `\n<details>\n<summary>ℹ️ Additional issues on unchanged lines</summary>\n\n\`\`\`\n${filteredContent}\n\`\`\`\n\n</details>\n`;
233340 }
234341
235342 reviewBody += `\n<!-- lint-review -->\n<!-- lint-hash:${issuesHash} -->`;
236343
344+ // Determine review event type
345+ const reviewEvent = totalIssues > 0 ? 'REQUEST_CHANGES' : 'COMMENT';
346+
237347 try {
238348 const review = await github.rest.pulls.createReview({
239349 owner: context.repo.owner,
240350 repo: context.repo.repo,
241351 pull_number: context.issue.number,
242352 commit_id: context.payload.pull_request.head.sha,
243353 body: reviewBody,
244- event: 'REQUEST_CHANGES' ,
354+ event: reviewEvent ,
245355 comments: reviewComments
246356 });
247357
248- console.log(`Created review with ${reviewComments.length} comments `);
358+ console.log(`Created review with ${reviewComments.length} inline comment(s) `);
249359 } catch (error) {
250360 console.error(`Failed to create review:`, error.message);
251361
@@ -257,7 +367,7 @@ jobs:
257367 pull_number: context.issue.number,
258368 commit_id: context.payload.pull_request.head.sha,
259369 body: reviewBody + '\n\n⚠️ Could not attach inline comments due to an error.',
260- event: 'REQUEST_CHANGES'
370+ event: reviewEvent
261371 });
262372 console.log('Created review without inline comments as fallback');
263373 } catch (fallbackError) {
0 commit comments