diff --git a/scripts/sonar_triage.mjs b/scripts/sonar_triage.mjs index 7bdddd3..46e43c3 100644 --- a/scripts/sonar_triage.mjs +++ b/scripts/sonar_triage.mjs @@ -145,7 +145,9 @@ export function postWebhook(url, payload) { function ghIssues(groups, apply, cap, log) { if (run('gh', ['auth', 'status']).status !== 0) { log('gh unauthenticated: skipping GitHub issues (run `gh auth login`).'); return []; } const list = run('gh', ['issue', 'list', '--state', 'open', '--label', 'sonar', '--limit', '300', '--json', 'number,body']); - const bodies = list.status === 0 ? JSON.parse(list.stdout).map((x) => x.body) : []; + // closed fixed-local issues also suppress (Sonar keys stay open until a re-scan) + const fixed = run('gh', ['issue', 'list', '--state', 'closed', '--label', 'fixed-local', '--limit', '300', '--json', 'number,body']); + const bodies = [list, fixed].flatMap((l) => (l.status === 0 ? JSON.parse(l.stdout).map((x) => x.body) : [])); const todo = groups.filter((g) => !hasMarker(bodies, g.keys)); log(`GitHub: ${groups.length - todo.length} duplicate(s) skipped, ${Math.min(todo.length, cap)} of ${todo.length} to create (cap ${cap}).`); const done = []; @@ -196,6 +198,7 @@ export async function main(argv, deps = {}) { else if (f === '--fix-small') a.fixSmall = true; else if (f === '--list-rules') a.list = true; else if (f === '--json') a.json = argv[++i]; else if (f === '--max-issues') a.max = +argv[++i]; else if (f === '--cap') a.cap = +argv[++i]; + else if (f === '--exclude-rules') a.exclude = (argv[++i] || '').split(',').filter(Boolean); else throw new Error(`unknown flag ${f}`); } const cfg = deps.cfg || loadConfig(); @@ -212,7 +215,7 @@ export async function main(argv, deps = {}) { } if (a.json) writeFileSync(a.json, JSON.stringify(res, null, 2)); if (deps.noActions) return res; - const groups = groupIssues([...res.medium, ...res.large]); + const groups = groupIssues([...res.medium, ...res.large]).filter((g) => !(a.exclude || []).includes(g.rule)); log(`\nMedium+ groups (per rule, max 40 locations): ${groups.length}`); ghIssues(groups, a.apply, a.cap, log); if (a.fixSmall) { if (a.apply) fixSmall(res.small, log); else log('fix-small needs --apply (dry-run).'); } diff --git a/tests/sonar_triage.test.mjs b/tests/sonar_triage.test.mjs index 90f72c4..2319f02 100644 --- a/tests/sonar_triage.test.mjs +++ b/tests/sonar_triage.test.mjs @@ -64,6 +64,13 @@ test('per-run cap limits created issues (dry-run)', async () => { assert.match(txt, /10 of \d+ to create \(cap 10\)|gh unauthenticated/); }); +test('--exclude-rules drops those rule groups', async () => { + const out = []; + const issues = [mk({ key: 'e1', rule: 'python:S3776' }), mk({ key: 'e2', rule: 'python:S3776' }), mk({ key: 'e3', rule: 'python:R9' })]; + await main(['--dry-run', '--all', '--exclude-rules', 'python:S3776'], { token: 't', cfg, log: (s) => out.push(s), fetchFn: async () => page(issues) }); + assert.match(out.join('\n'), /Medium\+ groups \(per rule, max 40 locations\): 1/); +}); + const page = (issues) => ({ ok: true, json: async () => ({ issues, paging: { total: issues.length } }) }); test('fetch sends token as basic username, new-code filter', async () => {