feat(protocol): expose analyzer quick fixes in scan output#9
Merged
Conversation
Engine.Issue has carried quickFixes() since sonarlint-core 11.x and the
sonar-plugin-api has shipped the NewIssue.addQuickFix(...) builder for
several major versions. Until now the daemon silently dropped them at
the protocol-DTO boundary, leaving downstream consumers (the agent
pipeline and the JSON CLI users) to guess at remediations.
This change carries quick fixes end-to-end:
Protocol DTOs (new)
- TextEdit(startLine, startColumn, endLine, endColumn, replacement) —
a single in-file replacement edit
- FileEdit(filePath, edits) — all TextEdits a quick fix applies to one
file; null-normalised and defensively copied
- QuickFix(message, fileEdits) — an analyzer-supplied remediation,
possibly spanning multiple files
Issue DTO
- Added List<QuickFix> quickFixes field (compact ctor null-normalises
and copies). A backwards-compatible 9-arg constructor delegates with
List.of(), so the ~15 existing test call sites keep compiling.
IssueMapper
- mapQuickFixes(List<engine.QuickFix>, baseDir) translates the engine's
QuickFix / ClientInputFileEdit / TextEdit tree into our DTOs,
resolving each edit's target file path through the same
resolveFilePath() the primary issue uses (so quick-fix paths share
the baseDir-relative, '/'-separated convention).
- The original 5-arg map(...) overload is preserved and delegates to a
new 6-arg overload that takes the quickFixes list.
Test coverage (356/356 pass, +20 new):
- Per-DTO Jackson roundtrip + null-normalisation + defensive-copy
contracts (TextEditTest x3, FileEditTest x5, QuickFixTest x5,
IssueTest +3).
- IssueMapperTest +3: single-edit roundtrip, empty list, multi-file.
- AnalysisServiceTest +1: the existing UtilityClass.java fixture
raises java:S1118; assert the resulting protocol Issue carries at
least one QuickFix with one FileEdit + TextEdit, and that the
quick-fix file path matches the issue's file path. This is the
canary that proves the full daemon -> JSON pipeline emits real
analyzer-supplied edits, not just the schema.
JSON shape is additive: existing consumers that don't read .quickFixes
remain unaffected; new consumers gain a per-issue remediation list.
Targeting 0.3.0-SNAPSHOT (0.2.0 was tagged at HEAD of main).
The previous commit added quickFixes to the protocol DTO. JsonReporter
builds the per-issue ObjectNode by hand and was never updated, so the
new field rode in the daemon -> CLI pipeline but never reached the
user-facing JSON.
This adds a quickFixNode() builder and emits a "quickFixes" array on
issues that carry at least one analyzer-supplied fix. Issues with no
quick fix omit the field entirely (rather than emitting "quickFixes":[])
to keep the wire format token-lean for the common case.
Schema:
"quickFixes": [{
"message": "Replace with isEmpty()",
"fileEdits": [{
"filePath": "src/main/java/.../SonarCommand.java",
"edits": [
{ "startLine":254, "startColumn":20, "endLine":254, "endColumn":20,
"replacement":"!" },
...
]
}]
}]
Field order in each node matches the DTO so emitted JSON roundtrips
cleanly through Json.mapper().readValue(QuickFix.class).
Test coverage (+2 in ReportersTest, 358/358 total):
- jsonOmitsEmptyQuickFixes: existing WITH_ISSUES fixture has no
quick fixes; the field must be absent from the rendered JSON.
- jsonEmitsQuickFixes: a synthetic Issue carrying a real QuickFix
serializes through JsonReporter; verifies message, fileEdits[0]
.filePath, and edits[0].{startLine,startColumn,endLine,endColumn,
replacement} all roundtrip with the right values.
Self-scan canary (ran sonar against this worktree): 3 of 29 issues
now carry quick fixes (2x java:S7158 "isEmpty()" replacement in
SonarCommand.java, 1x java:S1450 field-to-local in DaemonServer.java).
Real machine-applicable edits, not guesses.
SARIF reporter is intentionally unchanged in this commit — SARIF has
its own 'fixes' schema that warrants a separate, dedicated mapping.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
Issue.quickFixes()since sonarlint-core 11.x; until now the daemon dropped them at the protocol-DTO boundary. This change carries quick fixes end-to-end so the agent pipeline and JSON CLI users get actionable remediation edits instead of guessing.TextEdit,FileEdit,QuickFix(records, Jackson-roundtrip-safe, null-normalising compact constructors, defensively-copied lists).IssueDTO gains aList<QuickFix> quickFixesfield with a backwards-compatible 9-arg constructor that delegates withList.of()so the ~15 existing construction sites keep compiling.IssueMapper.mapQuickFixes(List<engine.QuickFix>, baseDir)translates the engine's QuickFix / ClientInputFileEdit / TextEdit tree, resolving each edit's target path through the sameresolveFilePaththe primary issue uses.Design
.quickFixesare unaffected; new consumers gain a per-issue remediation list.Issue.filePath. Quick-fix file paths are baseDir-relative, '/'-separated — so a UI/agent can render edits the same way it renders the primary issue position.Issue.startLine/startColumn. 1-indexed lines, 0-indexed columns, matching what LSPDiagnosticconsumers already expect.Test plan (all verified locally)
*Testclasses, 13 tests)IssueDTO: 9-arg ctor defaultsquickFixesto empty; null is normalised; roundtrip preserves a fully-populated quick-fix tree (+3 tests)IssueMappermapping logic: single-edit roundtrip, empty list, multi-file (+3 tests)AnalysisServiceTest: the existingUtilityClass.javafixture raisesjava:S1118; assert the resulting protocolIssuecarries at least oneQuickFixwith at least oneFileEdit+TextEdit, and that the quick-fix file path matches the issue's file path. This is the canary that proves the real sonar-java 8.29 analyzer emits a quick fix that reaches our JSON output — not just schema correctness.Version
pom.xmlbumped 0.2.0-SNAPSHOT → 0.3.0-SNAPSHOT (next dev cycle after the 0.2.0 release we tagged onmain).Sample JSON shape after this change
{ "ruleKey": "java:S1118", "filePath": "com/example/UtilityClass.java", "startLine": 3, "startColumn": 13, "endLine": 3, "endColumn": 26, "severity": "MAJOR", "type": "CODE_SMELL", "message": "Add a private constructor to hide the implicit public one.", "quickFixes": [{ "message": "Add a private constructor", "fileEdits": [{ "filePath": "com/example/UtilityClass.java", "edits": [{ "startLine": 3, "startColumn": 0, "endLine": 3, "endColumn": 0, "replacement": " private UtilityClass() {}\n" }] }] }] }