diff --git a/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml b/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml index 43a137e106fa..afc6a940cbf9 100644 --- a/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml +++ b/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml @@ -13,8 +13,6 @@ - - messages.DevKitBundle diff --git a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsAnalyzer.kt b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsAnalyzer.kt deleted file mode 100644 index c875d59f345b..000000000000 --- a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsAnalyzer.kt +++ /dev/null @@ -1,129 +0,0 @@ -// 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 com.intellij.psi.* -import com.intellij.psi.search.searches.OverridingMethodsSearch -import com.intellij.psi.search.searches.ClassInheritorsSearch - - -class LockReqsAnalyzer(private val detector: LockReqsDetector = LockReqsDetector()) { - - private data class TraversalContext( - val config: AnalysisConfig, - val currentPath: MutableList = mutableListOf(), - val visited: MutableSet = mutableSetOf(), - val paths: MutableSet = mutableSetOf(), - val messageBusTopics: MutableSet = mutableSetOf(), - val swingComponents: MutableSet = mutableSetOf(), - ) - - fun analyzeMethod(method: PsiMethod, config: AnalysisConfig = AnalysisConfig.forProject(method.project)): AnalysisResult { - val context = TraversalContext(config) - traverseMethod(method, context) - return AnalysisResult(method, context.paths, context.messageBusTopics, context.swingComponents) - } - - private fun traverseMethod(method: PsiMethod, context: TraversalContext, isPolymorphic: Boolean = false) { - if (context.currentPath.size >= context.config.maxDepth) return - val signature = MethodSignature.fromMethod(method) - if (signature in context.visited) return - context.visited.add(signature) - context.currentPath.add(MethodCall(method, isPolymorphic)) - - val annotationRequirements = detector.findAnnotationRequirements(method) - annotationRequirements.forEach { context.paths.add(ExecutionPath(context.currentPath.toList(), it)) } - processMethodBody(method, context) - context.currentPath.removeLast() - } - - - private fun processMethodBody(method: PsiMethod, context: TraversalContext) { - val body = method.body ?: return - val localRequirements = mutableListOf() - - body.accept(object : JavaRecursiveElementVisitor() { - override fun visitMethodCallExpression(expression: PsiMethodCallExpression) { - super.visitMethodCallExpression(expression) - - val resolvedMethod = expression.resolveMethod() ?: return - localRequirements += detector.findBodyRequirements(resolvedMethod) - if (localRequirements.any { it.requirementReason == RequirementReason.SWING_COMPONENT }) { - context.swingComponents.add(MethodSignature.fromMethod(resolvedMethod)) - } - handleMethodCall(resolvedMethod, expression, context) - } - - override fun visitMethodReferenceExpression(expression: PsiMethodReferenceExpression) { - super.visitMethodReferenceExpression(expression) - (expression.resolve() as? PsiMethod)?.let { resolvedMethod -> - localRequirements += detector.findBodyRequirements(resolvedMethod) - if (!detector.isAsyncBoundary(resolvedMethod)) traverseMethod(resolvedMethod, context) - } - } - - override fun visitNewExpression(expression: PsiNewExpression) { - super.visitNewExpression(expression) - expression.resolveMethod()?.let { resolvedMethod -> - if (!detector.isAsyncBoundary(resolvedMethod)) traverseMethod(resolvedMethod, context) - } - } - }) - - localRequirements.forEach { requirement -> - val path = ExecutionPath(context.currentPath.toList(), requirement, - context.currentPath.any { it.isPolymorphic || it.isMessageBusCall }) - context.paths.add(path) - } - } - - private fun handleMethodCall(method: PsiMethod, expression: PsiMethodCallExpression, context: TraversalContext) { - when { - context.config.includeMessageBus && detector.isMessageBusCall(expression) -> handleMessageBusCall(method, expression, context) - context.config.includePolymorphic && detector.isPolymorphicCall(method) -> handlePolymorphicCall(method, context) - else -> traverseMethod(method, context) - } - } - - private fun handleMessageBusCall(method: PsiMethod, expression: PsiMethodCallExpression, context: TraversalContext) { - detector.extractMessageBusTopic(expression)?.let { context.messageBusTopics.add(it) } - if (method.name != "syncPublisher") return - - val topicType = method.returnType as? PsiClassType ?: return - val topicInterface = topicType.resolve() ?: return - if (!topicInterface.isInterface) return - - findTopicListeners(topicInterface, context.config).forEach { listener -> - listener.methods.forEach { method -> traverseMethod(method, context) } - } - } - - - private fun handlePolymorphicCall(method: PsiMethod, context: TraversalContext) { - val implementations = findImplementations(method, context.config) - - if (implementations.size > context.config.maxImplementations) { - val requirement = LockRequirement(method, LockType.READ, RequirementReason.IMPLICIT) - context.currentPath.add(MethodCall(method, isPolymorphic = true)) - context.paths.add(ExecutionPath(context.currentPath.toList(), requirement, true)) - return - } - implementations.forEach { traverseMethod(it, context) } - } - - private fun findImplementations(method: PsiMethod, config: AnalysisConfig): List { - val implementations = mutableListOf() - if (method.body != null) implementations.add(method) - - if (method.hasModifierProperty(PsiModifier.ABSTRACT) || method.containingClass?.isInterface == true) { - val query = OverridingMethodsSearch.search(method, config.scope, true) - implementations.addAll(query.findAll().take(config.maxImplementations)) - } - return implementations - } - - - private fun findTopicListeners(topicInterface: PsiClass, config: AnalysisConfig): List { - val query = ClassInheritorsSearch.search(topicInterface, config.scope, true) - return query.findAll().take(config.maxImplementations).toList() - } -} \ No newline at end of file diff --git a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsAnalyzerDFS.kt b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsAnalyzerDFS.kt new file mode 100644 index 000000000000..ef5384a1b8ac --- /dev/null +++ b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsAnalyzerDFS.kt @@ -0,0 +1,114 @@ +// 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 com.intellij.psi.PsiMethod +import com.intellij.psi.JavaRecursiveElementVisitor +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiClassType +import com.intellij.psi.PsiMethodCallExpression +import com.intellij.psi.PsiMethodReferenceExpression +import com.intellij.psi.PsiModifier +import com.intellij.psi.PsiNewExpression +import com.intellij.psi.search.searches.OverridingMethodsSearch +import com.intellij.util.Processor + + +class LockReqsAnalyzerDFS(private val detector: LockReqsDetector = LockReqsDetector()) { + + private data class TraversalContext( + val config: AnalysisConfig, + val currentPath: MutableList = mutableListOf(), + val visited: MutableSet = mutableSetOf(), + val paths: MutableSet = mutableSetOf(), + val messageBusTopics: MutableSet = mutableSetOf(), + val swingComponents: MutableSet = mutableSetOf(), + ) + + fun analyzeMethod(method: PsiMethod, config: AnalysisConfig = AnalysisConfig.forProject(method.project)): AnalysisResult { + val context = TraversalContext(config) + traverseMethod(method, context) + return AnalysisResult(method, context.paths, context.messageBusTopics, context.swingComponents) + } + + private fun traverseMethod(method: PsiMethod, context: TraversalContext) { + println("Traversing ${method.containingClass?.qualifiedName}.${method.name}") + val signature = MethodSignature.fromMethod(method) + if (context.currentPath.size >= context.config.maxDepth || signature in context.visited) return + context.visited.add(signature) + context.currentPath.add(MethodCall(method)) + + val annotationRequirement = detector.findAnnotationRequirements(method) + annotationRequirement.forEach { context.paths.add(ExecutionPath(context.currentPath.toList(), it)) } + getMethodCallees(method).forEach { processCallee(it, context) } + + context.currentPath.removeLast() + } + + private fun getMethodCallees(method: PsiMethod): List { + val callees = mutableListOf() + + method.body?.accept(object : JavaRecursiveElementVisitor() { + override fun visitMethodCallExpression(expression: PsiMethodCallExpression) { + super.visitMethodCallExpression(expression) + expression.resolveMethod()?.let { callees.add(it) } + } + + override fun visitMethodReferenceExpression(expression: PsiMethodReferenceExpression) { + super.visitMethodReferenceExpression(expression) + (expression.resolve() as? PsiMethod)?.let { callees.add(it) } + } + + override fun visitNewExpression(expression: PsiNewExpression) { + super.visitNewExpression(expression) + expression.resolveMethod()?.let { callees.add(it) } + } + }) + + return callees + } + + private fun processCallee(callee: PsiMethod, context: TraversalContext) { + detector.findBodyRequirements(callee).forEach { requirement -> + context.paths.add(ExecutionPath(context.currentPath.toList(), requirement)) + } + if (!detector.isAsyncDispatch(callee)) { + when { + detector.isMessageBusCall(callee) -> handleMessageBusCall(callee, context) + detector.isPolymorphicCall(callee) -> handlePolymorphic(callee, context) + else -> traverseMethod(callee, context) + } + } + } + + private fun handlePolymorphic(method: PsiMethod, context: TraversalContext) { + val implementations = findImplementations(method, context.config) + implementations.forEach { traverseMethod(it, context) } + } + + private fun findImplementations(method: PsiMethod, config: AnalysisConfig): List { + val implementations = mutableListOf() + if (method.body != null) { + implementations.add(method) + } + if (method.hasModifierProperty(PsiModifier.ABSTRACT) || method.containingClass?.isInterface == true) { + val query = OverridingMethodsSearch.search(method, config.scope, true) + query.forEach(Processor { + if (implementations.size >= config.maxImplementations) return@Processor false + implementations.add(it) + }) + } + return implementations + } + + private fun handleMessageBusCall(method: PsiMethod, context: TraversalContext) { + detector.extractMessageBusTopic(method)?.let { context.messageBusTopics.add(it) } + + val topicType = method.returnType as? PsiClassType ?: return + val topicInterface = topicType.resolve() ?: return + if (!topicInterface.isInterface) return + + detector.findTopicListeners(topicInterface, context.config).forEach { listener -> + listener.methods.forEach { method -> traverseMethod(method, context) } + } + } +} diff --git a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsDetector.kt b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsDetector.kt index d450049e0607..816b68461000 100644 --- a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsDetector.kt +++ b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsDetector.kt @@ -1,12 +1,14 @@ // 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 com.intellij.psi.PsiClass import com.intellij.psi.PsiClassType import com.intellij.psi.PsiMethod import com.intellij.psi.PsiMethodCallExpression -import com.intellij.psi.PsiElement import com.intellij.psi.PsiModifier +import com.intellij.psi.search.searches.ClassInheritorsSearch import com.intellij.psi.util.InheritanceUtil +import com.intellij.util.Processor class LockReqsDetector(private val patterns: LockReqsPatterns = DefaultLockReqsPatterns()) { @@ -27,15 +29,12 @@ class LockReqsDetector(private val patterns: LockReqsPatterns = DefaultLockReqsP patterns.assertionMethods[className]?.get(methodName)?.let { lockType -> requirements.add(LockRequirement(method, lockType, RequirementReason.ASSERTION)) } - if (isSwingMethod(method)) requirements.add(LockRequirement(method, LockType.EDT, RequirementReason.SWING_COMPONENT)) + if (isSwingMethod(method)) { + requirements.add(LockRequirement(method, LockType.EDT, RequirementReason.SWING_COMPONENT)) + } return requirements } - private fun isSwingComponent(className: String): Boolean { - return patterns.edtRequiredClasses.contains(className) || - patterns.edtRequiredPackages.any { className.startsWith("$it.") } - } - private fun isSwingMethod(method: PsiMethod): Boolean { val containingClass = method.containingClass ?: return false val className = containingClass.qualifiedName ?: return false @@ -45,24 +44,13 @@ class LockReqsDetector(private val patterns: LockReqsPatterns = DefaultLockReqsP } } - fun isAsyncBoundary(method: PsiMethod): Boolean { - return method.name in patterns.asyncMethods || method.containingClass?.qualifiedName in patterns.asyncClasses + private fun isSwingComponent(className: String): Boolean { + return patterns.edtRequiredClasses.contains(className) || + patterns.edtRequiredPackages.any { className.startsWith("$it.") } } - fun isMessageBusCall(expression: PsiMethodCallExpression): Boolean { - val method = expression.resolveMethod() ?: return false - val containingClass = method.containingClass?.qualifiedName ?: return false - return patterns.messageBusClasses.contains(containingClass) && method.name in patterns.messageBusMethods - } - - fun extractMessageBusTopic(expression: PsiMethodCallExpression): String? { - val method = expression.resolveMethod() ?: return null - if (method.name == "syncPublisher") { - val returnType = method.returnType as? PsiClassType ?: return null - val resolvedTopicInterface = returnType.resolve() ?: return null - return resolvedTopicInterface.qualifiedName - } - return null + fun isAsyncDispatch(method: PsiMethod): Boolean { + return method.name in patterns.asyncMethods && method.containingClass?.qualifiedName in patterns.asyncClasses } fun isPolymorphicCall(method: PsiMethod): Boolean { @@ -72,4 +60,28 @@ class LockReqsDetector(private val patterns: LockReqsPatterns = DefaultLockReqsP val containingClass = method.containingClass ?: return false return !containingClass.hasModifierProperty(PsiModifier.FINAL) } + + fun isMessageBusCall(method: PsiMethod): Boolean { + val containingClass = method.containingClass?.qualifiedName ?: return false + return patterns.messageBusClasses.contains(containingClass) && method.name in patterns.messageBusSyncMethods + } + + fun extractMessageBusTopic(method: PsiMethod): PsiClass? { + if (method.name in patterns.messageBusSyncMethods) { + val returnType = method.returnType as? PsiClassType ?: return null + val resolvedTopicInterface = returnType.resolve() ?: return null + return resolvedTopicInterface + } + return null + } + + fun findTopicListeners(topicInterface: PsiClass, config: AnalysisConfig): List { + val listeners = mutableListOf() + val query = ClassInheritorsSearch.search(topicInterface, config.scope, true) + query.forEach(Processor { + if (listeners.size > config.maxImplementations) return@Processor false + listeners.add(it) + }) + return listeners + } } \ No newline at end of file diff --git a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsModel.kt b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsModel.kt index 50f1a7e8ada5..750da6a3a774 100644 --- a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsModel.kt +++ b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsModel.kt @@ -2,6 +2,7 @@ package org.jetbrains.idea.devkit.threadingModelHelper import com.intellij.openapi.project.Project +import com.intellij.psi.PsiClass import com.intellij.psi.PsiElement import com.intellij.psi.PsiMethod import com.intellij.psi.search.GlobalSearchScope @@ -47,7 +48,7 @@ data class ExecutionPath( data class AnalysisResult( val method: PsiMethod, val paths: Set, - val messageBusTopics: Set = emptySet(), + val messageBusTopics: Set = emptySet(), val swingComponents: Set = emptySet(), ) diff --git a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsPatterns.kt b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsPatterns.kt index c5a2d418d237..a0eb6e3f76e7 100644 --- a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsPatterns.kt +++ b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsPatterns.kt @@ -9,7 +9,7 @@ interface LockReqsPatterns { val asyncClasses: Set val asyncMethods: Set val messageBusClasses: Set - val messageBusMethods: Set + val messageBusSyncMethods: Set val safeSwingMethods: Set } @@ -66,12 +66,11 @@ class DefaultLockReqsPatterns : LockReqsPatterns { ) override val messageBusClasses: Set = setOf( - "com.intellij.util.messages.MessageBus", - "com.intellij.util.messages.MessageBusConnection" + "com.intellij.util.messages.MessageBus" ) - override val messageBusMethods: Set = setOf( - "syncPublisher", "connect", "simpleConnect" + override val messageBusSyncMethods: Set = setOf( + "syncPublisher" ) override val safeSwingMethods: Set = setOf() diff --git a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsService.kt b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsService.kt index 87609d7f1610..978dd177f737 100644 --- a/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsService.kt +++ b/plugins/devkit/devkit-core/src/threadingModelHelper/LockReqsService.kt @@ -20,7 +20,7 @@ class LockReqsService(private val project: Project) { var onResultsUpdated: ((AnalysisResult?) -> Unit)? = null fun updateResults(method: PsiMethod) { - val analyzer = LockReqsAnalyzer() + val analyzer = LockReqsAnalyzerDFS() _currentResult = analyzer.analyzeMethod(method) onResultsUpdated?.invoke(_currentResult) diff --git a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/DifferentClassesMethods.java b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/DifferentClassesMethods.java index 0c242c35ab2a..7489e090ba8b 100644 --- a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/DifferentClassesMethods.java +++ b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/DifferentClassesMethods.java @@ -11,14 +11,13 @@ class DifferentClassesMethods { void helperMethod() { Service service = new Service(); service.serviceMethod(); + ThreadingAssertions.assertWriteAccess(); } } class Service { @RequiresEdt - void serviceMethod() { - ThreadingAssertions.assertWriteAccess(); - } + void serviceMethod() {} } } diff --git a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/SubtypingPolymorphism.java b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/SubtypingPolymorphism.java index b86cc429970f..1b648e66033c 100644 --- a/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/SubtypingPolymorphism.java +++ b/plugins/devkit/devkit-java-tests/testData/threadingModelHelper/SubtypingPolymorphism.java @@ -3,7 +3,7 @@ import com.intellij.util.concurrency.ThreadingAssertions; public class SubtypingPolymorphism { public void testMethod() { - Service[] services = {new FileService(), new NetworkService(), new DatabaseService()}; + Service[] services = {new FileService(), new UIService(), new DatabaseService()}; for (Service service : services) { service.execute(); } diff --git a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/threadingModelHelper/LockReqsUnitTest.kt b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/threadingModelHelper/LockReqsUnitTest.kt index c34041ff8fed..d8c45be68121 100644 --- a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/threadingModelHelper/LockReqsUnitTest.kt +++ b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/threadingModelHelper/LockReqsUnitTest.kt @@ -10,11 +10,11 @@ import org.jetbrains.idea.devkit.DevkitJavaTestsUtil @TestDataPath($$"$CONTENT_ROOT/testData/threadingModelHelper/") class LockReqsUnitTest : BasePlatformTestCase() { - private lateinit var analyzer: LockReqsAnalyzer + private lateinit var analyzer: LockReqsAnalyzerDFS override fun setUp() { super.setUp() - analyzer = LockReqsAnalyzer() + analyzer = LockReqsAnalyzerDFS() myFixture.addFileToProject("com/intellij/util/concurrency/annotations.java", """ package com.intellij.util.concurrency.annotations; public @interface RequiresReadLock {} @@ -44,20 +44,20 @@ class LockReqsUnitTest : BasePlatformTestCase() { override fun getBasePath() = DevkitJavaTestsUtil.TESTDATA_PATH + "threadingModelHelper/" fun testNoLockRequirements() { - val result = doTest("NoLockRequirements", "testMethod") + val result = doTest("NoLockRequirements") val actualPaths = formatResult(result) assertTrue(actualPaths.isEmpty()) } fun testAssertionInNestedBlock() { - val result = doTest("AssertionInNestedBlock", "testMethod") + val result = doTest("AssertionInNestedBlock") val expectedPaths = listOf("AssertionInNestedBlock.testMethod => READ.ASSERTION") val actualPaths = formatResult(result) assertEquals(expectedPaths.sorted(), actualPaths.sorted()) } fun testAnnotationInChain() { - val result = doTest("AnnotationInChain", "testMethod") + val result = doTest("AnnotationInChain") val expectedPaths = listOf("AnnotationInChain.testMethod -> AnnotationInChain.intermediateMethod" + " -> AnnotationInChain.targetMethod => READ.ANNOTATION") val actualPaths = formatResult(result) @@ -65,7 +65,7 @@ class LockReqsUnitTest : BasePlatformTestCase() { } fun testBothAnnotationAndAssertion() { - val result = doTest("BothAnnotationAndAssertion", "testMethod") + val result = doTest("BothAnnotationAndAssertion") val expectedPaths = listOf("BothAnnotationAndAssertion.testMethod => WRITE.ANNOTATION", "BothAnnotationAndAssertion.testMethod => BGT.ASSERTION") val actualPaths = formatResult(result) @@ -73,22 +73,22 @@ class LockReqsUnitTest : BasePlatformTestCase() { } fun testCyclicRecursiveCalls() { - val result = doTest("CyclicRecursiveCalls", "testMethod") + val result = doTest("CyclicRecursiveCalls") val expectedPaths = listOf("CyclicRecursiveCalls.testMethod -> CyclicRecursiveCalls.methodB => READ.ANNOTATION") val actualPaths = formatResult(result) assertEquals(expectedPaths.sorted(), actualPaths.sorted()) } fun testDifferentClassesMethods() { - val result = doTest("DifferentClassesMethods", "testMethod") + val result = doTest("DifferentClassesMethods") val expectedPaths = listOf("DifferentClassesMethods.testMethod -> Helper.helperMethod -> Service.serviceMethod => EDT.ANNOTATION", - "DifferentClassesMethods.testMethod -> Helper.helperMethod -> Service.serviceMethod => WRITE.ASSERTION") + "DifferentClassesMethods.testMethod -> Helper.helperMethod => WRITE.ASSERTION") val actualPaths = formatResult(result) assertEquals(expectedPaths.sorted(), actualPaths.sorted()) } fun testMultipleAssertionsInMethod() { - val result = doTest("MultipleAssertionsInMethod", "testMethod") + val result = doTest("MultipleAssertionsInMethod") val expectedPaths = listOf("MultipleAssertionsInMethod.testMethod => READ.ASSERTION") val actualPaths = formatResult(result) assertEquals(1, actualPaths.size) @@ -97,7 +97,7 @@ class LockReqsUnitTest : BasePlatformTestCase() { fun testLambdaWithMethodReference() { - val result = doTest("LambdaWithMethodReference", "testMethod") + val result = doTest("LambdaWithMethodReference") val expectedPaths = listOf("LambdaWithMethodReference.testMethod -> LambdaWithMethodReference.processItem => READ.ASSERTION") val actualPaths = formatResult(result) assertEquals(expectedPaths.sorted(), actualPaths.sorted()) @@ -105,7 +105,7 @@ class LockReqsUnitTest : BasePlatformTestCase() { } fun testSubtypingPolymorphism() { - val result = doTest("SubtypingPolymorphism", "testMethod") + val result = doTest("SubtypingPolymorphism") val expectedPaths = listOf( "SubtypingPolymorphism.testMethod -> FileService.execute => READ.ANNOTATION", "SubtypingPolymorphism.testMethod -> UIService.execute => EDT.ASSERTION", @@ -115,11 +115,11 @@ class LockReqsUnitTest : BasePlatformTestCase() { assertEquals(expectedPaths.sorted(), actualPaths.sorted()) } - private fun doTest(className: String, methodName: String): AnalysisResult { + private fun doTest(className: String): AnalysisResult { val fileName = "${getTestName(false)}.java" val psiJavaFile = myFixture.configureByFile(fileName) as PsiJavaFile val targetClass = psiJavaFile.classes.find { it.name == className } ?: error("Could not find class $className") - val targetMethod = targetClass.methods.find { it.name == methodName } ?: error("Could not find method $methodName") + val targetMethod = targetClass.methods.find { it.name == testMethodName } ?: error("Could not find method $testMethodName") return analyzer.analyzeMethod(targetMethod) } @@ -131,4 +131,8 @@ class LockReqsUnitTest : BasePlatformTestCase() { } return actualPaths } + + companion object { + private const val testMethodName = "testMethod" + } }