diff --git a/README.md b/README.md index fbd1850..9775d6a 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,15 @@ [![Downloads](https://img.shields.io/jetbrains/plugin/d/com.github.warningimhack3r.npmupdatedependencies.svg)](https://plugins.jetbrains.com/plugin/com.github.warningimhack3r.npmupdatedependencies) ## Description + Update your npm dependencies with a single click. -This plugin will update all the dependencies in your `package.json` file to the latest version, or the satisfying version depending on your choice. +This plugin will update all the dependencies in your `package.json` file to the latest version, or the satisfying +version depending on your choice. ## Features + - Update a dependency to the latest or satisfying version - Support for custom registries and private packages - Keep comparators (e.g. `^`, `~`, `>`, `<`, `>=`, `<=`) when replacing versions @@ -18,6 +21,7 @@ This plugin will update all the dependencies in your `package.json` file to the - Batch update all dependencies (latest or satisfying), all deprecated dependencies - Get notified of deprecated dependencies with a banner - See your outdated dependencies at a glance in the status bar +- Exclude dependencies or versions from the scan - Configure everything in the settings - Manually invalidate the cache in case of issues - ...and more! @@ -25,6 +29,7 @@ This plugin will update all the dependencies in your `package.json` file to the ## Usage There are 3 ways to invoke the extension menu: + - Hover over an annotated dependency and click the action you want to perform - Right click in the package.json file and select the extension from the context menu - Use the Tools menu @@ -33,25 +38,22 @@ Configuration options are available in the settings. > Works by fetching [registry.npmjs.org](https://registry.npmjs.org). > Rewrite of the existing [npm-dependency-checker](https://github.com/unger1984/npm-dependency-checker) plugin. - -## Planned Features -- Show changelog for each dependency -- Automatically install upgraded dependencies when saving the package.json file? ## Installation - Using IDE built-in plugin system: - - Settings/Preferences > Plugins > Marketplace > Search for "npm-update-dependencies" > + + Settings/Preferences > Plugins > Marketplace > Search for " + npm-update-dependencies" > Install Plugin - + - Manually: - Download the [latest release](https://github.com/WarningImHack3r/npm-update-dependencies/releases/latest) and install it manually using + Download the [latest release](https://github.com/WarningImHack3r/npm-update-dependencies/releases/latest) and install + it manually using Settings/Preferences > Plugins > ⚙️ > Install plugin from disk... - --- Plugin based on the [IntelliJ Platform Plugin Template][template]. 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 a472a6a..b27a5b8 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 @@ -5,8 +5,8 @@ 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 com.jetbrains.rd.util.printlnError import java.net.URI import java.net.http.HttpClient import java.net.http.HttpRequest @@ -16,27 +16,46 @@ import java.net.http.HttpResponse class NPMJSClient(private val project: Project) { companion object { private const val NPMJS_REGISTRY = "https://registry.npmjs.org" + private val log = logger() + + @JvmStatic + fun getInstance(project: Project): NPMJSClient = project.service() } private fun getRegistry(packageName: String): String { - val registryForPackage = project.service().packageRegistries - val availableRegistries = project.service().registries - return registryForPackage[packageName] ?: ShellRunner.execute( + log.info("Getting registry for package $packageName") + val registryForPackage = NUDState.getInstance(project).packageRegistries + val availableRegistries = RegistriesScanner.getInstance(project).registries + return registryForPackage[packageName].also { + if (it != null) { + log.debug("Registry for package $packageName found in cache: $it") + } + } ?: ShellRunner.execute( arrayOf("npm", "v", packageName, "dist.tarball") )?.trim()?.let { dist -> val computedRegistry = dist.ifEmpty { + log.debug("No dist.tarball found for package $packageName, trying all registries") availableRegistries.parallelMap { registry -> ShellRunner.execute( arrayOf("npm", "v", packageName, "dist.tarball", "--registry=$registry") )?.trim()?.let { regDist -> regDist.ifEmpty { null } } - }.firstNotNullOfOrNull { it } ?: return@let null + }.firstNotNullOfOrNull { it }.also { + if (it != null) { + log.debug("Found dist.tarball for package $packageName in registry $it") + } + } ?: return@let null.also { + log.debug("No dist.tarball found for package $packageName in any registry") + } } val registry = "${computedRegistry.substringBefore("/$packageName")}/" + log.info("Computed registry for package $packageName: $registry") registryForPackage[packageName] = registry registry - } ?: NPMJS_REGISTRY + } ?: NPMJS_REGISTRY.also { + log.info("Using default registry for package $packageName") + } } private fun getResponseBody(uri: URI): String { @@ -53,7 +72,7 @@ class NPMJSClient(private val project: Project) { try { responseBody = getResponseBody(URI(uri)) } catch (e: Exception) { - printlnError("Error while getting response body from $uri: ${e.message}") + log.warn("Error while getting response body from $uri", e) return null } return try { @@ -63,40 +82,72 @@ class NPMJSClient(private val project: Project) { null } } catch (e: Exception) { - printlnError("Error while parsing response body from $uri: ${e.message}") + log.warn("Error while parsing response body from $uri", e) null } } fun getLatestVersion(packageName: String): String? { + log.info("Getting latest version for package $packageName") val registry = getRegistry(packageName) val json = getBodyAsJSON("${registry}/$packageName/latest") - return json?.get("version")?.asString ?: ShellRunner.execute( + return json?.get("version")?.asString.also { + if (it != null) { + log.info("Latest version for package $packageName found in cache: $it") + } + } ?: ShellRunner.execute( arrayOf("npm", "v", packageName, "version", "--registry=$registry") - )?.trim()?.let { it.ifEmpty { null } } + )?.trim()?.let { it.ifEmpty { null } }.also { + if (it != null) { + log.info("Latest version for package $packageName found: $it") + } else { + log.warn("Latest version for package $packageName not found") + } + } } fun getAllVersions(packageName: String): List? { + log.info("Getting all versions for package $packageName") val registry = getRegistry(packageName) val json = getBodyAsJSON("${registry}/$packageName") - return json?.get("versions")?.asJsonObject?.keySet()?.toList() ?: ShellRunner.execute( + return json?.get("versions")?.asJsonObject?.keySet()?.toList().also { + if (it != null) { + log.info("All versions for package $packageName found in cache: $it") + } + } ?: ShellRunner.execute( arrayOf("npm", "v", packageName, "versions", "--json", "--registry=$registry") )?.trim()?.let { versions -> if (versions.isEmpty()) { + log.warn("All versions for package $packageName not found") return null } else if (versions.startsWith("[")) { JsonParser.parseString(versions).asJsonArray.map { it.asString } } else { listOf(versions.replace("\"", "")) } + }.also { versions -> + if (versions != null) { + log.info("All versions for package $packageName found: $versions") + } } } fun getPackageDeprecation(packageName: String): String? { + log.info("Getting deprecation status for package $packageName") val registry = getRegistry(packageName) val json = getBodyAsJSON("${registry}/$packageName/latest") - return json?.get("deprecated")?.asString ?: ShellRunner.execute( + return json?.get("deprecated")?.asString.also { + if (it != null) { + log.info("Deprecation status for package $packageName found in cache: $it") + } + } ?: ShellRunner.execute( arrayOf("npm", "v", packageName, "deprecated", "--registry=$registry") - )?.trim()?.let { it.ifEmpty { null } } + )?.trim()?.let { it.ifEmpty { null } }.also { + if (it != null) { + log.info("Deprecation status for package $packageName found: $it") + } else { + log.warn("Deprecation status for package $packageName not found") + } + } } } diff --git a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/engine/NUDState.kt b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/engine/NUDState.kt index 0d838e6..74d920b 100644 --- a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/engine/NUDState.kt +++ b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/engine/NUDState.kt @@ -4,9 +4,16 @@ import com.github.warningimhack3r.npmupdatedependencies.backend.data.Deprecation import com.github.warningimhack3r.npmupdatedependencies.backend.data.ScanResult import com.github.warningimhack3r.npmupdatedependencies.ui.statusbar.StatusBarHelper import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project @Service(Service.Level.PROJECT) class NUDState { + companion object { + @JvmStatic + fun getInstance(project: Project): NUDState = project.service() + } + val availableUpdates = mutableMapOf() val deprecations = mutableMapOf() val packageRegistries = mutableMapOf() diff --git a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/engine/PackageUpdateChecker.kt b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/engine/PackageUpdateChecker.kt index 7a1cb64..2cf3c5d 100644 --- a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/engine/PackageUpdateChecker.kt +++ b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/engine/PackageUpdateChecker.kt @@ -6,11 +6,19 @@ import com.github.warningimhack3r.npmupdatedependencies.settings.NUDSettingsStat import com.github.warningimhack3r.npmupdatedependencies.ui.helpers.NUDHelper 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 org.semver4j.Semver @Service(Service.Level.PROJECT) class PackageUpdateChecker(private val project: Project) { + companion object { + private val log = logger() + + @JvmStatic + fun getInstance(project: Project): PackageUpdateChecker = project.service() + } + private fun isVersionUpgradable(version: String): Boolean { return !(version.startsWith("http") || version.startsWith("git") @@ -22,7 +30,13 @@ class PackageUpdateChecker(private val project: Project) { return comparator.split(" ").any { comp -> val comparatorVersion = NUDHelper.Regex.semverPrefix.replace(comp, "") if (comparatorVersion.trim().isEmpty()) return@any false - Semver.coerce(comparatorVersion)?.let { version.isGreaterThan(it) } == true + Semver.coerce(comparatorVersion)?.let { version.isGreaterThan(it) }.also { + when (it) { + true -> log.debug("Version $version is greater than $comparatorVersion") + false -> log.debug("Version $version is not greater than $comparatorVersion") + else -> log.warn("Comparator version $comparatorVersion is invalid (comparator: $comp)") + } + } == true } } @@ -38,39 +52,52 @@ class PackageUpdateChecker(private val project: Project) { private fun getVersionExcludingFilter(packageName: String, version: Semver): String? { return NUDSettingsState.instance.excludedVersions[packageName]?.let { excludedVersions -> + log.debug("Excluded versions for $packageName: $excludedVersions") excludedVersions.firstOrNull { excludedVersion -> - version.satisfies(excludedVersion) + version.satisfies(excludedVersion).also { satisfies -> + if (satisfies) { + log.debug("Version $version satisfies excluded version $excludedVersion") + } else { + log.debug("Version $version does not satisfy excluded version $excludedVersion") + } + } } } } fun areUpdatesAvailable(packageName: String, comparator: String): ScanResult? { - val availableUpdates = project.service().availableUpdates + log.info("Checking for updates for $packageName with comparator $comparator") + val availableUpdates = NUDState.getInstance(project).availableUpdates if (!isVersionUpgradable(comparator)) { if (availableUpdates.containsKey(packageName)) { availableUpdates.remove(packageName) } + log.warn("Comparator $comparator is not upgradable, removing cached versions for $packageName") return null } // Check if an update has already been found availableUpdates[packageName]?.let { cachedVersions -> if (areVersionsMatchingComparatorNeeds(cachedVersions.versions, comparator)) { + log.info("Cached versions for $packageName are still valid, returning them") return cachedVersions } } // Check if an update is available - val npmjsClient = project.service() + val npmjsClient = NPMJSClient.getInstance(project) var newestVersion = npmjsClient.getLatestVersion(packageName)?.let { Semver.coerce(it) - } ?: return null + } ?: return null.also { + log.warn("No latest version found for $packageName") + } var satisfyingVersion: Semver? = null val updateAvailable = isVersionMoreRecentThanComparator(newestVersion, comparator) if (!updateAvailable) { if (availableUpdates.containsKey(packageName)) { availableUpdates.remove(packageName) } + log.info("No update available for $packageName, removing cached versions") return null } @@ -81,6 +108,7 @@ class PackageUpdateChecker(private val project: Project) { || newestVersion.build.isNotEmpty() || !newestVersion.satisfies(comparator) ) { + log.debug("Latest version $newestVersion is excluded, a beta, or does not satisfy the comparator") val allVersions = npmjsClient.getAllVersions(packageName)?.mapNotNull { version -> Semver.coerce(version) }?.sortedDescending() ?: emptyList() @@ -94,11 +122,14 @@ class PackageUpdateChecker(private val project: Project) { } else if (version.preRelease.isEmpty() && version.build.isEmpty() && isVersionMoreRecentThanComparator(version, comparator) ) { + log.debug("Found latest version $version that satisfies the comparator, excluding filters: $filtersAffectingVersions") latest = version break } } - newestVersion = latest ?: return null // No version greater than the comparator and not filtered + newestVersion = latest ?: return null.also { // No version greater than the comparator and not filtered + log.warn("No latest version found for $packageName that satisfies the comparator") + } // Find satisfying version if (!newestVersion.satisfies(comparator)) { @@ -111,6 +142,7 @@ class PackageUpdateChecker(private val project: Project) { && version.satisfies(comparator) && isVersionMoreRecentThanComparator(version, comparator) } + log.debug("Found satisfying version $satisfyingVersion for $packageName, excluding filters: $filtersAffectingVersions") } } @@ -118,6 +150,7 @@ class PackageUpdateChecker(private val project: Project) { Versions(newestVersion, satisfyingVersion), filtersAffectingVersions ).also { + log.info("Found updates for $packageName: $it") availableUpdates[packageName] = it } } diff --git a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/engine/RegistriesScanner.kt b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/engine/RegistriesScanner.kt index 71d95be..7950381 100644 --- a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/engine/RegistriesScanner.kt +++ b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/engine/RegistriesScanner.kt @@ -1,12 +1,23 @@ package com.github.warningimhack3r.npmupdatedependencies.backend.engine import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.project.Project @Service(Service.Level.PROJECT) class RegistriesScanner { + companion object { + private val log = logger() + + @JvmStatic + fun getInstance(project: Project): RegistriesScanner = project.service() + } + var registries: List = emptyList() fun scan() { + log.info("Starting to scan registries") // Run `npm config ls` to get the list of registries val config = ShellRunner.execute(arrayOf("npm", "config", "ls")) ?: return registries = config.lines().asSequence().filter { line -> @@ -24,5 +35,6 @@ class RegistriesScanner { .replace("\"", "") } }.map { it.removeSuffix("/") }.distinct().toList() + log.info("Found registries: $registries") } } diff --git a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/engine/ShellRunner.kt b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/engine/ShellRunner.kt index 5f02b93..ca08b12 100644 --- a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/engine/ShellRunner.kt +++ b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/engine/ShellRunner.kt @@ -1,8 +1,9 @@ package com.github.warningimhack3r.npmupdatedependencies.backend.engine -import com.jetbrains.rd.util.printlnError +import com.intellij.openapi.diagnostic.logger object ShellRunner { + private val log = logger() private val failedCommands = mutableSetOf() private fun isWindows() = System.getProperty("os.name").lowercase().contains("win") @@ -11,8 +12,10 @@ object ShellRunner { val commandName = command.firstOrNull() ?: return null if (isWindows() && failedCommands.contains(commandName)) { command[0] = "$commandName.cmd" + log.warn("Retrying command with .cmd extension: \"${command.joinToString(" ")}\"") } return try { + log.debug("Executing \"${command.joinToString(" ")}\"") val process = ProcessBuilder(*command) .redirectOutput(ProcessBuilder.Redirect.PIPE) .start() @@ -23,7 +26,7 @@ object ShellRunner { failedCommands.add(commandName) return execute(arrayOf("$commandName.cmd") + command.drop(1).toTypedArray()) } - printlnError("Error while executing \"${command.joinToString(" ")}\": ${e.message}") + log.warn("Error while executing \"${command.joinToString(" ")}\"", e) null } } diff --git a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/settings/NUDSettingsComponent.kt b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/settings/NUDSettingsComponent.kt index 93c069e..49b8208 100644 --- a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/settings/NUDSettingsComponent.kt +++ b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/settings/NUDSettingsComponent.kt @@ -8,7 +8,6 @@ import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer import com.intellij.ide.DataManager import com.intellij.openapi.application.ModalityState import com.intellij.openapi.application.runInEdt -import com.intellij.openapi.components.service import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.options.ex.Settings import com.intellij.openapi.project.ProjectManager @@ -188,7 +187,7 @@ class NUDSettingsComponent { ProjectManager.getInstance().openProjects.forEach { project -> // Clear the cache for packages with excluded versions settings.excludedVersions.keys.forEach { packageName -> - project.service().availableUpdates.remove(packageName) + NUDState.getInstance(project).availableUpdates.remove(packageName) } // if project's currently open file is package.json, re-analyze it FileEditorManager.getInstance(project).selectedTextEditor?.let { editor -> diff --git a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/annotation/DeprecationAnnotator.kt b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/annotation/DeprecationAnnotator.kt index 5147d31..0d9294a 100644 --- a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/annotation/DeprecationAnnotator.kt +++ b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/annotation/DeprecationAnnotator.kt @@ -13,7 +13,7 @@ import com.intellij.json.psi.JsonProperty import com.intellij.lang.annotation.AnnotationHolder import com.intellij.lang.annotation.ExternalAnnotator import com.intellij.lang.annotation.HighlightSeverity -import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.DumbAware import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile @@ -24,6 +24,9 @@ class DeprecationAnnotator : DumbAware, ExternalAnnotator< Pair>, Map >() { + companion object { + private val log = logger() + } override fun collectInformation(file: PsiFile): Pair> = Pair(file.project, AnnotatorsCommon.getInfo(file)) @@ -32,19 +35,23 @@ class DeprecationAnnotator : DumbAware, ExternalAnnotator< val (project, info) = collectedInfo if (info.isEmpty()) return emptyMap() - var state = project.service() + var state = NUDState.getInstance(project) if (!state.isScanningForRegistries && state.packageRegistries.isEmpty()) { state.isScanningForRegistries = true - project.service().scan() + log.debug("No registries found, scanning for registries...") + RegistriesScanner.getInstance(project).scan() + log.debug("Registries scanned") state.isScanningForRegistries = false } while (state.isScanningForRegistries || state.isScanningForDeprecations) { // Wait for the registries to be scanned and avoid multiple scans at the same time + log.debug("Waiting for registries to be scanned...") } - state = project.service() - val npmjsClient = project.service() + log.debug("Scanning for deprecations...") + state = NUDState.getInstance(project) + val npmjsClient = NPMJSClient.getInstance(project) return info .also { // Remove from the cache all deprecations that are no longer in the file @@ -106,11 +113,13 @@ class DeprecationAnnotator : DumbAware, ExternalAnnotator< } } }.filterNotNull().toMap().also { + log.debug("Deprecations scanned, ${it.size} found") state.isScanningForDeprecations = false } } override fun apply(file: PsiFile, annotationResult: Map, holder: AnnotationHolder) { + if (annotationResult.isNotEmpty()) log.debug("Creating annotations...") annotationResult.forEach { (property, deprecation) -> holder.newAnnotation(HighlightSeverity.ERROR, deprecation.reason) .range(property.textRange) @@ -137,6 +146,7 @@ class DeprecationAnnotator : DumbAware, ExternalAnnotator< .create() } if (annotationResult.isNotEmpty()) { + log.debug("Annotations created, updating banner") EditorNotifications.getInstance(file.project).updateAllNotifications() } } diff --git a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/annotation/UpdatesAnnotator.kt b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/annotation/UpdatesAnnotator.kt index 6e54e7f..3bdcbc4 100644 --- a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/annotation/UpdatesAnnotator.kt +++ b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/annotation/UpdatesAnnotator.kt @@ -16,7 +16,7 @@ import com.intellij.json.psi.JsonProperty import com.intellij.lang.annotation.AnnotationHolder import com.intellij.lang.annotation.ExternalAnnotator import com.intellij.lang.annotation.HighlightSeverity -import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.DumbAware import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile @@ -27,6 +27,9 @@ class UpdatesAnnotator : DumbAware, ExternalAnnotator< Pair>, Map >() { + companion object { + private val log = logger() + } override fun collectInformation(file: PsiFile): Pair> = Pair(file.project, AnnotatorsCommon.getInfo(file)) @@ -35,19 +38,23 @@ class UpdatesAnnotator : DumbAware, ExternalAnnotator< val (project, info) = collectedInfo if (info.isEmpty()) return emptyMap() - var state = project.service() + var state = NUDState.getInstance(project) if (!state.isScanningForRegistries && state.packageRegistries.isEmpty()) { state.isScanningForRegistries = true - project.service().scan() + log.debug("No registries found, scanning for registries...") + RegistriesScanner.getInstance(project).scan() + log.debug("Registries scanned") state.isScanningForRegistries = false } while (state.isScanningForRegistries || state.isScanningForUpdates) { // Wait for the registries to be scanned and avoid multiple scans at the same time + log.debug("Waiting for registries to be scanned...") } - state = project.service() - val updateChecker = project.service() + log.debug("Scanning for updates...") + state = NUDState.getInstance(project) + val updateChecker = PackageUpdateChecker.getInstance(project) return info .also { // Remove from the cache all properties that are no longer in the file @@ -67,11 +74,13 @@ class UpdatesAnnotator : DumbAware, ExternalAnnotator< Pair(property.jsonProperty, scanResult) } else null }.filterNotNull().toMap().also { + log.debug("Updates scanned, ${it.size} found") state.isScanningForUpdates = false } } override fun apply(file: PsiFile, annotationResult: Map, holder: AnnotationHolder) { + if (annotationResult.isNotEmpty()) log.debug("Creating annotations...") annotationResult.forEach { (property, scanResult) -> val versions = scanResult.versions val text = "An update is available!" + if (scanResult.affectedByFilters.isNotEmpty()) { @@ -120,5 +129,6 @@ class UpdatesAnnotator : DumbAware, ExternalAnnotator< .needsUpdateOnTyping() .create() } + if (annotationResult.isNotEmpty()) log.debug("Annotations created") } } diff --git a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/banner/DeprecationBanner.kt b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/banner/DeprecationBanner.kt index 15bd824..874a40e 100644 --- a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/banner/DeprecationBanner.kt +++ b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/banner/DeprecationBanner.kt @@ -5,7 +5,7 @@ import com.github.warningimhack3r.npmupdatedependencies.backend.engine.NUDState import com.github.warningimhack3r.npmupdatedependencies.settings.NUDSettingsState import com.github.warningimhack3r.npmupdatedependencies.ui.helpers.ActionsCommon import com.intellij.icons.AllIcons -import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.fileEditor.FileEditor import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile @@ -20,14 +20,22 @@ import java.util.function.Function import javax.swing.JComponent class DeprecationBanner : EditorNotificationProvider { + companion object { + private val log = logger() + } override fun collectNotificationData( project: Project, file: VirtualFile ): Function = Function { _ -> val psiFile = PsiManager.getInstance(project).findFile(file) - val deprecations = project.service().deprecations + val deprecations = NUDState.getInstance(project).deprecations if (psiFile == null || file.name != "package.json" || deprecations.isEmpty() || !NUDSettingsState.instance.showDeprecationBanner) { + when { + psiFile == null -> log.warn("Leaving: cannot find PSI file for ${file.name} @ ${file.path}") + deprecations.isEmpty() -> log.debug("Leaving: no deprecations found") + !NUDSettingsState.instance.showDeprecationBanner -> log.debug("Leaving: deprecation banner is disabled") + } return@Function null } val deprecationsCount = deprecations.size diff --git a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/helpers/ActionsCommon.kt b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/helpers/ActionsCommon.kt index 8cc1263..419a450 100644 --- a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/helpers/ActionsCommon.kt +++ b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/helpers/ActionsCommon.kt @@ -5,7 +5,6 @@ import com.github.warningimhack3r.npmupdatedependencies.backend.engine.NUDState import com.github.warningimhack3r.npmupdatedependencies.backend.extensions.stringValue import com.github.warningimhack3r.npmupdatedependencies.settings.NUDSettingsState import com.intellij.json.psi.JsonProperty -import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile import com.intellij.psi.impl.source.tree.LeafPsiElement @@ -24,7 +23,7 @@ object ActionsCommon { fun updateAll(file: PsiFile, kind: Versions.Kind) { getAllDependencies(file) .mapNotNull { property -> - file.project.service().availableUpdates[property.name]?.let { scanResult -> + NUDState.getInstance(file.project).availableUpdates[property.name]?.let { scanResult -> val newVersion = scanResult.versions.from(kind) ?: scanResult.versions.orderedAvailableKinds(kind) .firstOrNull { it != kind }?.let { scanResult.versions.from(it) } ?: return@mapNotNull null val prefix = NUDHelper.Regex.semverPrefix.find(property.value?.stringValue() ?: "")?.value ?: "" @@ -43,7 +42,7 @@ object ActionsCommon { } fun replaceAllDeprecations(file: PsiFile) { - val deprecations = file.project.service().deprecations + val deprecations = NUDState.getInstance(file.project).deprecations getAllDependencies(file) .mapNotNull { property -> deprecations[property.name]?.let { deprecation -> @@ -112,7 +111,7 @@ object ActionsCommon { } fun deleteAllDeprecations(file: PsiFile) { - val deprecations = file.project.service().deprecations + val deprecations = NUDState.getInstance(file.project).deprecations getAllDependencies(file) .mapNotNull { property -> if (deprecations.containsKey(property.name)) property else null diff --git a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/helpers/NUDHelper.kt b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/helpers/NUDHelper.kt index 3631070..d54945f 100644 --- a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/helpers/NUDHelper.kt +++ b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/helpers/NUDHelper.kt @@ -18,7 +18,8 @@ object NUDHelper { WriteCommandAction.runWriteCommandAction( file.project, description, "com.github.warningimhack3r.npmupdatedependencies", - action, file) + action, file + ) } if (async) { ApplicationManager.getApplication().invokeLater(writeAction) @@ -33,7 +34,11 @@ object NUDHelper { .firstChild } - fun getClosestElementMatching(match: (PsiElement) -> Boolean, element: PsiElement, cls: Class = PsiElement::class.java): PsiElement? { + fun getClosestElementMatching( + match: (PsiElement) -> Boolean, + element: PsiElement, + cls: Class = PsiElement::class.java + ): PsiElement? { var sibling = element.nextSibling while (sibling != null) { if (sibling.javaClass == cls && match(sibling)) { diff --git a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/listeners/OnSaveListener.kt b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/listeners/OnSaveListener.kt index 361a551..0432f00 100644 --- a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/listeners/OnSaveListener.kt +++ b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/listeners/OnSaveListener.kt @@ -7,7 +7,6 @@ import com.github.warningimhack3r.npmupdatedependencies.settings.NUDSettingsStat import com.github.warningimhack3r.npmupdatedependencies.ui.helpers.ActionsCommon import com.github.warningimhack3r.npmupdatedependencies.ui.helpers.NUDHelper import com.intellij.codeInsight.hint.HintManager -import com.intellij.openapi.components.service import com.intellij.openapi.editor.Document import com.intellij.openapi.fileEditor.FileDocumentManagerListener import com.intellij.openapi.fileEditor.FileEditorManager @@ -21,11 +20,12 @@ import javax.swing.JLabel class OnSaveListener(val project: Project) : FileDocumentManagerListener { override fun beforeDocumentSaving(document: Document) { - val state = project.service() + val state = NUDState.getInstance(project) // Initial checks val file = PsiDocumentManager.getInstance(project).getPsiFile(document) ?: return if (file.name != "package.json" || !NUDSettingsState.instance.autoFixOnSave - || (state.availableUpdates.isEmpty() && state.deprecations.isEmpty())) return + || (state.availableUpdates.isEmpty() && state.deprecations.isEmpty()) + ) return // Create a set of actions to perform val actionsToPerform = mutableSetOf<() -> Unit>() @@ -47,6 +47,7 @@ class OnSaveListener(val project: Project) : FileDocumentManagerListener { Deprecation.Action.REPLACE -> actionsToPerform.add { ActionsCommon.replaceAllDeprecations(file) } + Deprecation.Action.REMOVE -> actionsToPerform.add { ActionsCommon.deleteAllDeprecations(file) } diff --git a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/quickfix/BlacklistVersionFix.kt b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/quickfix/BlacklistVersionFix.kt index 4e74613..51ab5a4 100644 --- a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/quickfix/BlacklistVersionFix.kt +++ b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/quickfix/BlacklistVersionFix.kt @@ -6,7 +6,6 @@ import com.github.warningimhack3r.npmupdatedependencies.settings.NUDSettingsStat import com.github.warningimhack3r.npmupdatedependencies.ui.helpers.QuickFixesCommon import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer import com.intellij.codeInsight.intention.impl.BaseIntentionAction -import com.intellij.openapi.components.service import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile @@ -35,6 +34,6 @@ class BlacklistVersionFix( DaemonCodeAnalyzer.getInstance(project).restart(it) } // Clear the cache - project.service().availableUpdates.remove(dependencyName) + NUDState.getInstance(project).availableUpdates.remove(dependencyName) } } diff --git a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/quickfix/DeprecatedDependencyFix.kt b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/quickfix/DeprecatedDependencyFix.kt index 82b930e..8e85d38 100644 --- a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/quickfix/DeprecatedDependencyFix.kt +++ b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/quickfix/DeprecatedDependencyFix.kt @@ -9,7 +9,6 @@ import com.github.warningimhack3r.npmupdatedependencies.ui.helpers.NUDHelper import com.github.warningimhack3r.npmupdatedependencies.ui.helpers.QuickFixesCommon import com.intellij.codeInsight.intention.impl.BaseIntentionAction import com.intellij.json.psi.JsonProperty -import com.intellij.openapi.components.service import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile @@ -71,7 +70,7 @@ class DeprecatedDependencyFix( property.delete() } } - project.service().deprecations.remove(property.name) + NUDState.getInstance(project).deprecations.remove(property.name) ActionsCommon.deprecationsCompletion(project) } } diff --git a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/statusbar/StatusBarFactory.kt b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/statusbar/StatusBarFactory.kt index 61daa22..800f2d0 100644 --- a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/statusbar/StatusBarFactory.kt +++ b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/statusbar/StatusBarFactory.kt @@ -4,7 +4,6 @@ import com.github.warningimhack3r.npmupdatedependencies.backend.engine.NUDState import com.github.warningimhack3r.npmupdatedependencies.settings.NUDSettingsState import com.intellij.dvcs.ui.LightActionGroup import com.intellij.ide.DataManager -import com.intellij.openapi.components.service import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.fileEditor.OpenFileDescriptor import com.intellij.openapi.project.DumbAwareAction @@ -24,6 +23,7 @@ class StatusBarFactory : StatusBarEditorBasedWidgetFactory() { companion object { const val ID = "NpmUpdateDependenciesStatusBarEditorFactory" } + override fun getId(): String = ID override fun getDisplayName(): String = "NPM Update Dependencies Status Bar" @@ -48,6 +48,7 @@ class WidgetBar(project: Project) : EditorBasedWidget(project), StatusBarWidget. companion object { const val ID = "NpmUpdateDependenciesStatusBarWidgetBar" } + private var currentStatus = Status.UNAVAILABLE enum class Status { @@ -123,13 +124,13 @@ class WidgetBar(project: Project) : EditorBasedWidget(project), StatusBarWidget. "Available Changes", LightActionGroup().apply { addSeparator("Updates") - addAll(project.service().availableUpdates.toSortedMap().map { update -> + addAll(NUDState.getInstance(project).availableUpdates.toSortedMap().map { update -> DumbAwareAction.create(update.key) { openPackageJson(update.key) } }) addSeparator("Deprecations") - addAll(project.service().deprecations.toSortedMap().map { deprecation -> + addAll(NUDState.getInstance(project).deprecations.toSortedMap().map { deprecation -> DumbAwareAction.create(deprecation.key) { openPackageJson(deprecation.key) } @@ -143,7 +144,7 @@ class WidgetBar(project: Project) : EditorBasedWidget(project), StatusBarWidget. override fun getSelectedValue(): String? { if (!NUDSettingsState.instance.showStatusBarWidget) return null - val state = project.service() + val state = NUDState.getInstance(project) return when (currentStatus) { Status.UNAVAILABLE -> null Status.GATHERING_REGISTRIES -> "Gathering registries..." @@ -160,12 +161,14 @@ class WidgetBar(project: Project) : EditorBasedWidget(project), StatusBarWidget. deprecated == 0 -> "$outdated update${if (outdated == 1) "" else "s"}" else -> "$outdated update${if (outdated == 1) "" else "s"}, $deprecated deprecation${if (deprecated == 1) "" else "s"}" } + StatusBarMode.COMPACT -> when { outdated == 0 && deprecated == 0 -> null outdated == 0 -> "$deprecated D" deprecated == 0 -> "$outdated U" else -> "$outdated U / $deprecated D" } + null -> null } } @@ -174,7 +177,7 @@ class WidgetBar(project: Project) : EditorBasedWidget(project), StatusBarWidget. // Custom fun update() { - val state = project.service() + val state = NUDState.getInstance(project) currentStatus = when { project.isDisposed || !NUDSettingsState.instance.showStatusBarWidget -> Status.UNAVAILABLE state.isScanningForRegistries -> Status.GATHERING_REGISTRIES diff --git a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/statusbar/StatusBarHelper.kt b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/statusbar/StatusBarHelper.kt index 587ba36..f1784bd 100644 --- a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/statusbar/StatusBarHelper.kt +++ b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/statusbar/StatusBarHelper.kt @@ -1,15 +1,21 @@ package com.github.warningimhack3r.npmupdatedependencies.ui.statusbar +import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.ProjectManager import com.intellij.openapi.wm.WindowManager object StatusBarHelper { + private val log = logger() fun updateWidget() { + log.info("Updating widget") val projectManager = ProjectManager.getInstanceIfCreated() ?: return for (project in projectManager.openProjects) { - val widgetBar = WindowManager.getInstance().getStatusBar(project).getWidget(WidgetBar.ID) as? WidgetBar ?: continue + log.debug("Updating widget for project ${project.name}") + val widgetBar = + WindowManager.getInstance().getStatusBar(project).getWidget(WidgetBar.ID) as? WidgetBar ?: continue widgetBar.update() + log.debug("Widget updated for project ${project.name}") } } } diff --git a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/statusbar/StatusBarItemChooserDialog.kt b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/statusbar/StatusBarItemChooserDialog.kt index 9a3d23e..626dca4 100644 --- a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/statusbar/StatusBarItemChooserDialog.kt +++ b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/ui/statusbar/StatusBarItemChooserDialog.kt @@ -49,14 +49,17 @@ class StatusBarItemChooserDialog(items: Collection) : JDialog() { selectedItem = null dispose() } + KeyEvent.VK_ENTER -> { selectedItem = itemList.selectedIndex dispose() } + KeyEvent.VK_UP -> { itemList.selectedIndex = (itemList.selectedIndex - 1 + items.size) % items.size itemList.ensureIndexIsVisible(itemList.selectedIndex) } + KeyEvent.VK_DOWN -> { itemList.selectedIndex = (itemList.selectedIndex + 1) % items.size itemList.ensureIndexIsVisible(itemList.selectedIndex) @@ -117,6 +120,7 @@ class StatusBarItemChooserDialog(items: Collection) : JDialog() { selectedItem = null dispose() } + KeyEvent.VK_ENTER -> { selectedItem = itemList.selectedIndex dispose() @@ -124,7 +128,11 @@ class StatusBarItemChooserDialog(items: Collection) : JDialog() { } } }) - val scrollPane = JBScrollPane(itemList, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER) + val scrollPane = JBScrollPane( + itemList, + ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, + ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER + ) scrollPane.preferredSize = Dimension(WIDTH, (itemList.getCellBounds(0, 0)?.height?.plus(1) ?: 20).let { if (items.size > 10) it * 10 else it * items.size })