Skip to content

Commit

Permalink
2.3.0
Browse files Browse the repository at this point in the history
- Add a setting to customize max parallel tasks
- Slight performance improvements on scans startups
- Fix a rare crash with status bar when navigating projects (fixes #99)
- Fix cached updates never being used
- Fix logic issues, add more logs
- Upload .idea/icon.svg for a shared IDE look-and-feel
- Upgrade dependencies
  • Loading branch information
WarningImHack3r committed May 3, 2024
1 parent 2a92970 commit 1f8bcb7
Show file tree
Hide file tree
Showing 14 changed files with 149 additions and 61 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ jobs:

# Run Qodana inspections
- name: Qodana - Code Inspection
uses: JetBrains/[email protected].2
uses: JetBrains/[email protected]
with:
cache-default-branch-only: true

Expand Down
7 changes: 4 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.gradle
.idea
.gradle/
.idea/*
!.idea/icon.svg
.qodana
assets
build
build/
.DS_Store
22 changes: 22 additions & 0 deletions .idea/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@

## [Unreleased]

### Added

- Introduce a setting to customize the maximum amount of simultaneous scans

### Changed

- Slightly improve performance on scans startup
- Logic improvements for scanners

### Fixed

- Fix a rare crash with the status bar when navigating between projects (#99)
- Fix cached updates not being used when they should

## [2.2.1] - 2024-04-26

### Changed
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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.1
pluginVersion = 2.3.0

# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
pluginSinceBuild = 221
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ semver4j = "5.3.0"
kotlin = "1.9.23"
changelog = "2.2.0"
gradleIntelliJPlugin = "1.17.3"
qodana = "2024.1.2"
qodana = "2024.1.3"
kover = "0.7.6"

[libraries]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ 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
Expand Down Expand Up @@ -79,12 +78,7 @@ class NPMJSClient(private val project: Project) {
log.warn("Error while getting response body from $uri", e)
return null
}
return try {
Json.parseToJsonElement(responseBody).jsonObject
} catch (e: Exception) {
log.warn("Error while parsing response body from $uri", e)
null
}
return Json.parseToJsonElement(responseBody).asJsonObject
}

fun getLatestVersion(packageName: String): String? {
Expand Down Expand Up @@ -112,7 +106,8 @@ class NPMJSClient(private val project: Project) {
val json = getBodyAsJSON("${registry}/$packageName")
return json?.get("versions")?.asJsonObject?.keys?.toList().also {
if (it != null) {
log.info("All versions for package $packageName found in cache: $it")
log.info("All versions for package $packageName found in cache (${it.size} versions)")
log.debug("Versions in cache for $packageName: $it")
}
} ?: ShellRunner.execute(
arrayOf("npm", "v", packageName, "versions", "--json", "--registry=$registry")
Expand All @@ -132,7 +127,8 @@ class NPMJSClient(private val project: Project) {
}
}.also { versions ->
if (versions != null) {
log.info("All versions for package $packageName found: $versions")
log.info("All versions for package $packageName found (${versions.size} versions)")
log.debug("Versions for $packageName: $versions")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,10 @@ class PackageUpdateChecker(private val project: Project) {
}

private fun areVersionsMatchingComparatorNeeds(versions: Versions, comparator: String): Boolean {
return if (versions.latest.satisfies(comparator)) {
versions.satisfies == null
} else {
versions.satisfies != null
&& versions.satisfies.satisfies(comparator)
&& isVersionMoreRecentThanComparator(versions.satisfies, comparator)
} && isVersionMoreRecentThanComparator(versions.latest, comparator)
return isVersionMoreRecentThanComparator(versions.latest, comparator) && versions.satisfies?.let { satisfying ->
satisfying.satisfies(comparator)
&& isVersionMoreRecentThanComparator(satisfying, comparator)
} ?: true
}

private fun getVersionExcludingFilter(packageName: String, version: Semver): String? {
Expand Down Expand Up @@ -81,6 +78,9 @@ class PackageUpdateChecker(private val project: Project) {
if (areVersionsMatchingComparatorNeeds(cachedVersions.versions, comparator)) {
log.info("Cached versions for $packageName are still valid, returning them")
return cachedVersions
} else {
log.debug("Cached versions for $packageName are outdated, removing them")
availableUpdates.remove(packageName)
}
}

Expand All @@ -94,7 +94,7 @@ class PackageUpdateChecker(private val project: Project) {
var satisfyingVersion: Semver? = null
val updateAvailable = isVersionMoreRecentThanComparator(newestVersion, comparator)
if (!updateAvailable) {
if (availableUpdates.containsKey(packageName)) {
availableUpdates[packageName]?.let {
availableUpdates.remove(packageName)
}
log.info("No update available for $packageName, removing cached versions")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.Project

@Service(Service.Level.PROJECT)
class RegistriesScanner {
class RegistriesScanner(private val project: Project) {
companion object {
private val log = logger<RegistriesScanner>()

Expand All @@ -17,7 +17,9 @@ class RegistriesScanner {
var registries: List<String> = emptyList()

fun scan() {
val state = NUDState.getInstance(project)
log.info("Starting to scan registries")
state.isScanningForRegistries = true
// 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 ->
Expand All @@ -36,5 +38,6 @@ class RegistriesScanner {
}
}.map { it.removeSuffix("/") }.distinct().toList()
log.info("Found registries: $registries")
state.isScanningForRegistries = false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.github.warningimhack3r.npmupdatedependencies.backend.engine.NUDState
import com.github.warningimhack3r.npmupdatedependencies.ui.statusbar.StatusBarMode
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
import com.intellij.ide.DataManager
import com.intellij.openapi.application.ApplicationNamesInfo
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.fileEditor.FileEditorManager
Expand Down Expand Up @@ -122,7 +123,7 @@ class NUDSettingsComponent {
setCancelOperation {
dialogWrapper.close(DialogWrapper.CANCEL_EXIT_CODE)
}
dialogWrapper.setSize(400, 300) // Width is not really respected, text width takes over
dialogWrapper.setSize(400, 300) // Width is not respected, text width takes over
}

val panel = panel {
Expand All @@ -149,6 +150,19 @@ class NUDSettingsComponent {
.bindSelected(settings::autoReorderDependencies)
}
}
group("Parallelism") {
row("Maximum parallel processes:") {
spinner(1..100)
.comment(
"Control the maximum number of parallel scans that can be run at the same time. Higher values can speed up the scan but might cause performance issues or out of memory issues. Make sure to bump ${
ApplicationNamesInfo.getInstance().fullProductName.substringBefore(
" "
)
}'s memory as needed.<br><strong>100 means no limit.</strong>"
)
.bindIntValue(settings::maxParallelism)
}
}
group("Status Bar") {
lateinit var statusBarEnabled: Cell<JBCheckBox>
row {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ class NUDSettingsState : PersistentStateComponent<NUDSettingsState.Settings> {
set(value) {
settings.autoReorderDependencies = value
}
var maxParallelism: Int
get() = settings.maxParallelism
set(value) {
settings.maxParallelism = value
}
var showStatusBarWidget: Boolean
get() = settings.showStatusBarWidget
set(value) {
Expand All @@ -71,6 +76,7 @@ class NUDSettingsState : PersistentStateComponent<NUDSettingsState.Settings> {
var defaultDeprecationAction: Deprecation.Action = Deprecation.Action.REPLACE,
var showDeprecationBanner: Boolean = true,
var autoReorderDependencies: Boolean = true,
var maxParallelism: Int = 100,
var showStatusBarWidget: Boolean = true,
var statusBarMode: StatusBarMode = StatusBarMode.FULL,
var autoFixOnSave: Boolean = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.github.warningimhack3r.npmupdatedependencies.backend.engine.NPMJSClie
import com.github.warningimhack3r.npmupdatedependencies.backend.engine.NUDState
import com.github.warningimhack3r.npmupdatedependencies.backend.engine.RegistriesScanner
import com.github.warningimhack3r.npmupdatedependencies.backend.extensions.parallelMap
import com.github.warningimhack3r.npmupdatedependencies.settings.NUDSettingsState
import com.github.warningimhack3r.npmupdatedependencies.ui.helpers.AnnotatorsCommon
import com.github.warningimhack3r.npmupdatedependencies.ui.quickfix.DeprecatedDependencyFix
import com.intellij.codeInspection.ProblemHighlightType
Expand All @@ -19,6 +20,7 @@ import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import com.intellij.ui.EditorNotifications
import com.intellij.util.applyIf
import kotlinx.coroutines.delay

class DeprecationAnnotator : DumbAware, ExternalAnnotator<
Pair<Project, List<Property>>,
Expand All @@ -37,21 +39,24 @@ class DeprecationAnnotator : DumbAware, ExternalAnnotator<

var state = NUDState.getInstance(project)
if (!state.isScanningForRegistries && state.packageRegistries.isEmpty()) {
state.isScanningForRegistries = true
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...")
if (state.isScanningForRegistries || state.isScanningForUpdates) {
log.debug("Waiting for registries and/or updates to be scanned...")
while (state.isScanningForRegistries || state.isScanningForUpdates) {
// Wait for the registries to be scanned and avoid multiple scans at the same time
}
}

log.debug("Scanning for deprecations...")
state = NUDState.getInstance(project)
val maxParallelism = NUDSettingsState.instance.maxParallelism
var activeTasks = 0
val npmjsClient = NPMJSClient.getInstance(project)

return info
.also {
// Remove from the cache all deprecations that are no longer in the file
Expand All @@ -62,8 +67,18 @@ class DeprecationAnnotator : DumbAware, ExternalAnnotator<
state.scannedDeprecations = 0
state.isScanningForDeprecations = true
}.parallelMap { property ->
if (maxParallelism < 100) {
while (activeTasks >= maxParallelism) {
// Wait for the active tasks count to decrease
delay(50)
}
activeTasks++
log.debug("Task $activeTasks/$maxParallelism started: ${property.name}")
}
state.deprecations[property.name]?.let { deprecation ->
log.debug("Deprecation found in cache: ${property.name}")
state.scannedDeprecations++
activeTasks--
// If the deprecation is already in the cache, we don't need to check the NPM registry
Pair(property.jsonProperty, deprecation)
} ?: npmjsClient.getPackageDeprecation(property.name)?.let { reason ->
Expand All @@ -72,45 +87,44 @@ class DeprecationAnnotator : DumbAware, ExternalAnnotator<
// Remove punctuation at the end of the word
word.replace(Regex("[,;.]$"), "")
}.filter { word ->
// Try to find a word that looks like a package name
if (word.startsWith("@")) {
// Scoped package
return@filter word.split("/").size == 2
}
if (word.contains("/")) {
// If it contains a slash without being a scoped package, it's likely an URL
return@filter false
with(word) {
// Try to find a word that looks like a package name
when {
// Scoped package
startsWith("@") -> split("/").size == 2
// If it contains a slash without being a scoped package, it's likely an URL
contains("/") -> false
// Other potential matches
contains("-") -> lowercase() == this
// Else if we're unsure, we don't consider it as a package name
else -> false
}
}
// Other potential matches
if (word.contains("-")) {
return@filter word.lowercase() == word
}
// Else if we're unsure, we don't consider it as a package name
false
}.parallelMap innerMap@{ potentialPackage ->
// Confirm that the word is a package name by trying to get its latest version
npmjsClient.getLatestVersion(potentialPackage)?.let {
Pair(potentialPackage, it)
}
}.filterNotNull().also {
state.scannedDeprecations++
}.firstOrNull()?.let { (name, version) ->
}.filterNotNull().firstOrNull()?.let { (name, version) ->
// We found a package name and its latest version, so we can create a replacement
Pair(property.jsonProperty, Deprecation(reason, Deprecation.Replacement(name, version)))
} ?: Pair(
property.jsonProperty,
Deprecation(reason, null)
) // No replacement found in the deprecation reason
}.also { pair ->
pair?.let {
}.also { result ->
result?.let { (property, deprecation) ->
// Add the deprecation to the cache if any
state.deprecations[property.name] = it.second
} ?: run {
state.deprecations[property.name] = deprecation
} ?: state.deprecations[property.name]?.let { _ ->
// Remove the deprecation from the cache if no deprecation is found
if (state.deprecations.containsKey(property.name)) {
state.deprecations.remove(property.name)
}
state.deprecations.remove(property.name)
}

log.debug("Finished task for ${property.name}, deprecation found: ${result != null}")
// Manage counters
state.scannedDeprecations++
activeTasks--
}
}.filterNotNull().toMap().also {
log.debug("Deprecations scanned, ${it.size} found")
Expand Down
Loading

0 comments on commit 1f8bcb7

Please sign in to comment.