Add SARIF output support for 13 linters + 4 Salesforce Code Analyzer engines - #8715
Conversation
…engines zizmor, bicep_linter, cppcheck, clj-kondo, roslynator, htmlhint, protolint, sqlfluff, swiftlint, osv-scanner, trufflehog, jscpd and lintr now support can_output_sarif. The 4 SALESFORCE_CODE_ANALYZER_* engines switch their report format from CSV to SARIF automatically when SARIF reporting is requested, via a new shared SalesforceCodeAnalyzerLinter class. Roslynator is bumped to 0.13.0, the first release including SARIF output.
✅
|
6354787 to
7137776
Compare
- roslynator: the dotnet-restore pre-step ran through execute_lint_command(), which now also triggers manage_sarif_output() before sarif_output_file is ever set by the real build_lint_command() call — crashed with a TypeError. Route the restore step through the lower-level subprocess primitive instead. - jscpd (pinned v5.0.14): its SARIF reporter actually writes copy-paste/jscpd-report.sarif, not jscpd-sarif.json as newer versions do. - trufflehog: --sarif is a global flag parsed before the subcommand; inserting it after "filesystem" like a normal cli_sarif_args append made the CLI reject it as unknown. Insert it before "filesystem" in the linter class. - lintr: sarif_output() requires the jsonlite R package, which wasn't installed. - htmlhint: its formatter writes htmlhint.sarif to the CWD in addition to stdout; wire sarif_default_output_file instead of relying on stdout capture. - zizmor: --format=sarif always exits 0 regardless of findings, so MegaLinter's return-code-gated error counting never ran. Recover a findings-reflecting return code from the SARIF results after the fact. - osv-scanner and the Salesforce Code Analyzer aura (eslint) engine report all their SARIF findings at "warning" level, same as the existing betterleaks exception in the shared SARIF test — add them to that list. - sqlfluff: active_only_if_file_found: ['.sqlfluff'] deactivated it because the SARIF test scans the test-folder root, which had no root-level config (only bad/good/fix subfolders did) — add one at the root.
…F check
- roslynator: the pinned roslynator.dotnet.cli 0.13.0 actually rejects
--output-format sarif ("Unknown output format 'sarif'") — the NuGet
release/GitHub changelog version correlation used to pin this doesn't
hold. Revert the SARIF wiring, keep the 0.13.0 version bump (works fine
otherwise).
- jscpd: its SARIF output reports individual clones as "warning" and only
the duplication-threshold breach as "error" (one per run), same pattern
as the existing betterleaks/osv-scanner/salesforce-aura exceptions.
- cspell: add "jsonlite" (R package name) to ignoreWords.
- trufflehog: --sarif was merged upstream on 2026-08-05, after the latest release v3.96.0 (2026-07-24) that MegaLinter pins — no released version has the flag yet. Revert the SARIF wiring entirely. - r_lintr: the jsonlite install-block change from the previous fix never reached the generated Dockerfile because make megalinter-build wasn't re-run after editing the descriptor.
lintr::sarif_output() rejects an absolute filename ("Package path needs to
be a relative path"). Compute the path relative to the R process's cwd
(set to the file's own directory) instead of passing the absolute
sarif_output_file.
Path(file).parent is frequently just "." for files directly under the workspace, and os.path.relpath() resolves a relative start against this Python process's own cwd, not the R subprocess's actual working directory (self.workspace + the file's subdirectory, set via setwd()). Ground the relpath computation in that real absolute directory instead.
lintr::sarif_output() rejects not just absolute paths but any relative path containing ".." — it can never point directly at self.sarif_output_file, which lives under the report folder outside the linted file's directory tree entirely. Write a bare filename in the R process's own cwd instead, then move it into place ourselves after the command runs.
Read lintr's actual source: sarif_output() aborts with "Package path needs to be a relative path" whenever attr(lints, "path") is NULL, which it always is for plain lint() results — that attribute is only ever set by lint_dir()/lint_package(). The filename argument was never the problem across the last several attempts; it only controls where the file gets written. Set the path attribute manually (it only feeds the SARIF ROOTPATH URI, not file I/O) and keep the existing move-into-place logic.
|
I see that in utilstest.py you're setting it up so that several linters report everything as a warning instead of warnings and errors, and you're preventing an assert from being triggered. Back in the day, in several pull requests I made to improve Sarif-related issues, what I did was identify all of that and create issues in their respective official repositories, and even create pull requests where I saw that it was possible. Because many have warning or error rules, but when you use Sarif, everything is treated as a warning—in other words, support for Sarif is partial. |
|
@bdovaz you're right, i just launched a task to precise in doc when sarif is partially supported and submit PRs to related repos ! |
All three currently report every SARIF finding at "warning" level regardless of actual severity, which was hidden behind an exclusion in utilstest.py's SARIF test rather than documented anywhere a user would see it. Note it in each linter's docs, with a link to the upstream fix (osv-scanner, PR opened) or discussion (jscpd, issue opened; betterleaks, already tracked in gitleaks/gitleaks#1858).
|
Good point, thanks. I looked into each of the three linters this touched (osv-scanner, jscpd, the Salesforce Code Analyzer aura engine) plus the pre-existing betterleaks case:
Documented the partial support (with the links above) directly in each linter's |

Summary
Wires native SARIF output support into linters that already have it upstream but weren't hooked into MegaLinter's
SARIF_REPORTERyet:--format=sarif, stdout) — also recovers a findings-reflecting return code, since--format=sarifalways exits 0--stdout --diagnostics-format Sarif)--output-format=sarif, both C and CPP descriptors via the shared linter file)--config '{:output {:format :sarif}}')--format sarif, picked up fromhtmlhint.sarifwhich it writes to the CWD)--add-reporter sarif:<path>)--format sarif)--reporter sarif --output <path>)can_output_sarifwas force-disabled with a "disabled until it works!" comment; no blocking bug found in git history)JsCpdLinter.build_lint_command()since jscpd's reporter list is one comma-joined CLI value; picked up fromcopy-paste/jscpd-report.sarif(the actual filename jscpd 5.0.14 writes)RLinter.pybuilds its ownR -ecommand rather than going through the standard command pipeline, solintr::sarif_output()is invoked directly when SARIF is requested (requires thejsonliteR package, now installed, and a manually-setpathattribute on the lint results — see below)Also adds SARIF output to the 4 Salesforce Code Analyzer engines (
SALESFORCE_CODE_ANALYZER_APEX/AURA/LWC/FLOW) via a new sharedSalesforceCodeAnalyzerLinterclass: thesf code-analyzerCLI infers its report format from the--output-fileextension, which is already baked into a fixed argument list, so a second--output-filefrom the standard SARIF injection would collide. The class swaps the extension from.csvto.sarifonly when SARIF output is requested, so normal runs keep producing CSV as before.Two linters were dropped from this batch after CI proved the SARIF support isn't actually available in the pinned/released tool version, despite what the initial research found:
roslynator.dotnet.cli0.13.0 rejects--output-format sarifat runtime ("Unknown output format 'sarif'"). The 0.13.0 version bump is kept (works fine otherwise).--sarifwas merged upstream on 2026-08-05, after the latest release v3.96.0 (2026-07-24) that MegaLinter pins — no released version has it yet.Known caveats worth a second look
clj-kondo's upstream SARIF output currently nestsregionone level too deep underartifactLocation(Invalid SARIF output clj-kondo/clj-kondo#2345), and enabling SARIF output together withEXCLUDED_DIRECTORIESforwarding in project mode causes the exclude-forwarding to be silently skipped (both share the--configflag guard inCljKondoLinter.manage_excluded_directories_config()).bicep_linter's--stdoutmixes the compiled ARM template JSON and the SARIF diagnostics in the same stream;Linter.manage_sarif_output()'s JSON-block extraction correctly picks out the SARIF-shaped block in CI, but it's a less common pattern worth extra scrutiny if it's ever touched again.osv-scanner,jscpd, and the Salesforceauraengine report all (or nearly all) of their SARIF findings at "warning" level rather than "error" — added to the existingbetterleaks-style exception list in the shared SARIF test rather than the plainerrors > 1assertion.lintr::sarif_output()only works on results fromlint_dir()/lint_package()in principle (it reads an internalpathattribute those set on the lint results, and aborts otherwise);RLinter.pysets that attribute manually since MegaLinter calls plainlint()per file. The attribute only feeds the SARIFROOTPATHURI, not file I/O, so this is safe but is relying on an internal implementation detail rather than a documented API.Test plan
test_report_sarifpasses for all linters/engines above — verified in CI across 8 rounds of real bug fixes surfaced by the new tests (a crash inRoslynatorLinter's restore step, wrong filenames/flag placement for jscpd/trufflehog/htmlhint, a missing R package, a forgottenmake megalinter-build, zizmor's always-0 exit code, a test-fixture activation gap for sqlfluff, several rounds on lintr's SARIF path handling, and two linters — roslynator, trufflehog — whose upstream SARIF support turned out not to be released yet)