What makes a Problems-API diagnostic 'actionable' for an IDE, and which ProblemSpec facets would you set to achieve that across a plugin?
answer
- stable ProblemId/ProblemGroup — consumers key off ids
- severity + location + solution + documentedAt + details
- centralise ids + helper builders across the plugin
- stable doc URLs; incubating API churn risk
- report vs throwing for non-fatal vs fatal
basics
~20 sAn actionable diagnostic sets a stable id+label, a severity, a precise location (lineInFileLocation), one or more solutions, and a documentedAt link — so an IDE can navigate to it, show the fix, and link the docs instead of parsing console text.
solid answer
~50 s"Actionable" means a consumer (IDE, HTML report, CI dashboard) can do something useful without parsing free text. To get there, design every reported problem to set the full set of `ProblemSpec` facets: a **stable `ProblemId`** (id + human label under a `ProblemGroup`) so problems are categorised and de-duplicated consistently across runs; a **`severity`** so they're triaged/coloured correctly; a **precise location** via `lineInFileLocation(path, line, col, length)` (or `pluginLocation` when there's no source line) so the IDE offers navigation/underlining; one or more **`solution()`** hints so the user knows the fix; and a **`documentedAt`** URL for deeper docs. Keeping ids and doc URLs *stable* matters: consumers key off them, so churn breaks suppression rules and bookmarks. Across a plugin, centralise your `ProblemGroup`/ids and helper builders so all diagnostics share the same quality bar rather than each task hand-rolling `logger.warn`.
code
kotlin · 7 linesfun ProblemReporter.licenseWarning(file: String) =
report(ProblemId.create("missing-license", "Missing license header", validationGroup)) {
it.severity(Severity.WARNING)
.lineInFileLocation(file, 1, 1, 0)
.solution("Run ./gradlew addLicenseHeader")
.documentedAt("https://docs.example.com/license-policy")
}go deeper
List the facets (id, severity, location, solution, docs) at a high level.
Explain how location + solution + documentedAt make a problem navigable and fixable in an IDE.
Stress stable ids and centralised helpers for consistency, and the report/throwing split; note incubating-API churn.
Define an org-wide diagnostics standard: stable id taxonomy, versioned doc URLs, helper API, and a policy that plugins emit structured problems rather than logger.warn.
## What 'actionable' means A diagnostic is actionable when a machine consumer can, without natural-language parsing: (1) **categorise** it, (2) **triage** it by seriousness, (3) **navigate** to the cause, (4) **present a fix**, and (5) **link** authoritative docs. The Problems API exposes one `ProblemSpec` facet for each. ## The facets to set ### Stable identity — ProblemId / ProblemGroup A problem is keyed by a `ProblemId.create(id, label, group)` where `group` is a `ProblemGroup`. The **id** is a stable machine key; the **label** is the short human headline. Stability matters because consumers group, count, and *suppress* by id — renaming ids breaks those rules and any saved baselines. ### Severity `severity(Severity.WARNING/ERROR/ADVICE)` so the IDE colours/sorts and the HTML report buckets correctly. ### Precise location `lineInFileLocation(path, line, column, length)` gives navigation + underlining; `offsetInFileLocation` for offset-based tooling; `fileLocation` when only the file is known; `pluginLocation(pluginId)` when there's no user-source coordinate. Multiple locations may be attached. ### Solutions `solution(String)` (repeatable) — the remediation(s). This is what turns a warning into something the user can fix immediately. ### Documentation `documentedAt(url)` — a stable docs link rendered as 'learn more'. ### Description `details(String)` — the fuller description beyond the label. ```kotlin val group = ProblemGroup.create("com.example.validation", "Validation") val id = ProblemId.create("missing-license", "Missing license header", group) reporter.report(id) { spec -> spec.severity(Severity.WARNING) .details("Source file has no license header") .lineInFileLocation("src/main/kotlin/App.kt", 1, 1, 0) .solution("Run ./gradlew addLicenseHeader") .documentedAt("https://docs.example.com/license-policy") } ``` ## Cross-plugin consistency (the design angle) If every task hand-rolls `logger.warn`, you get inconsistent, unstructured output. A well-designed plugin: - defines a small set of **`ProblemGroup`s and stable ids** in one place, - provides **helper functions** that enforce setting severity + location + solution + documentedAt, - keeps **doc URLs stable** and versioned alongside the plugin, - routes fatal cases through `throwing` and non-fatal through `report`. This makes the plugin's diagnostics navigable in IntelliJ/Buildship, filterable in the HTML problems report, and analysable in CI — a genuine product-quality bar rather than ad-hoc console noise. ## Trade-offs More structure = more authoring effort, and the API is incubating so signatures shift between 8.x releases. The payoff is durable, consumable diagnostics; weigh it against churn risk by centralising the integration so a signature change is a one-place fix.
- Why must ProblemId values be stable across releases?Consumers group, count, and suppress problems by id (baselines, IDE filters). Renaming ids silently breaks those rules and any saved suppressions.
- How do you keep diagnostics consistent across many tasks in one plugin?Centralise ProblemGroup/ids and provide helper builders that enforce setting severity, location, solution and documentedAt, instead of each task calling logger.warn directly.
saying these in an interview costs you the question
- Generating problem ids dynamically (e.g. from messages) so they change run-to-run, breaking consumer keying/suppression.
- Setting only severity + details and skipping location/solution — not actually actionable.
- Claiming the API is fully stable; it is incubating and signatures shift across 8.x.