From fc8747094251b68b2b7a07faaddb3f9b9acbfe9b Mon Sep 17 00:00:00 2001 From: WarningImHack3r <43064022+WarningImHack3r@users.noreply.github.com> Date: Fri, 26 Apr 2024 14:30:15 +0200 Subject: [PATCH] =?UTF-8?q?2.2.1=20-=20Fix=20Semver=20comparison=20failing?= =?UTF-8?q?=20=E2=80=93=20update=20came=20in=20time=20(fixes=20#94)=20-=20?= =?UTF-8?q?Fix=20last=20missing=20UpdateInBackground=20for=20actions=20-?= =?UTF-8?q?=20Improve=20crash=20reporter=20to=20drop=20`at`=20lines=20unti?= =?UTF-8?q?l=20last=20"Caused=20by:"=20if=20any=20-=20Use=20koltinx=20JSON?= =?UTF-8?q?=20parser=20instead=20of=20gson=20-=20Remove=20useless=20traili?= =?UTF-8?q?ng=20slash=20in=20registry=20resolution=20-=20Upgrade=20depende?= =?UTF-8?q?ncies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yml | 2 +- CHANGELOG.md | 9 ++ build.gradle.kts | 8 +- gradle.properties | 2 +- gradle/libs.versions.toml | 5 +- .../backend/engine/NPMJSClient.kt | 27 ++-- .../backend/extensions/Extensions.kt | 22 +++ .../GitHubErrorReportSubmitter.kt | 131 ++++++++++++------ .../update/UpdateAllSatisfiesAction.kt | 3 +- 9 files changed, 149 insertions(+), 60 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3d4577a..3fba5ba 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -168,7 +168,7 @@ jobs: # Run Qodana inspections - name: Qodana - Code Inspection - uses: JetBrains/qodana-action@v2023.3.2 + uses: JetBrains/qodana-action@v2024.1.2 with: cache-default-branch-only: true diff --git a/CHANGELOG.md b/CHANGELOG.md index eae2510..dcb9459 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,15 @@ ## [Unreleased] +### Changed + +- Improve crash reporter to include more relevant information + +### Fixed + +- Fix a library crash due to a version parsing logic problem (#94) +- Fix yet another compatibility issue with newer versions (2024.1+) (#94) + ## [2.2.0] - 2024-04-17 ### Added diff --git a/build.gradle.kts b/build.gradle.kts index ac4d301..413b31e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,6 +7,7 @@ fun environment(key: String) = providers.environmentVariable(key) plugins { id("java") // Java support alias(libs.plugins.kotlin) // Kotlin support + kotlin(libs.plugins.serialization.get().pluginId) version libs.versions.kotlin // Kotlin Serialization support alias(libs.plugins.gradleIntelliJPlugin) // Gradle IntelliJ Plugin alias(libs.plugins.changelog) // Gradle Changelog Plugin alias(libs.plugins.qodana) // Gradle Qodana Plugin @@ -73,7 +74,7 @@ tasks { val start = "" val end = "" - with (it.lines()) { + with(it.lines()) { if (!containsAll(listOf(start, end))) { throw GradleException("Plugin description section not found in README.md:\n$start ... $end") } @@ -116,6 +117,9 @@ tasks { // The pluginVersion is based on the SemVer (https://semver.org) and supports pre-release labels, like 2.1.7-alpha.3 // Specify pre-release label to publish the plugin in a custom Release Channel automatically. Read more: // https://plugins.jetbrains.com/docs/intellij/deployment.html#specifying-a-release-channel - channels = properties("pluginVersion").map { listOf(it.substringAfter('-', "").substringBefore('.').ifEmpty { "default" }) } + channels = properties("pluginVersion").map { + listOf( + it.substringAfter('-', "").substringBefore('.').ifEmpty { "default" }) + } } } diff --git a/gradle.properties b/gradle.properties index 5022b30..983f802 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ pluginGroup = com.github.warningimhack3r.npmupdatedependencies pluginName = npm-update-dependencies pluginRepositoryUrl = https://github.com/WarningImHack3r/npm-update-dependencies # SemVer format -> https://semver.org -pluginVersion = 2.2.0 +pluginVersion = 2.2.1 # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html pluginSinceBuild = 221 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 805f62c..e720888 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,13 +1,13 @@ [versions] # libraries annotations = "24.1.0" -semver4j = "5.2.3" +semver4j = "5.3.0" # plugins kotlin = "1.9.23" changelog = "2.2.0" gradleIntelliJPlugin = "1.17.3" -qodana = "2023.3.2" +qodana = "2024.1.2" kover = "0.7.6" [libraries] @@ -18,5 +18,6 @@ semver4j = { group = "org.semver4j", name = "semver4j", version.ref = "semver4j" changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" } gradleIntelliJPlugin = { id = "org.jetbrains.intellij", version.ref = "gradleIntelliJPlugin" } kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +serialization = { id = "plugin.serialization", version.ref = "kotlin" } kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } qodana = { id = "org.jetbrains.qodana", version.ref = "qodana" } diff --git a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/engine/NPMJSClient.kt b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/engine/NPMJSClient.kt index b27a5b8..ebe320e 100644 --- a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/engine/NPMJSClient.kt +++ b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/engine/NPMJSClient.kt @@ -1,12 +1,16 @@ package com.github.warningimhack3r.npmupdatedependencies.backend.engine +import com.github.warningimhack3r.npmupdatedependencies.backend.extensions.asJsonArray +import com.github.warningimhack3r.npmupdatedependencies.backend.extensions.asJsonObject +import com.github.warningimhack3r.npmupdatedependencies.backend.extensions.asString import com.github.warningimhack3r.npmupdatedependencies.backend.extensions.parallelMap -import com.google.gson.JsonObject -import com.google.gson.JsonParser import com.intellij.openapi.components.Service import com.intellij.openapi.components.service import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonObject import java.net.URI import java.net.http.HttpClient import java.net.http.HttpRequest @@ -49,7 +53,7 @@ class NPMJSClient(private val project: Project) { log.debug("No dist.tarball found for package $packageName in any registry") } } - val registry = "${computedRegistry.substringBefore("/$packageName")}/" + val registry = computedRegistry.substringBefore("/$packageName") log.info("Computed registry for package $packageName: $registry") registryForPackage[packageName] = registry registry @@ -76,11 +80,7 @@ class NPMJSClient(private val project: Project) { return null } return try { - if (JsonParser.parseString(responseBody).isJsonObject) { - JsonParser.parseString(responseBody).asJsonObject - } else { - null - } + Json.parseToJsonElement(responseBody).jsonObject } catch (e: Exception) { log.warn("Error while parsing response body from $uri", e) null @@ -110,7 +110,7 @@ class NPMJSClient(private val project: Project) { log.info("Getting all versions for package $packageName") val registry = getRegistry(packageName) val json = getBodyAsJSON("${registry}/$packageName") - return json?.get("versions")?.asJsonObject?.keySet()?.toList().also { + return json?.get("versions")?.asJsonObject?.keys?.toList().also { if (it != null) { log.info("All versions for package $packageName found in cache: $it") } @@ -121,7 +121,12 @@ class NPMJSClient(private val project: Project) { log.warn("All versions for package $packageName not found") return null } else if (versions.startsWith("[")) { - JsonParser.parseString(versions).asJsonArray.map { it.asString } + try { + Json.parseToJsonElement(versions) + } catch (e: Exception) { + log.warn("Error while parsing all versions for package $packageName", e) + null + }?.asJsonArray?.mapNotNull { it.asString } ?: emptyList() } else { listOf(versions.replace("\"", "")) } @@ -146,7 +151,7 @@ class NPMJSClient(private val project: Project) { if (it != null) { log.info("Deprecation status for package $packageName found: $it") } else { - log.warn("Deprecation status for package $packageName not found") + log.debug("No deprecation status found for package $packageName") } } } diff --git a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/extensions/Extensions.kt b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/extensions/Extensions.kt index b7e2e82..9245d8a 100644 --- a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/extensions/Extensions.kt +++ b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/extensions/Extensions.kt @@ -2,6 +2,28 @@ package com.github.warningimhack3r.npmupdatedependencies.backend.extensions import com.intellij.json.psi.JsonValue import kotlinx.coroutines.* +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive + +fun safeConversion(block: () -> T): T? = try { + block() +} catch (e: Exception) { + null +} + +val JsonElement.asJsonObject + get() = safeConversion { jsonObject } + +val JsonElement.asJsonArray + get() = safeConversion { jsonArray } + +var JsonElement.asString + get() = safeConversion { jsonPrimitive.content } + set(_) { + // Do nothing + } fun JsonValue.stringValue(): String = text.replace("\"", "") diff --git a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/errorsubmitter/GitHubErrorReportSubmitter.kt b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/errorsubmitter/GitHubErrorReportSubmitter.kt index b3d82b4..f25d3ad 100644 --- a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/errorsubmitter/GitHubErrorReportSubmitter.kt +++ b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/errorsubmitter/GitHubErrorReportSubmitter.kt @@ -25,10 +25,10 @@ class GitHubErrorReportSubmitter : ErrorReportSubmitter() { private const val MAX_URL_LENGTH = 2083 private const val BUG_LOGS_KEY = "bug-logs" private const val TRIMMED_STACKTRACE_MARKER = "\n\n" - private const val WORM_UNICODE = "\uD83D\uDC1B" + private const val UNICODE_WORM = "\uD83D\uDC1B" } - override fun getReportActionText() = "$WORM_UNICODE Open an Issue on GitHub" + override fun getReportActionText() = "$UNICODE_WORM Open an Issue on GitHub" override fun submit( events: Array, @@ -37,6 +37,7 @@ class GitHubErrorReportSubmitter : ErrorReportSubmitter() { consumer: Consumer ): Boolean { return try { + // Base data val event = if (events.isNotEmpty()) events.first() else null val stackTrace = event?.throwableText ?: "" @@ -48,13 +49,32 @@ class GitHubErrorReportSubmitter : ErrorReportSubmitter() { DataManager.getInstance().getDataContext(parentComponent) ) ?: getLastFocusedOrOpenedProject() + // Computed data + var causedByLastIndex = -1 + val splitStackTrace = stackTrace.split("\n") + splitStackTrace.reversed().forEachIndexed { index, s -> + if (s.lowercase().startsWith("caused by")) { + causedByLastIndex = splitStackTrace.size - index + return@forEachIndexed + } + } + + // Build URL and content BrowserUtil.browse(buildAbbreviatedUrl( mapOf( "title" to "[crash] $simpleErrorMessage", "bug-explanation" to (additionalInfo ?: ""), - BUG_LOGS_KEY to stackTrace.split("\n").filter { - !it.trim().startsWith("at java.desktop/") - && !it.trim().startsWith("at java.base/") + BUG_LOGS_KEY to splitStackTrace.filterIndexed { index, s -> + if (index == 0) return@filterIndexed true + val line = s.trim() + if (causedByLastIndex > 0 && line.startsWith("at ") && index < causedByLastIndex) { + return@filterIndexed false + } + !line.startsWith("at java.desktop/") + && !line.startsWith("at java.base/") + && !line.startsWith("at kotlin.") + && !line.startsWith("at kotlinx.") + && !line.startsWith("at com.intellij.") }.joinToString("\n"), /*"device-os" to with(System.getProperty("os.name").lowercase()) { when { // Windows, macOS or Linux @@ -63,7 +83,7 @@ class GitHubErrorReportSubmitter : ErrorReportSubmitter() { else -> "Linux" } },*/ // currently cannot be set (https://github.com/orgs/community/discussions/44983) - "additional-device-info" to getDefaultHelpBlock(project) + "additional-device-info" to getPlatformAndPluginsInfo(project) ).filterValues { it.isNotEmpty() } )) consumer.consume(SubmittedReportInfo(SubmittedReportInfo.SubmissionStatus.NEW_ISSUE)) @@ -74,32 +94,47 @@ class GitHubErrorReportSubmitter : ErrorReportSubmitter() { } } + /** + * Build the URL for the GitHub issue from the given fields, abbreviating the URL if necessary + * to fit within the maximum URL length. + * + * @param fields the fields to include in the URL. + * @return the URL for the GitHub issue. + */ private fun buildAbbreviatedUrl(fields: Map): URI { val url = buildUrl(fields) - return URI(if (url.length > MAX_URL_LENGTH) { - val newMap = fields.toMutableMap() - newMap[BUG_LOGS_KEY]?.let { fullLog -> - val logLessUrlLength = buildUrl(fields.mapValues { (key, value) -> - if (key == BUG_LOGS_KEY) "" else value - }).length - val encodedLogDiff = URLEncoder.encode(fullLog, StandardCharsets.UTF_8).length - fullLog.length - newMap[BUG_LOGS_KEY] = fullLog.take( - (MAX_URL_LENGTH - logLessUrlLength - encodedLogDiff).coerceAtLeast(fullLog.substringBefore("\n").length) - ).run { - if (length > fullLog.substringBefore("\n").length + TRIMMED_STACKTRACE_MARKER.length) { - "${take(length - TRIMMED_STACKTRACE_MARKER.length)}$TRIMMED_STACKTRACE_MARKER" - } else this + return URI( + if (url.length > MAX_URL_LENGTH) { + val newMap = fields.toMutableMap() + newMap[BUG_LOGS_KEY]?.let { fullLog -> + val logLessUrlLength = buildUrl(fields.mapValues { (key, value) -> + if (key == BUG_LOGS_KEY) "" else value + }).length + val encodedLogDiff = URLEncoder.encode(fullLog, StandardCharsets.UTF_8).length - fullLog.length + newMap[BUG_LOGS_KEY] = fullLog.take( + (MAX_URL_LENGTH - logLessUrlLength - encodedLogDiff).coerceAtLeast(fullLog.substringBefore("\n").length) + ).run { + if (length > fullLog.substringBefore("\n").length + TRIMMED_STACKTRACE_MARKER.length) { + "${take(length - TRIMMED_STACKTRACE_MARKER.length)}$TRIMMED_STACKTRACE_MARKER" + } else this + } } - } - val shorterLogUrl = buildUrl(newMap) - if (shorterLogUrl.length > MAX_URL_LENGTH) { - buildUrl(fields.filter { (key, _) -> - key == "title" || key == "additional-device-info" - }) - } else shorterLogUrl - } else url) + val shorterLogUrl = buildUrl(newMap) + if (shorterLogUrl.length > MAX_URL_LENGTH) { + buildUrl(fields.filter { (key, _) -> + key == "title" || key == "additional-device-info" + }) + } else shorterLogUrl + } else url + ) } + /** + * Build the URL for the GitHub issue from the given fields. + * + * @param fields the fields to include in the URL. + * @return the URL for the GitHub issue. + */ private fun buildUrl(fields: Map) = buildString { append("https://github.com/WarningImHack3r/npm-update-dependencies/issues/new?labels=bug&template=bug_report.yml") fields.forEach { (key, value) -> @@ -107,20 +142,34 @@ class GitHubErrorReportSubmitter : ErrorReportSubmitter() { } } - private fun getDefaultHelpBlock(project: Project): String { + /** + * Get the platform and plugins information for the given project. + * Used in the "Additional platform info" section of the GitHub issue. + * + * @param project the [Project][com.intellij.openapi.project.Project] to get the platform and plugins information from. + * @return the platform and plugins information for the given project. + */ + private fun getPlatformAndPluginsInfo(project: Project): String { return CompositeGeneralTroubleInfoCollector().collectInfo(project).run { val trimmedAndCleaned = split("\n".toRegex()).filter { trim().isNotEmpty() } // Build, JRE, JVM, OS - trimmedAndCleaned - .dropWhile { s -> s == "=== About ==="} - .takeWhile { s -> s != "=== System ===" } - .filter { s -> !s.startsWith("idea.") && !s.startsWith("Theme") } - .joinToString("\n") + "\n" + - // Plugins - trimmedAndCleaned - .dropWhile { s -> s != "=== Plugins ===" } - .takeWhile { s -> s.isNotBlank() && s.isNotEmpty() } - .joinToString("\n") + buildString { + append( + trimmedAndCleaned + .dropWhile { s -> s == "=== About ===" } + .takeWhile { s -> s != "=== System ===" } + .filter { s -> !s.startsWith("idea.") && !s.startsWith("Theme") } + .joinToString("\n") + ) + append("\n") + // Plugins + append( + trimmedAndCleaned + .dropWhile { s -> s != "=== Plugins ===" } + .takeWhile { s -> s.isNotBlank() && s.isNotEmpty() } + .joinToString("\n") + ) + } } } @@ -131,12 +180,10 @@ class GitHubErrorReportSubmitter : ErrorReportSubmitter() { * @return the [Project][com.intellij.openapi.project.Project] that was last in focus or open. */ private fun getLastFocusedOrOpenedProject(): Project { - val project = IdeFocusManager.getGlobalInstance().lastFocusedFrame?.project - if (project == null) { + return IdeFocusManager.getGlobalInstance().lastFocusedFrame?.project ?: run { val projectManager = ProjectManager.getInstance() val openProjects = projectManager.openProjects - return if (openProjects.isNotEmpty()) openProjects.first() else projectManager.defaultProject + if (openProjects.isNotEmpty()) openProjects.first() else projectManager.defaultProject } - return project } } diff --git a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/actions/update/UpdateAllSatisfiesAction.kt b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/actions/update/UpdateAllSatisfiesAction.kt index f6bc26e..bdcd891 100644 --- a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/actions/update/UpdateAllSatisfiesAction.kt +++ b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/actions/update/UpdateAllSatisfiesAction.kt @@ -6,9 +6,10 @@ import com.github.warningimhack3r.npmupdatedependencies.ui.helpers.ActionsCommon import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.actionSystem.UpdateInBackground import com.intellij.openapi.components.service -class UpdateAllSatisfiesAction : AnAction() { +class UpdateAllSatisfiesAction : AnAction(), UpdateInBackground { override fun update(e: AnActionEvent) { val availableUpdates = e.project?.service()?.availableUpdates e.presentation.isEnabled = if (availableUpdates != null) {