From 37f576bc4eebbd9fc32d9eaa9dcdecf95aaea504 Mon Sep 17 00:00:00 2001 From: Daniil Kalinin Date: Wed, 2 Sep 2026 13:19:11 +0200 Subject: [PATCH] PY-91923 Tolerate a null field in the Ruff rule JSON and fix UOE (cherry picked from commit fd57454567a06b8781a1b8de01c605889dfd667d) IJ-MR-221438 GitOrigin-RevId: 62e1f885aebd1b22dfc7af2f3417a27c54a2cb3d --- .../com/intellij/python/ruff/RuffService.kt | 45 ++++++++++++------- .../ruff/codeinsight/RuffDocumentationUtil.kt | 2 +- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/python/python-ruff/src/com/intellij/python/ruff/RuffService.kt b/python/python-ruff/src/com/intellij/python/ruff/RuffService.kt index af27d6e085b7..af9933fc2973 100644 --- a/python/python-ruff/src/com/intellij/python/ruff/RuffService.kt +++ b/python/python-ruff/src/com/intellij/python/ruff/RuffService.kt @@ -4,10 +4,12 @@ package com.intellij.python.ruff import com.google.gson.JsonArray import com.google.gson.JsonElement import com.google.gson.JsonObject +import com.google.gson.JsonParseException import com.google.gson.JsonParser import com.intellij.openapi.components.Service import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project +import com.intellij.openapi.util.NlsSafe import com.intellij.python.community.execService.Args import com.intellij.python.pytools.executeOn import com.jetbrains.python.NON_INTERACTIVE_ROOT_TRACE_CONTEXT @@ -63,9 +65,9 @@ data class RuffRuleInfo( val summary: String, val fix: String, /** - * formatted in Markdown + * formatted in Markdown. Ruff has no explanation for every rule. */ - val explanation: String, + val explanation: String?, val preview: Boolean, ) @@ -221,22 +223,35 @@ class RuffService(val project: Project, val cs: CoroutineScope) { * @param jsonString The JSON string containing rule information. */ fun loadRuleInformation(@Language("JSON") jsonString: String) { - val jsonArray = JsonParser.parseString(jsonString) as? JsonArray ?: return - ruleInformation = jsonArray.associate { item -> - item as JsonObject - val code = item.get("code").asString - code to RuffRuleInfo( - name = item.get("name").asString, - code = code, - linter = item.get("linter").asString, - summary = item.get("summary").asString, - fix = item.get("fix").asString, - explanation = item.get("explanation").asString, - preview = item.get("preview").asBoolean, - ) + val jsonArray = try { + JsonParser.parseString(jsonString) as? JsonArray ?: return } + catch (e: JsonParseException) { + LOG.warn("Error parsing Ruff rules JSON", e) + return + } + + ruleInformation = jsonArray.mapNotNull { item -> + val rule = item as? JsonObject ?: return@mapNotNull null + // Ruff omits an optional field, or reports it as null. A rule without a code is not addressable. + val code = rule.stringOrNull("code") ?: return@mapNotNull null + val linter: @NlsSafe String = rule.stringOrNull("linter") ?: "" + code to RuffRuleInfo( + name = rule.stringOrNull("name") ?: code, + code = code, + linter = linter, + summary = rule.stringOrNull("summary").orEmpty(), + fix = rule.stringOrNull("fix").orEmpty(), + explanation = rule.stringOrNull("explanation"), + preview = rule.get("preview")?.takeIf { it.isJsonPrimitive }?.asBoolean == true, + ) + }.toMap() + linterInformation = ruleInformation.entries.associate { (key, value) -> key.takeWhile { it.isLetter() } to value.linter } } + + private fun JsonObject.stringOrNull(key: String): @NlsSafe String? = + get(key)?.takeIf { it.isJsonPrimitive }?.asString } \ No newline at end of file diff --git a/python/python-ruff/src/com/intellij/python/ruff/codeinsight/RuffDocumentationUtil.kt b/python/python-ruff/src/com/intellij/python/ruff/codeinsight/RuffDocumentationUtil.kt index edc69b00ba2d..edb6e1cb92a2 100644 --- a/python/python-ruff/src/com/intellij/python/ruff/codeinsight/RuffDocumentationUtil.kt +++ b/python/python-ruff/src/com/intellij/python/ruff/codeinsight/RuffDocumentationUtil.kt @@ -62,7 +62,7 @@ object RuffDocumentationUtil { # ${ruleInfo.name} (${ruleInfo.code}) Derived from the **${ruleInfo.linter}** linter. - """.trimIndent() + ruleInfo.explanation) + """.trimIndent() + (ruleInfo.explanation ?: ruleInfo.summary)) } }