From 3eabd0ac8a53df8a4905b73a7317a6a53a08c100 Mon Sep 17 00:00:00 2001 From: Moncef Slimani Date: Fri, 1 Aug 2025 15:18:07 +0100 Subject: [PATCH] [threading] IJPL-179707: Added compose compiler dependency GitOrigin-RevId: 10e5d3a83036bf64046af77c3fb6669c59e7e0ab --- .../resources/intellij.devkit.core.xml | 20 ++++---- .../threadingModelHelper/LockReqsAnalyzer.kt | 29 ++++++++---- .../threadingModelHelper/LockReqsCompose.kt | 47 +++++++++++++++++++ .../threadingModelHelper/LockReqsService.kt | 23 ++++++--- .../LockReqsToolWindowFactory.kt | 30 +++++++++--- .../RealWorldInsideUI.java | 30 ++++++++++++ 6 files changed, 146 insertions(+), 33 deletions(-) create mode 100644 plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsCompose.kt create mode 100644 plugins/devkit/devkit-java-tests/testData/threadingModelHelper/RealWorldInsideUI.java diff --git a/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml b/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml index 48f4c4aec75c..8be46228092a 100644 --- a/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml +++ b/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml @@ -8,13 +8,15 @@ - - - - - - - + intellijPlatform { + bundledModule("intellij.platform.jewel.foundation") + bundledModule("intellij.platform.jewel.ui") + bundledModule("intellij.platform.jewel.ideLafBridge") + bundledModule("intellij.platform.jewel.markdown.core") + bundledModule("intellij.platform.jewel.markdown.ideLafBridgeStyling") + bundledModule("intellij.libraries.compose.foundation.desktop") + bundledModule("intellij.libraries.skiko") + } messages.DevKitBundle @@ -740,8 +742,8 @@ diff --git a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsAnalyzer.kt b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsAnalyzer.kt index 8cb4531954d4..d8f3b305effe 100644 --- a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsAnalyzer.kt +++ b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsAnalyzer.kt @@ -15,9 +15,10 @@ class LockReqsAnalyzer { private const val REQUIRES_READ_LOCK_ANNOTATION = "com.intellij.util.concurrency.annotations.RequiresReadLock" private const val MAX_PATH_DEPTH = 1000 + private val ASYNC_METHODS = setOf("invokeLater", "invokeAndWait", "runInEdt") + enum class LockCheckType { - ANNOTATION, - ASSERTION + ANNOTATION, ASSERTION } data class LockRequirement( @@ -32,12 +33,13 @@ class LockReqsAnalyzer { val pathString: String get() = buildString { append(methodChain.joinToString(" -> ") { - "${it.containingClass?.name}.${it.name}" + - when (lockRequirement.type) { - LockCheckType.ANNOTATION -> "@RequiresReadLock" - LockCheckType.ASSERTION -> "ThreadingAssertions.assertReadAccess()" - } + "${it.containingClass?.name}.${it.name}" }) + append(" -> ") + when (lockRequirement.type) { + LockCheckType.ANNOTATION -> append("@RequiresReadLock") + LockCheckType.ASSERTION -> append("ThreadingAssertions.assertReadAccess()") + } } } } @@ -64,10 +66,18 @@ class LockReqsAnalyzer { findLockChecks(method).forEach { check -> paths.add(ExecutionPath(currentPath.toList(), LockRequirement(check, method))) } - getMethodCallees(method).forEach { callee -> processMethodDFS(callee, currentPath, paths) } + getMethodCallees(method).forEach { + if (!isAsyncMethod(it)) { + processMethodDFS(it, currentPath, paths) + } + } currentPath.removeAt(currentPath.lastIndex) } + private fun isAsyncMethod(method: PsiMethod): Boolean { + return method.name in ASYNC_METHODS + } + private fun getMethodCallees(method: PsiMethod): List { val callees = mutableListOf() method.body?.accept(object : JavaRecursiveElementVisitor() { @@ -96,8 +106,7 @@ class LockReqsAnalyzer { } private fun isAssertReadAccess(expression: PsiMethodCallExpression): Boolean { - return ASSERT_READ_ACCESS_METHOD == expression.methodExpression.referenceName && - THREADING_ASSERTIONS_CLASS == expression.resolveMethod()?.containingClass?.qualifiedName + return ASSERT_READ_ACCESS_METHOD == expression.methodExpression.referenceName && THREADING_ASSERTIONS_CLASS == expression.resolveMethod()?.containingClass?.qualifiedName } private fun hasAssertReadAccessCall(method: PsiMethod): Boolean { diff --git a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsCompose.kt b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsCompose.kt new file mode 100644 index 000000000000..c74447b953f0 --- /dev/null +++ b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsCompose.kt @@ -0,0 +1,47 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.idea.devkit.threadingModelHelper + + +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.text.input.rememberTextFieldState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import java.util.Locale +import org.jetbrains.jewel.bridge.LocalComponent +import org.jetbrains.jewel.foundation.modifier.trackActivation +import org.jetbrains.jewel.foundation.modifier.trackComponentActivation +import org.jetbrains.jewel.foundation.theme.JewelTheme +import org.jetbrains.jewel.ui.Orientation +import org.jetbrains.jewel.ui.component.Divider +import org.jetbrains.jewel.ui.component.Text +import org.jetbrains.jewel.ui.component.TextArea +import org.jetbrains.jewel.ui.component.VerticallyScrollableContainer +import org.jetbrains.jewel.ui.component.scrollbarContentSafePadding + +import com.intellij.openapi.project.Project + +@Composable +fun LockReqsCompose(project: Project) { + Column() { + Text("Working now?") + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsService.kt b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsService.kt index c9933ecdd6fd..f75a64d8f7a8 100644 --- a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsService.kt +++ b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsService.kt @@ -8,14 +8,23 @@ import com.intellij.psi.PsiMethod @Service(Service.Level.PROJECT) class LockReqsService(private val project: Project) { - private var currentResults: List = emptyList() + + companion object { + const val TOOL_WINDOW_ID: String = "Locking Requirements" + } + + private var _currentResults: List = emptyList() + val currentResults: List + get() = _currentResults + + var onResultsUpdated: (() -> Unit)? = null fun updateResults(method: PsiMethod) { val analyzer = LockReqsAnalyzer() - val paths = analyzer.analyzeMethod(method).map { it.pathString } - currentResults = paths - ToolWindowManager.getInstance(project).getToolWindow("LockReqsToolWindow")?.show() - } + val paths = analyzer.analyzeMethod(method) + _currentResults = paths.map { it.pathString } + onResultsUpdated?.invoke() - fun getCurrentResults(): List = currentResults -} + ToolWindowManager.getInstance(project).getToolWindow(TOOL_WINDOW_ID)?.show() + } +} \ No newline at end of file diff --git a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsToolWindowFactory.kt b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsToolWindowFactory.kt index 688808e9da4f..1c45469218b3 100644 --- a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsToolWindowFactory.kt +++ b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsToolWindowFactory.kt @@ -1,20 +1,36 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.idea.devkit.threadingModelHelper -import androidx.compose.runtime.Composable import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import com.intellij.openapi.wm.ToolWindow import com.intellij.openapi.wm.ToolWindowFactory -import org.jetbrains.idea.devkit.DevKitBundle -import org.jetbrains.jewel.bridge.addComposeTab -import org.jetbrains.jewel.ui.component.Text +import com.intellij.ui.components.JBScrollPane +import com.intellij.ui.table.JBTable +import javax.swing.JPanel +import javax.swing.table.DefaultTableModel +import java.awt.BorderLayout class LockReqsToolWindowFactory : ToolWindowFactory { + override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { val service = project.service() - toolWindow.addComposeTab(DevKitBundle.message("tab.title.locking.requirements")) { - LockReqsToolWindow(service) - } + + val tableModel = DefaultTableModel(arrayOf("Execution Path"), 0) + val panel = JPanel(BorderLayout()) + val table = JBTable(tableModel) + val scrollPane = JBScrollPane(table) + panel.add(scrollPane) + + service.onResultsUpdated = { loadData(service.currentResults, tableModel) } + val factory = toolWindow.contentManager.factory + val content = factory.createContent(panel, null, false) + toolWindow.contentManager.addContent(content) + } + + private fun loadData(results: List, tableModel: DefaultTableModel) { + tableModel.rowCount = 0 + results.forEach { tableModel.addRow(arrayOf(it)) } } } + diff --git a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/RealWorldInsideUI.java b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/RealWorldInsideUI.java new file mode 100644 index 000000000000..c1f8df4225e4 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/RealWorldInsideUI.java @@ -0,0 +1,30 @@ +package org.jetbrains.idea.devkit.threadingModelHelper; +import com.intellij.util.concurrency.ThreadingAssertions; +import com.intellij.util.concurrency.annotations.RequiresReadLock; +import testutils.ExpectedPath; + +@ExpectedPath("RealWorldUI.testMethod -> Helper.helperMethod -> Service.serviceMethod -> @RequiresReadLock") +@ExpectedPath("RealWorldUI.testMethod -> Helper.helperMethod -> Service.serviceMethod -> ThreadingAssertions.assertReadAccess()") +class RealWorldInsideUI { + void testMethod() { + Helper helper = new Helper(); + helper.helperMethod(); + } + + class Helper { + void helperMethod() { + Service service = new Service(); + service.serviceMethod(); + } + } + + class Service { + @RequiresReadLock + void serviceMethod() { + ThreadingAssertions.assertReadAccess(); + } + } +} + + +