Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import org.sonarlint.intellij.analysis.AnalysisSubmitter.Companion.collectContri
import org.sonarlint.intellij.analysis.InferredAnalysisPropertiesProvider.collectContributedExtraProperties
import org.sonarlint.intellij.analysis.InferredAnalysisPropertiesProvider.getConfigurationFromConfiguratorEP
import org.sonarlint.intellij.analysis.LocalFileExclusions
import org.sonarlint.intellij.analysis.OnTheFlyFindingsCoordinator
import org.sonarlint.intellij.analysis.OpenInIdeFindingCache
import org.sonarlint.intellij.analysis.RunningAnalysesTracker
import org.sonarlint.intellij.binding.BindingSuggestionHandler.findOverriddenModules
Expand Down Expand Up @@ -103,6 +104,7 @@ import org.sonarlint.intellij.documentation.SonarLintDocumentation.Intellij.CONN
import org.sonarlint.intellij.documentation.SonarLintDocumentation.Intellij.CONNECTED_MODE_SETUP_LINK
import org.sonarlint.intellij.documentation.SonarLintDocumentation.Intellij.SUPPORT_POLICY_LINK
import org.sonarlint.intellij.documentation.SonarLintDocumentation.Intellij.TROUBLESHOOTING_CONNECTED_MODE_SETUP_LINK
import org.sonarlint.intellij.editor.EditorHighlightRefresh
import org.sonarlint.intellij.finding.Finding
import org.sonarlint.intellij.finding.ShowFinding
import org.sonarlint.intellij.finding.hotspot.LiveSecurityHotspot
Expand Down Expand Up @@ -925,7 +927,7 @@ object SonarLintIntelliJClient : SonarLintRpcClientDelegate {
} ?: return
getService(project, TaintVulnerabilitiesCache::class.java)
.update(closedTaintVulnerabilityIds, locallyMatchedAddedTaintVulnerabilities, locallyMatchedUpdatedTaintVulnerabilities)
getService(project, SonarLintToolWindow::class.java).refreshViews()
getService(project, OnTheFlyFindingsCoordinator::class.java).applyHighlightRefreshAndRefreshPanels(EditorHighlightRefresh.enabled())
}

override fun didChangeDependencyRisks(
Expand All @@ -938,7 +940,7 @@ object SonarLintIntelliJClient : SonarLintRpcClientDelegate {
val added = addedDependencyRisks.map { LocalDependencyRisk(it) }
val updated = updatedDependencyRisks.map { LocalDependencyRisk(it) }
getService(project, DependencyRisksCache::class.java).update(closedDependencyRiskIds, added, updated)
getService(project, SonarLintToolWindow::class.java).refreshViews()
getService(project, OnTheFlyFindingsCoordinator::class.java).applyHighlightRefreshAndRefreshPanels(EditorHighlightRefresh.enabled())
}

override fun raiseIssues(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ import com.intellij.openapi.ui.DoNotAskOption
import com.intellij.openapi.ui.MessageDialogBuilder
import com.intellij.openapi.util.Iconable
import com.intellij.psi.PsiFile
import org.sonarlint.intellij.analysis.OnTheFlyFindingsCoordinator
import org.sonarlint.intellij.common.ui.SonarLintConsole
import org.sonarlint.intellij.common.util.SonarLintUtils.getService
import org.sonarlint.intellij.config.global.ServerConnection
import org.sonarlint.intellij.core.BackendService
import org.sonarlint.intellij.core.ProjectBindingManager
import org.sonarlint.intellij.editor.EditorHighlightRefresh
import org.sonarlint.intellij.finding.Issue
import org.sonarlint.intellij.finding.issue.vulnerabilities.LocalTaintVulnerability
import org.sonarlint.intellij.notifications.SonarLintProjectNotifications
Expand Down Expand Up @@ -149,7 +151,7 @@ class MarkAsResolvedAction(

private fun updateUI(project: Project, issue: Issue) {
issue.resolve()
getService(project, SonarLintToolWindow::class.java).refreshViews()
getService(project, OnTheFlyFindingsCoordinator::class.java).applyHighlightRefreshAndRefreshPanels(EditorHighlightRefresh.enabled())
}

private fun addComment(project: Project, module: Module, issueKey: String, comment: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@ import com.intellij.openapi.ui.MessageDialogBuilder
import com.intellij.openapi.util.Iconable
import com.intellij.psi.PsiFile
import org.sonarlint.intellij.actions.MarkAsResolvedAction.Companion.REVIEW_ISSUE_GROUP
import org.sonarlint.intellij.analysis.OnTheFlyFindingsCoordinator
import org.sonarlint.intellij.common.ui.SonarLintConsole
import org.sonarlint.intellij.common.util.SonarLintUtils
import org.sonarlint.intellij.common.util.SonarLintUtils.getService
import org.sonarlint.intellij.config.global.ServerConnection
import org.sonarlint.intellij.core.BackendService
import org.sonarlint.intellij.core.ProjectBindingManager
import org.sonarlint.intellij.editor.EditorHighlightRefresh
import org.sonarlint.intellij.finding.Issue
import org.sonarlint.intellij.finding.issue.LiveIssue
import org.sonarlint.intellij.finding.issue.vulnerabilities.LocalTaintVulnerability
Expand Down Expand Up @@ -104,7 +106,7 @@ class ReopenIssueAction(private var issue: LiveIssue? = null) : AbstractSonarAct

private fun updateUI(project: Project, issue: Issue) {
issue.reopen()
getService(project, SonarLintToolWindow::class.java).refreshViews()
getService(project, OnTheFlyFindingsCoordinator::class.java).applyHighlightRefreshAndRefreshPanels(EditorHighlightRefresh.enabled())
}

private fun confirm(project: Project, productName: String): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import javax.annotation.Nullable;
import javax.swing.SwingUtilities;
import org.sonarlint.intellij.analysis.AnalysisResult;
import org.sonarlint.intellij.analysis.OnTheFlyFindingsCoordinator;
import org.sonarlint.intellij.editor.EditorHighlightRefresh;
import org.sonarlint.intellij.finding.Finding;
import org.sonarlint.intellij.finding.ShowFinding;
Expand All @@ -40,6 +41,11 @@
import org.sonarlint.intellij.notifications.SonarLintProjectNotifications;
import org.sonarlint.intellij.ui.ToolWindowConstants;
import org.sonarlint.intellij.ui.currentfile.CurrentFilePanel;
import org.sonarlint.intellij.ui.filter.FilterCriteria;
import org.sonarlint.intellij.ui.filter.FilterSettingsService;
import org.sonarlint.intellij.ui.filter.SeverityFilter;
import org.sonarlint.intellij.ui.filter.SeverityImpactFilter;
import org.sonarlint.intellij.ui.filter.StatusFilter;
import org.sonarlint.intellij.ui.report.ReportPanel;
import org.sonarlint.intellij.ui.report.ReportTabManager;

Expand Down Expand Up @@ -117,11 +123,7 @@ public void openLogTab() {
}

public void refreshViews() {
refreshViews(EditorHighlightRefresh.enabled());
}

public void refreshViews(EditorHighlightRefresh highlightRefresh) {
this.<CurrentFilePanel>updateCurrentFileTab(panel -> panel.refreshView(highlightRefresh));
this.<CurrentFilePanel>updateCurrentFileTab(panel -> panel.refreshView(EditorHighlightRefresh.enabled()));
var toolWindow = getToolWindow();
if (toolWindow != null) {
runOnUiThread(project, () -> {
Expand Down Expand Up @@ -163,9 +165,32 @@ private static void selectTab(ToolWindow toolWindow, String tabId) {
}
}

public void updateCurrentFileTab(@Nullable VirtualFile selectedFile, EditorHighlightRefresh highlightRefresh) {

public FilterCriteria getCurrentFileFilterCriteria() {
var toolWindow = getToolWindow();
if (toolWindow != null) {
var content = toolWindow.getContentManager().findContent(CURRENT_FILE_TAB_TITLE);
if (content != null && content.getComponent() instanceof CurrentFilePanel panel) {
return panel.getCurrentFilterCriteria();
}
}
return getDefaultCurrentFileFilterCriteria();
}

private FilterCriteria getDefaultCurrentFileFilterCriteria() {
return new FilterCriteria(
new SeverityImpactFilter.Severity(SeverityFilter.NO_FILTER),
StatusFilter.OPEN,
"",
false,
false,
getService(FilterSettingsService.class).getDefaultFindingsScope()
);
}

public void updateCurrentFileTab(@Nullable VirtualFile selectedFile) {
this.<CurrentFilePanel>updateCurrentFileTab(
panel -> runOnUiThread(project, () -> panel.update(selectedFile, highlightRefresh)));
panel -> runOnUiThread(project, () -> panel.update(selectedFile, EditorHighlightRefresh.NONE)));
}

public void showFindingDescription(Finding liveIssue) {
Expand Down Expand Up @@ -321,7 +346,7 @@ public void bringToFront() {

@Override
public void bindingChanged() {
refreshViews();
getService(project, OnTheFlyFindingsCoordinator.class).applyHighlightRefreshAndRefreshPanels(EditorHighlightRefresh.enabled());
}

}
32 changes: 16 additions & 16 deletions src/main/java/org/sonarlint/intellij/analysis/AnalysisState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ import org.sonarlint.intellij.util.VirtualFileUtils.uriToVirtualFile
import org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.RaisedHotspotDto
import org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto

class AnalysisState(
class AnalysisState @JvmOverloads constructor(
val id: UUID,
private val analysisCallback: AnalysisCallback,
private val module: Module
private val module: Module,
private val submittedFiles: Collection<VirtualFile> = emptyList(),
) {
private val modificationStampByFile = ConcurrentHashMap<VirtualFile, Long>()
private val analysisDate: Instant = Instant.now()
Expand All @@ -63,19 +64,7 @@ class AnalysisState(
}
})

if (isAnalysisFinished()) {
analysisCallback.onSuccess(
AnalysisResult(
analysisId,
LiveFindings(liveIssues, liveHotspots),
liveHotspots.keys,
analysisDate
)
)
getService(module.project, RunningAnalysesTracker::class.java).finish(this)
} else {
analysisCallback.onIntermediateResult(AnalysisIntermediateResult(LiveFindings(liveIssues, liveHotspots)))
}
notifyIfFinished(analysisId)
}

fun addRawIssues(analysisId: UUID, issuesByFile: Map<URI, List<RaisedIssueDto>>, isIntermediate: Boolean) {
Expand All @@ -91,12 +80,16 @@ class AnalysisState(
}
})

notifyIfFinished(analysisId)
}

private fun notifyIfFinished(analysisId: UUID) {
if (isAnalysisFinished()) {
analysisCallback.onSuccess(
AnalysisResult(
analysisId,
LiveFindings(liveIssues, liveHotspots),
liveIssues.keys,
analyzedFilesForResult(),
analysisDate
)
)
Expand All @@ -106,6 +99,13 @@ class AnalysisState(
}
}

/**
* Files this analysis actually covered. When the submitter did not pass a set (full-project / VCS-changed),
* fall back to the union of findings keys so report tabs keep today's behaviour.
*/
private fun analyzedFilesForResult(): Collection<VirtualFile> =
submittedFiles.ifEmpty { liveIssues.keys + liveHotspots.keys }

private fun convertRawHotspots(virtualFile: VirtualFile, rawHotspots: Collection<RaisedHotspotDto>): Collection<LiveSecurityHotspot> {
try {
return rawHotspots.mapNotNull { hotspot ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package org.sonarlint.intellij.analysis
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.Service
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.project.Project
Expand Down Expand Up @@ -95,10 +96,12 @@ class AnalysisSubmitter(private val project: Project) {
runOnPooledThread(project) {
val callback = UpdateOnTheFlyFindingsCallable(onTheFlyFindingsHolder)
val modules = ModuleManager.getInstance(project).modules
val openFiles = FileEditorManager.getInstance(project).openFiles.toList()
modules.forEach { module ->
getService(BackendService::class.java).analyzeOpenFiles(module).thenAccept { response ->
response.analysisId?.let { analysisId ->
val analysisState = AnalysisState(analysisId, callback, module)
val moduleOpenFiles = openFiles.filter { findModuleForFile(it, project) == module }
val analysisState = AnalysisState(analysisId, callback, module, moduleOpenFiles)
getService(project, RunningAnalysesTracker::class.java).track(analysisState)
}
}
Expand All @@ -123,7 +126,7 @@ class AnalysisSubmitter(private val project: Project) {
try {
future[5, TimeUnit.SECONDS].analysisId?.let { analysisId ->
console.debug("Pre-commit: analyzeFileList returned analysisId=$analysisId in ${System.currentTimeMillis() - startTime} ms")
getService(project, RunningAnalysesTracker::class.java).track(AnalysisState(analysisId, callback, module))
getService(project, RunningAnalysesTracker::class.java).track(AnalysisState(analysisId, callback, module, files))
analysisIds.add(analysisId)
} ?: console.debug("Pre-commit: analyzeFileList returned no analysisId in ${System.currentTimeMillis() - startTime} ms")
} catch (e: TimeoutException) {
Expand Down Expand Up @@ -190,7 +193,7 @@ class AnalysisSubmitter(private val project: Project) {
.thenAccept { response ->
response.analysisId?.let { analysisId ->
getService(project, AnalysisStatus::class.java).tryRun(analysisId)
val analysisState = AnalysisState(analysisId, callback, module)
val analysisState = AnalysisState(analysisId, callback, module, files)
getService(project, RunningAnalysesTracker::class.java).track(analysisState)
}
taskState?.trackTask(module, response.analysisId?.toString())
Expand All @@ -212,7 +215,7 @@ class AnalysisSubmitter(private val project: Project) {
response.analysisId?.let { analysisId ->
getService(project, OpenInIdeFindingCache::class.java).finding = null
getService(project, OpenInIdeFindingCache::class.java).analysisQueued = false
val analysisState = AnalysisState(analysisId, callback, module)
val analysisState = AnalysisState(analysisId, callback, module, listOf(showFinding.file))
getService(project, RunningAnalysesTracker::class.java).track(analysisState)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* SonarLint for IntelliJ IDEA
* Copyright (C) SonarSource Sàrl
* sonarlint@sonarsource.com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonarlint.intellij.analysis

import com.intellij.openapi.components.Service
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import org.sonarlint.intellij.actions.SonarLintToolWindow
import org.sonarlint.intellij.common.util.SonarLintUtils
import org.sonarlint.intellij.common.util.SonarLintUtils.getService
import org.sonarlint.intellij.editor.CodeAnalyzerRestarter
import org.sonarlint.intellij.editor.EditorHighlightRefresh
import org.sonarlint.intellij.ui.currentfile.CurrentFileDisplayedFindingsRefresher

/**
* Resolves an [EditorHighlightRefresh] into a set of files, refreshes the filtered findings snapshot used by
* [org.sonarlint.intellij.editor.DirectHighlighter], and triggers editor markup refresh independently of the
* Current File tool window UI lifecycle.
*
* The [OnTheFlyFindingsHolder] owns the unfiltered per-open-file maps. Editor squiggles follow the Current File tab
* filters via [org.sonarlint.intellij.ui.currentfile.CurrentFileDisplayedFindingsStore]. [SonarLintToolWindow.refreshViews]
* rebuilds panels only; callers that need markup plus panels use [applyHighlightRefreshAndRefreshPanels].
*/
Comment thread
gitar-bot[bot] marked this conversation as resolved.
@Service(Service.Level.PROJECT)
class OnTheFlyFindingsCoordinator(private val project: Project) {

fun applyHighlightRefresh(highlightRefresh: EditorHighlightRefresh) {
if (!highlightRefresh.enabled || project.isDisposed) {
return
}
val files = resolveFiles(highlightRefresh).filter { it.isValid }
if (files.isEmpty()) {
return
}
getService(project, CurrentFileDisplayedFindingsRefresher::class.java)
.refreshDisplayedFindings(SonarLintUtils.getSelectedFile(project))
getService(project, CodeAnalyzerRestarter::class.java).refreshFiles(files)
}

/**
* Refreshes editor markup, then rebuilds tool-window panels. Use when findings caches changed outside the
* holder's analysis publish path (taints, CAYC, resolve actions, binding changes).
*/
fun applyHighlightRefreshAndRefreshPanels(highlightRefresh: EditorHighlightRefresh) {
applyHighlightRefresh(highlightRefresh)
if (!project.isDisposed) {
getService(project, SonarLintToolWindow::class.java).refreshViews()
}
}
Comment on lines +62 to +67

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Performance: Highlight refresh and filtering run twice per resolve/CAYC/binding event

applyHighlightRefreshAndRefreshPanels calls applyHighlightRefresh (snapshot rebuild + refreshFiles) and then refreshViews(), which now calls panel.refreshView(EditorHighlightRefresh.enabled()) whose finally block calls applyHighlightRefresh again — so every mark-as-resolved, reopen, CAYC toggle and binding change re-filters all findings and re-queues markup twice, the second time on the EDT. Pass EditorHighlightRefresh.NONE from refreshViews() (the coordinator already handled markup) and keep the panel's highlight trigger for callers that only rebuild the panel.

Let the coordinator own the markup pass so panel rebuilds do not duplicate it:

public void refreshViews() {
  this.<CurrentFilePanel>updateCurrentFileTab(panel -> panel.refreshView(EditorHighlightRefresh.NONE));
  • Apply fix

Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎


private fun resolveFiles(highlightRefresh: EditorHighlightRefresh): Collection<VirtualFile> {
return when {
highlightRefresh.allOpenFiles -> openEditors()
highlightRefresh.changedFiles != null -> highlightRefresh.changedFiles
else -> openEditors()
}
}

private fun openEditors(): List<VirtualFile> =
FileEditorManager.getInstance(project).openFiles.toList()
}
Loading
Loading