Skip to content

fix(kotlin): parse ktlint --reporter=json output correctly - #680

Open
violinyanev wants to merge 1 commit into
peteromallet:mainfrom
violinyanev:fix/ktlint-json-parser-mismatch
Open

fix(kotlin): parse ktlint --reporter=json output correctly#680
violinyanev wants to merge 1 commit into
peteromallet:mainfrom
violinyanev:fix/ktlint-json-parser-mismatch

Conversation

@violinyanev

Copy link
Copy Markdown

Problem

The kotlin plugin declares its ktlint tool with fmt=\"json\", which maps to
the generic parse_json parser. That parser expects a flat list of
objects with top-level file/line/message keys (the shape most linters
use), but ktlint --reporter=json nests violations per file:

[
  {
    "file": "src/main/Foo.kt",
    "errors": [
      {"line": 10, "column": 5, "message": "Newline expected", "rule": "standard:class-signature"}
    ]
  }
]

Since parse_json looks for a top-level "line"/"message" key that
doesn't exist on these file objects, every entry is silently dropped. The
result: ktlint_violation always returns zero entries even when ktlint
exits non-zero with real violations, and the scan reports a permanent

Coverage reduced (ktlint_violation): ktlint tooling unavailable (tool_failed_unparsed_output)

warning — so Kotlin projects never actually get ktlint findings out of
desloppify, regardless of whether ktlint is installed and working.

Repro (any Kotlin project with ktlint on PATH and at least one lint
violation):

$ desloppify scan --path .
...
Coverage reduced (ktlint_violation): ktlint tooling unavailable (tool_failed_unparsed_output)

Fix

Add a dedicated parse_ktlint parser that understands ktlint's nested
file/errors shape (one entry per error, with the rule id appended to the
message), register it in PARSERS, and switch the kotlin plugin's tool spec
to fmt=\"ktlint\" instead of the generic \"json\".

Verified against a real multi-module Kotlin (Android) project:

  • Before: ktlint_violation coverage reduced, mechanical "Code quality"
    score understated (e.g. 60.7%).
  • After: no coverage warning, real ktlint findings show up in desloppify next/show, and "Code quality" score reflects them (e.g. 90.9%).

Added unit tests (TestParseKtlint) covering rule-suffix formatting,
multiple files/errors, files with no errors, empty output, and invalid
JSON. Full test suite passes locally (5200 passed, 1 skipped).

The kotlin plugin declared the ktlint tool with fmt="json", which maps
to the generic parse_json parser. That parser expects a flat list of
objects with top-level file/line/message keys (like most linters), but
ktlint's --reporter=json output nests violations per file:

  [{"file": ..., "errors": [{"line", "column", "message", "rule"}]}]

Because parse_json looked for top-level "line"/"message" keys that
don't exist on ktlint's file objects, every entry was silently dropped,
so ktlint_violation always produced zero entries despite ktlint exiting
non-zero with real violations. This surfaced as a permanent
'Coverage reduced (ktlint_violation): ... tool_failed_unparsed_output'
warning and meant Kotlin projects never got real ktlint findings from
desloppify.

Add a dedicated parse_ktlint parser that understands the nested
file/errors shape, register it in PARSERS, and switch the kotlin plugin
to fmt="ktlint". Verified against a real multi-module Kotlin project:
ktlint_violation coverage is no longer reduced and the mechanical
'Code quality' score reflects real lint findings.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant