Skip to content

IntelliJ stability update#50

Draft
lventura-codacy wants to merge 1 commit intomasterfrom
stability-update
Draft

IntelliJ stability update#50
lventura-codacy wants to merge 1 commit intomasterfrom
stability-update

Conversation

@lventura-codacy
Copy link
Contributor

No description provided.

Copilot AI review requested due to automatic review settings March 10, 2026 04:09
@codacy-production
Copy link

Codacy's Analysis Summary

0 new issue (≤ 1 medium issue)
0 new security issue (≤ 0 issue)
0 complexity
More details

AI Reviewer: first review requested successfully. AI can make mistakes. Always validate suggestions.

Run reviewer

TIP This summary will be updated as you push new changes. Give us feedback

Copy link

@codacy-production codacy-production bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR improves IntelliJ plugin stability by refining coroutine lifecycles and service registration. However, several critical issues must be addressed before merging. Specifically, there are thread-safety risks in the global scope management within StartupListener.kt and a remaining race condition in RepositoryManager.kt initialization.

Furthermore, the implementation of API error handling in Api.kt contradicts the objective of providing meaningful error messages, as it currently wraps all failures in generic exceptions, obscuring the root cause. While the PR aims for better resource management, the continued use of GlobalScope in several files undermines the transition to structured concurrency.

About this PR

  • Systemic issue: GlobalScope.launch is still used in RepositoryManager.kt and Config.kt. This bypasses the structured concurrency improvements introduced in StartupListener.kt and can lead to leaked coroutines when the plugin is unloaded.
  • The use of println in Api.kt (line 38) is inconsistent with the use of a Logger elsewhere in the codebase. Replace with a standard logger for consistency and better log management.
  • The PR description is empty. Please provide context regarding why these specific stability changes were prioritized and any potential side effects considered.

Suggestions for missing tests

  • Verify that closing a project triggers the Disposer to cancel the specific CoroutineScope associated with that project.
  • Verify that unloading the plugin (DynamicPluginListener) invokes StartupListener.cancelAllScopes() and successfully terminates pending tasks.
  • Verify that Api.makeRequest throws an exception containing the original cause when a network error occurs, instead of returning a default instance.
  • Verify that RepositoryManager.open sets the currentRepository state before launching the background metadata fetching task.
Prompt proposal for missing tests
Consider implementing these tests if applicable:
1. Verify that closing a project triggers the Disposer to cancel the specific CoroutineScope associated with that project.
2. Verify that unloading the plugin (DynamicPluginListener) invokes StartupListener.cancelAllScopes() and successfully terminates pending tasks.
3. Verify that Api.makeRequest throws an exception containing the original cause when a network error occurs, instead of returning a default instance.
4. Verify that RepositoryManager.open sets the currentRepository state before launching the background metadata fetching task.

🗒️ Improve review quality by adding custom instructions

throw Exception("Failed to fetch data: HTTP response code $responseCode")
}
}
catch (e: Exception) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 HIGH RISK

Re-throwing a generic Exception obscures the actual failure type (e.g., IOException or JsonSyntaxException). Refactor makeRequest to catch and handle specific exceptions or wrap them in a custom CodacyApiException to preserve context for callers.

See Issue in Codacy

}

if (currentRepository != gitRepository) {
currentRepository = gitRepository

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM RISK

While moving the currentRepository assignment helps, it is still not atomic across different threads. Concurrent calls to open (e.g., from both startup and git events) can result in redundant background initialization tasks. Use a Mutex or AtomicReference to synchronize this check-and-set operation.


companion object {
private const val MIN_REFRESH_INTERVAL_MS = 5000L
private val activeScopes = mutableListOf<CoroutineScope>()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM RISK

The activeScopes list is a shared mutable list in a companion object accessed by multiple project-level coroutines. Using a non-thread-safe ArrayList can lead to ConcurrentModificationException during project disposal or plugin unloading.

Suggested change
private val activeScopes = mutableListOf<CoroutineScope>()
private val activeScopes = java.util.Collections.synchronizedList(mutableListOf<CoroutineScope>())

Logger.info("Fetched ${enabledBranches.size} enabled branches")
} catch (e: Exception) {
Logger.error("Failed to fetch enabled branches: ${e.message}")
Logger.warn("Failed toAf fetch enabled branches: ${e.message}")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ LOW RISK

Nitpick: Typo in log message: 'toAf' should be 'to'.

Suggested change
Logger.warn("Failed toAf fetch enabled branches: ${e.message}")
Logger.warn("Failed to fetch enabled branches: ${e.message}")

See Issue in Codacy

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR focuses on improving IntelliJ plugin stability by migrating core components to IntelliJ services (instead of manual instantiation) and by introducing lifecycle-managed coroutine scopes to reduce background-work leaks during project disposal / plugin unload.

Changes:

  • Replace Api() / Config() instantiations with IntelliJ service lookups (service<Api>(), Config.instance).
  • Introduce a per-project CoroutineScope in StartupListener, cancel it on project dispose, and cancel all scopes on plugin unload.
  • Update logging severity and bump plugin version to 0.1.0 with a changelog entry.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/main/kotlin/com/codacy/intellij/plugin/services/git/RepositoryManager.kt Uses DI for Api/Config, minor flow adjustment in repo open, logging changes.
src/main/kotlin/com/codacy/intellij/plugin/services/git/PullRequest.kt Uses DI for Api.
src/main/kotlin/com/codacy/intellij/plugin/services/common/Config.kt Uses DI Api when fetching user profile after storing token.
src/main/kotlin/com/codacy/intellij/plugin/services/api/Api.kt Declares Api as an APP-level service; makeRequest now throws instead of returning default instances; logging tweak.
src/main/kotlin/com/codacy/intellij/plugin/listeners/StartupListener.kt Adds project-tied coroutine scope, cancels on dispose/unload, avoids GlobalScope, adds initial repository open.
src/main/kotlin/com/codacy/intellij/plugin/listeners/PluginStateListener.kt Cancels all tracked scopes on plugin unload.
gradle.properties Version bump to 0.1.0.
CHANGELOG.md Adds 0.1.0 entry.
Comments suppressed due to low confidence (1)

src/main/kotlin/com/codacy/intellij/plugin/listeners/StartupListener.kt:129

  • initializeGit throws IllegalStateException (and uses !! on remote.firstUrl) when no remote/URL is present. Since this runs inside the GitRepository change listener, it can surface as an unexpected runtime exception during normal IDE usage (e.g., newly inited repo with no remote). Handle this case gracefully (log + return early / skip initialization) instead of throwing.
        val remote = gitRepository?.remotes?.firstOrNull()
            ?: throw IllegalStateException("No remote found in the Git repository")

        val gitInfo = GitRemoteParser.parseGitRemote(remote.firstUrl!!)


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 77 to 80
ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Opening repository", false) {
override fun run(indicator: com.intellij.openapi.progress.ProgressIndicator) {
GlobalScope.launch {
currentRepository = gitRepository
try {
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This launches work in GlobalScope inside a project-scoped service, so the job will outlive the project/plugin unload and cannot be cancelled (potential leak / background work after dispose). Prefer launching on a structured CoroutineScope tied to the project lifecycle (e.g., a scope stored in the service and cancelled on dispose, or passed in from StartupListener) instead of GlobalScope.

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +36
private val activeScopes = mutableListOf<CoroutineScope>()

fun cancelAllScopes() {
activeScopes.forEach { it.cancel() }
activeScopes.clear()
}
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

activeScopes is a mutableList accessed from multiple lifecycle callbacks (runActivity, project dispose, beforePluginUnload). As written, cancelAllScopes() iterates while other code may remove scopes, which can throw ConcurrentModificationException. Use a thread-safe collection (e.g., CopyOnWriteArrayList / synchronizedList) or synchronize access around add/remove/iterate/clear.

Copilot uses AI. Check for mistakes.
Comment on lines +77 to 79
scope.launch {
service<Api>().listTools()
}
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Api.listTools() can now throw (Api.makeRequest throws on failures). This launch block doesn't handle exceptions, so failures will be reported as uncaught coroutine exceptions in the IDE logs. Wrap this call in try/catch (and log at warn) or use runCatching so startup remains quiet/stable when the API is unavailable.

Copilot uses AI. Check for mistakes.

### Changed

- Stability update
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changelog entry has a trailing space after "Stability update"; consider trimming it to keep the changelog clean.

Suggested change
- Stability update
- Stability update

Copilot uses AI. Check for mistakes.
Logger.info("Fetched ${enabledBranches.size} enabled branches")
} catch (e: Exception) {
Logger.error("Failed to fetch enabled branches: ${e.message}")
Logger.warn("Failed toAf fetch enabled branches: ${e.message}")
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Log message has a typo ("Failed toAf fetch enabled branches"); please correct it so logs are searchable and readable.

Suggested change
Logger.warn("Failed toAf fetch enabled branches: ${e.message}")
Logger.warn("Failed to fetch enabled branches: ${e.message}")

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants