[jvm] Split JUnit usage provider per framework

Will allow for disabling them individually. #IDEA-346510 Fixed

GitOrigin-RevId: 7b879111b92f38a7a55166d67d657520c6d9314d
This commit is contained in:
Bart van Helvert
2024-03-11 23:51:14 +01:00
committed by intellij-monorepo-bot
parent 7da501ece2
commit f896419d92
28 changed files with 595 additions and 282 deletions

View File

@@ -78,4 +78,5 @@ public final class JUnitCommonClassNames {
public static final String ORG_JUNIT_EXPERIMENTAL_THEORIES_DATAPOINT = "org.junit.experimental.theories.DataPoint";
public static final String ORG_JUNIT_EXPERIMENTAL_THEORIES_DATAPOINTS = "org.junit.experimental.theories.DataPoints";
public static final String ORG_JUNIT_EXPERIMENTAL_RUNNERS_ENCLOSED = "org.junit.experimental.runners.Enclosed";
public static final String ORG_JUNIT_JUPITER_API_IO_TEMPDIR = "org.junit.jupiter.api.io.TempDir";
}

View File

@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<problems/>

View File

@@ -1,11 +0,0 @@
public class MyEasyMockTest {
@org.easymock.Mock
private String myFoo;
{
System.out.println(myFoo);
}
@org.junit.Test
public void testName() throws Exception {}
}

View File

@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<problems/>

View File

@@ -1,11 +0,0 @@
public class MyMockitoTest {
@org.mockito.Mock
private String myFoo;
{
System.out.println(myFoo);
}
@org.junit.Test
public void testName() throws Exception {}
}

View File

@@ -167,14 +167,6 @@ public class UnusedDeclarationInspectionTest extends AbstractUnusedDeclarationTe
doTest();
}
public void testMockedMockitoField() {
doTest();
}
public void testMockedEasyMockField() {
doTest();
}
public void testConstructorCalls() {
doTest();
}

View File

@@ -51,6 +51,11 @@
implementationClass="com.intellij.codeInspection.test.TestInProductSourceInspection"/>
<inspectionElementsMerger implementation="com.intellij.codeInspection.test.TestInProductSourceInspectionMerger"/>
<!--deadCode-->
<implicitUsageProvider implementation="com.intellij.codeInspection.deadCode.AssertJImplicitUsageProvider"/>
<implicitUsageProvider implementation="com.intellij.codeInspection.deadCode.MockitoImplicitUsageProvider"/>
<implicitUsageProvider implementation="com.intellij.codeInspection.deadCode.EasyMockImplicitUsageProvider"/>
<!--logging-->
<localInspection language="UAST" enabledByDefault="true" level="WARNING" shortName="LoggingStringTemplateAsArgument"
groupBundle="messages.JvmAnalysisBundle" bundle="messages.JvmAnalysisBundle"

View File

@@ -0,0 +1,21 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection.deadCode
import com.intellij.codeInsight.AnnotationUtil
import com.intellij.codeInsight.daemon.ImplicitUsageProvider
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiField
private const val ORG_ASSERTJ_CORE_API_JUNIT_JUPITER_INJECTSOFTASSERTIONS = "org.assertj.core.api.junit.jupiter.InjectSoftAssertions"
class AssertJImplicitUsageProvider : ImplicitUsageProvider {
override fun isImplicitUsage(element: PsiElement): Boolean = false
override fun isImplicitRead(element: PsiElement): Boolean = false
override fun isImplicitWrite(element: PsiElement): Boolean {
return element is PsiField && AnnotationUtil.isAnnotated(element, ORG_ASSERTJ_CORE_API_JUNIT_JUPITER_INJECTSOFTASSERTIONS, 0)
}
override fun isImplicitlyNotNullInitialized(element: PsiElement): Boolean = isImplicitWrite(element)
}

View File

@@ -0,0 +1,19 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection.deadCode
import com.intellij.codeInsight.AnnotationUtil
import com.intellij.codeInsight.daemon.ImplicitUsageProvider
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiField
class EasyMockImplicitUsageProvider : ImplicitUsageProvider {
override fun isImplicitUsage(element: PsiElement): Boolean = false
override fun isImplicitRead(element: PsiElement): Boolean = false
override fun isImplicitWrite(element: PsiElement): Boolean {
return element is PsiField && AnnotationUtil.isAnnotated(element, "org.easymock.Mock", 0)
}
override fun isImplicitlyNotNullInitialized(element: PsiElement): Boolean = isImplicitWrite(element)
}

View File

@@ -0,0 +1,30 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection.deadCode
import com.intellij.codeInsight.AnnotationUtil
import com.intellij.codeInsight.daemon.ImplicitUsageProvider
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiField
import com.intellij.psi.PsiParameter
private const val ORG_MOCKITO_MOCK = "org.mockito.Mock"
private val INJECTED_FIELD_ANNOTATIONS = listOf(
ORG_MOCKITO_MOCK,
"org.mockito.Spy",
"org.mockito.Captor",
"org.mockito.InjectMocks"
)
class MockitoImplicitUsageProvider : ImplicitUsageProvider {
override fun isImplicitUsage(element: PsiElement): Boolean = false
override fun isImplicitRead(element: PsiElement): Boolean = false
override fun isImplicitWrite(element: PsiElement): Boolean {
return element is PsiParameter && AnnotationUtil.isAnnotated(element, ORG_MOCKITO_MOCK, 0)
|| element is PsiField && AnnotationUtil.isAnnotated(element, INJECTED_FIELD_ANNOTATIONS, 0)
}
override fun isImplicitlyNotNullInitialized(element: PsiElement): Boolean = isImplicitWrite(element)
}

View File

@@ -0,0 +1,22 @@
package com.intellij.jvm.analysis.internal.testFramework.deadCode
import com.intellij.jvm.analysis.internal.testFramework.test.addAssertJLibrary
import com.intellij.jvm.analysis.internal.testFramework.test.addJUnit5Library
import com.intellij.jvm.analysis.testFramework.JvmImplicitUsageProviderTestBase
import com.intellij.openapi.module.Module
import com.intellij.openapi.roots.ContentEntry
import com.intellij.openapi.roots.ModifiableRootModel
import com.intellij.pom.java.LanguageLevel
import com.intellij.testFramework.LightProjectDescriptor
abstract class AssertJImplicitUsageProviderTestBase : JvmImplicitUsageProviderTestBase() {
protected open class AssertJProjectDescriptor(languageLevel: LanguageLevel) : ProjectDescriptor(languageLevel) {
override fun configureModule(module: Module, model: ModifiableRootModel, contentEntry: ContentEntry) {
super.configureModule(module, model, contentEntry)
model.addJUnit5Library()
model.addAssertJLibrary()
}
}
override fun getProjectDescriptor(): LightProjectDescriptor = AssertJProjectDescriptor(LanguageLevel.HIGHEST)
}

View File

@@ -0,0 +1,22 @@
package com.intellij.jvm.analysis.internal.testFramework.deadCode
import com.intellij.jvm.analysis.internal.testFramework.test.addEasyMockLibrary
import com.intellij.jvm.analysis.internal.testFramework.test.addJUnit4Library
import com.intellij.jvm.analysis.testFramework.JvmImplicitUsageProviderTestBase
import com.intellij.openapi.module.Module
import com.intellij.openapi.roots.ContentEntry
import com.intellij.openapi.roots.ModifiableRootModel
import com.intellij.pom.java.LanguageLevel
import com.intellij.testFramework.LightProjectDescriptor
abstract class EasyMockImplicitUsageProviderTestBase : JvmImplicitUsageProviderTestBase() {
protected open class EasyMockProjectDescriptor(languageLevel: LanguageLevel) : ProjectDescriptor(languageLevel) {
override fun configureModule(module: Module, model: ModifiableRootModel, contentEntry: ContentEntry) {
super.configureModule(module, model, contentEntry)
model.addJUnit4Library()
model.addEasyMockLibrary()
}
}
override fun getProjectDescriptor(): LightProjectDescriptor = EasyMockProjectDescriptor(LanguageLevel.HIGHEST)
}

View File

@@ -0,0 +1,22 @@
package com.intellij.jvm.analysis.internal.testFramework.deadCode
import com.intellij.jvm.analysis.internal.testFramework.test.addJUnit4Library
import com.intellij.jvm.analysis.internal.testFramework.test.addMockitoLibrary
import com.intellij.jvm.analysis.testFramework.JvmImplicitUsageProviderTestBase
import com.intellij.openapi.module.Module
import com.intellij.openapi.roots.ContentEntry
import com.intellij.openapi.roots.ModifiableRootModel
import com.intellij.pom.java.LanguageLevel
import com.intellij.testFramework.LightProjectDescriptor
abstract class MockitoImplicitUsageProviderTestBase : JvmImplicitUsageProviderTestBase() {
protected open class MockitoProjectDescriptor(languageLevel: LanguageLevel) : ProjectDescriptor(languageLevel) {
override fun configureModule(module: Module, model: ModifiableRootModel, contentEntry: ContentEntry) {
super.configureModule(module, model, contentEntry)
model.addJUnit4Library()
model.addMockitoLibrary()
}
}
override fun getProjectDescriptor(): LightProjectDescriptor = MockitoProjectDescriptor(LanguageLevel.HIGHEST)
}

View File

@@ -30,6 +30,14 @@ internal fun ModifiableRootModel.addJUnit5Library(version: String = "5.9.1") {
MavenDependencyUtil.addFromMaven(this, "org.junit.jupiter:junit-jupiter-params:$version")
}
internal fun ModifiableRootModel.addAssertJLibrary() {
MavenDependencyUtil.addFromMaven(this, "org.assertj:assertj-core:3.24.2")
internal fun ModifiableRootModel.addAssertJLibrary(version: String = "3.24.2") {
MavenDependencyUtil.addFromMaven(this, "org.assertj:assertj-core:$version")
}
internal fun ModifiableRootModel.addMockitoLibrary(version: String = "1.10.19") {
MavenDependencyUtil.addFromMaven(this, "org.mockito:mockito-all:$version")
}
internal fun ModifiableRootModel.addEasyMockLibrary(version: String = "5.2.0") {
MavenDependencyUtil.addFromMaven(this, "org.easymock:easymock:$version")
}

View File

@@ -0,0 +1,21 @@
package com.intellij.codeInspection.deadCode
import com.intellij.jvm.analysis.internal.testFramework.deadCode.AssertJImplicitUsageProviderTestBase
import com.intellij.jvm.analysis.testFramework.JvmLanguage
class JavaAssertJImplicitUsageProviderTest : AssertJImplicitUsageProviderTestBase() {
fun `test inject soft assertion implicit usage provider`() {
myFixture.testHighlighting(JvmLanguage.JAVA, """
@org.junit.jupiter.api.extension.ExtendWith(org.assertj.core.api.junit.jupiter.SoftAssertionsExtension.class)
public class TestClass {
@org.assertj.core.api.junit.jupiter.InjectSoftAssertions
private org.assertj.core.api.SoftAssertions softAssertions;
@org.junit.jupiter.api.Test
public void doSomething() {
softAssertions.assertThat("string").isEqualTo("string");
}
}
""".trimIndent(), fileName = "TestClass")
}
}

View File

@@ -0,0 +1,22 @@
package com.intellij.codeInspection.deadCode
import com.intellij.jvm.analysis.internal.testFramework.deadCode.EasyMockImplicitUsageProviderTestBase
import com.intellij.jvm.analysis.testFramework.JvmLanguage
class JavaEasyMockImplicitUsageProviderTest : EasyMockImplicitUsageProviderTestBase() {
fun `test implicit usage for mocked field`() {
myFixture.testHighlighting(JvmLanguage.JAVA, """
public class MyEasyMockTest {
@org.easymock.Mock
private String myFoo;
{
System.out.println(myFoo);
}
@org.junit.Test
public void testName() throws Exception { }
}
""".trimIndent(), fileName = "MyEasyMockTest")
}
}

View File

@@ -0,0 +1,22 @@
package com.intellij.codeInspection.deadCode
import com.intellij.jvm.analysis.internal.testFramework.deadCode.MockitoImplicitUsageProviderTestBase
import com.intellij.jvm.analysis.testFramework.JvmLanguage
class JavaMockitoImplicitUsageProviderTest : MockitoImplicitUsageProviderTestBase() {
fun `test implicit usage for mocked field`() {
myFixture.testHighlighting(JvmLanguage.JAVA, """
public class MyMockitoTest {
@org.mockito.Mock
private String myFoo;
{
System.out.println(myFoo);
}
@org.junit.Test
public void testName() throws Exception { }
}
""".trimIndent(), fileName = "MyMockitoTest")
}
}

View File

@@ -0,0 +1,21 @@
package com.intellij.codeInspection.tests.kotlin.deadCode
import com.intellij.jvm.analysis.internal.testFramework.deadCode.AssertJImplicitUsageProviderTestBase
import com.intellij.jvm.analysis.testFramework.JvmLanguage
class KotlinAssertJImplicitUsageProviderTest : AssertJImplicitUsageProviderTestBase() {
fun `test inject soft assertion implicit usage provider`() {
myFixture.testHighlighting(JvmLanguage.KOTLIN, """
@org.junit.jupiter.api.extension.ExtendWith(org.assertj.core.api.junit.jupiter.SoftAssertionsExtension::class)
class TestClass {
@org.assertj.core.api.junit.jupiter.InjectSoftAssertions
private lateinit var softAssertions: org.assertj.core.api.SoftAssertions
@org.junit.jupiter.api.Test
fun doSomething() {
softAssertions.assertThat("string").isEqualTo("string")
}
}
""".trimIndent(), fileName = "TestClass")
}
}

View File

@@ -0,0 +1,22 @@
package com.intellij.codeInspection.tests.kotlin.deadCode
import com.intellij.jvm.analysis.internal.testFramework.deadCode.EasyMockImplicitUsageProviderTestBase
import com.intellij.jvm.analysis.testFramework.JvmLanguage
class KotlinEasyMockImplicitUsageProviderTest : EasyMockImplicitUsageProviderTestBase() {
fun `test implicit usage for mocked field`() {
myFixture.testHighlighting(JvmLanguage.KOTLIN, """
class MyEasyMockTest {
@org.easymock.Mock
private lateinit var myFoo: String
init {
System.out.println(myFoo)
}
@org.junit.Test
fun testName() { }
}
""".trimIndent(), fileName = "MyEasyMockTest")
}
}

View File

@@ -0,0 +1,22 @@
package com.intellij.codeInspection.tests.kotlin.deadCode
import com.intellij.jvm.analysis.internal.testFramework.deadCode.MockitoImplicitUsageProviderTestBase
import com.intellij.jvm.analysis.testFramework.JvmLanguage
class KotlinMockitoImplicitUsageProviderTest : MockitoImplicitUsageProviderTestBase() {
fun `test implicit usage for mocked field`() {
myFixture.testHighlighting(JvmLanguage.KOTLIN, """
class MyMockitoTest {
@org.mockito.Mock
private lateinit var myFoo: String
init {
System.out.println(myFoo)
}
@org.junit.Test
fun testName() { }
}
""".trimIndent(), fileName = "MyMockitoTest")
}
}

View File

@@ -0,0 +1,10 @@
package com.intellij.jvm.analysis.testFramework
import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection
/**
* A test base for testing [com.intellij.codeInsight.daemon.ImplicitUsageProvider] implementations in all JVM languages.
*/
abstract class JvmImplicitUsageProviderTestBase : JvmInspectionTestBase() {
override val inspection by lazy { UnusedDeclarationInspection(true) }
}

View File

@@ -1,83 +0,0 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.execution.junit;
import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection;
import com.intellij.execution.junit.codeInsight.JUnit5TestFrameworkSetupUtil;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
public class JunitImplicitUsageProviderTest extends LightJavaCodeInsightFixtureTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
JUnit5TestFrameworkSetupUtil.setupJUnit5Library(myFixture);
myFixture.enableInspections(new UnusedDeclarationInspection(true));
myFixture.addClass("""
package java.nio.file;
public interface Path {}
""");
myFixture.addClass("""
package org.assertj.core.api;
import java.nio.file.Path;
public final class Assertions { public static void assertThat(Path actual) {} }
""");
}
public void testRecognizeTestMethodInParameterizedClass() {
myFixture.configureByText("Test.java", """
import org.junit.jupiter.params.provider.EnumSource;
class Test {
enum HostnameVerification {
YES, NO;
}
@EnumSource(HostnameVerification.class)
static void test(HostnameVerification hostnameVerification) {
System.out.println(hostnameVerification);
}
public static void main(String[] args) {
test(null);
}
}""");
myFixture.testHighlighting(true, false, false);
}
public void testParameterizedDisplayNameByParameter() {
myFixture.configureByText("MyTest.java", """
import org.junit.jupiter.params.ParameterizedTest;class MyTest {
@ParameterizedTest(name = "{0}")
void byName(String name) {}
}""");
myFixture.testHighlighting(true, false, false);
}
public void testJunitTempDirMetaAnnotation() {
myFixture.configureByText("Test.java", """
import org.junit.jupiter.api.io.TempDir;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
class Test {
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@TempDir
@interface CustomTempDir {
}
@CustomTempDir
private Path tempDir;
@org.junit.jupiter.api.Test
void test() {
assertThat(tempDir);
}
}
""");
myFixture.testHighlighting(true, false, false);
}
}

View File

@@ -0,0 +1,87 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.execution.junit.codeInspection.deadCode
import com.intellij.junit.testFramework.JUnit5ImplicitUsageProviderTestBase
import com.intellij.jvm.analysis.testFramework.JvmLanguage
class JavaJunit5ImplicitUsageProviderTest : JUnit5ImplicitUsageProviderTestBase() {
fun `test implicit usage of enum source`() {
myFixture.testHighlighting(JvmLanguage.JAVA, """
class Test {
enum HostnameVerification {
YES, NO;
}
@org.junit.jupiter.params.provider.EnumSource(HostnameVerification.class)
@org.junit.jupiter.params.ParameterizedTest
void testHostNameVerification(HostnameVerification hostnameVerification) {
System.out.println(hostnameVerification);
}
}
""".trimIndent())
}
fun `test implicit usage of parameter in parameterized test`() {
myFixture.testHighlighting(JvmLanguage.JAVA, """
class MyTest {
@org.junit.jupiter.params.ParameterizedTest(name = "{0}")
void byName(String name) {}
}
""".trimIndent())
}
fun `test implicit usage of method source with implicit method name`() {
myFixture.testHighlighting(JvmLanguage.JAVA, """
import java.util.stream.*;
class MyTest {
@org.junit.jupiter.params.provider.MethodSource
@org.junit.jupiter.params.ParameterizedTest
void foo(String input) {
System.out.println(input);
}
private static Stream<String> foo() {
return Stream.of("");
}
}
""".trimIndent())
}
fun `test implicit usage of TempDir as direct annotation`() {
myFixture.testHighlighting(JvmLanguage.JAVA, """
class Test {
@org.junit.jupiter.api.io.TempDir
private java.nio.file.Path tempDir;
@org.junit.jupiter.api.Test
void test() {
System.out.println(tempDir);
}
}
""".trimIndent())
}
fun `test implicit usage of TempDir as meta annotation`() {
myFixture.testHighlighting(JvmLanguage.JAVA, """
import java.lang.annotation.*;
class Test {
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@org.junit.jupiter.api.io.TempDir
@interface CustomTempDir { }
@CustomTempDir
private java.nio.file.Path tempDir;
@org.junit.jupiter.api.Test
void test() {
System.out.println(tempDir);
}
}
""".trimIndent())
}
}

View File

@@ -0,0 +1,94 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.execution.junit.codeInspection.deadCode
import com.intellij.junit.testFramework.JUnit5ImplicitUsageProviderTestBase
import com.intellij.jvm.analysis.testFramework.JvmLanguage
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
class KotlinJUnit5ImplicitUsageProviderTest : JUnit5ImplicitUsageProviderTestBase() {
override fun setUp() {
super.setUp()
ConfigLibraryUtil.configureKotlinRuntime(myFixture.module)
}
fun `test implicit usage of enum source`() {
myFixture.testHighlighting(JvmLanguage.KOTLIN, """
class Test {
enum class HostnameVerification {
YES, NO
}
@org.junit.jupiter.params.provider.EnumSource(HostnameVerification::class)
@org.junit.jupiter.params.ParameterizedTest
fun testHostNameVerification(hostnameVerification: HostnameVerification) {
System.out.println(hostnameVerification)
}
}
""".trimIndent())
}
fun `test implicit usage of parameter in parameterized test`() {
myFixture.testHighlighting(JvmLanguage.KOTLIN, """
class MyTest {
@org.junit.jupiter.params.ParameterizedTest(name = "{0}")
fun byName(name: String) { }
}
""".trimIndent())
}
fun `test implicit usage of method source with implicit method name`() {
myFixture.testHighlighting(JvmLanguage.KOTLIN, """
import java.util.stream.*
class MyTest {
@org.junit.jupiter.params.provider.MethodSource
@org.junit.jupiter.params.ParameterizedTest
fun foo(input: String) {
System.out.println(input)
}
companion object {
@JvmStatic
private fun foo(): Stream<String> {
return Stream.of("")
}
}
}
""".trimIndent())
}
fun `test implicit usage of TempDir as direct annotation`() {
myFixture.testHighlighting(JvmLanguage.KOTLIN, """
class Test {
@org.junit.jupiter.api.io.TempDir
private lateinit var tempDir: java.nio.file.Path
@org.junit.jupiter.api.Test
fun test() {
System.out.println(tempDir)
}
}
""".trimIndent())
}
fun `test implicit usage of TempDir as meta annotation`() {
myFixture.testHighlighting(JvmLanguage.KOTLIN, """
class Test {
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@org.junit.jupiter.api.io.TempDir
annotation class CustomTempDir { }
@CustomTempDir
private lateinit var tempDir: java.nio.file.Path
@org.junit.jupiter.api.Test
fun test() {
System.out.println(tempDir)
}
}
""".trimIndent())
}
}

View File

@@ -64,7 +64,7 @@
<runConfigurationProducer implementation="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer"/>
<testDiscoveryProducer implementation="com.intellij.execution.testDiscovery.LocalTestDiscoveryProducer"/>
<testDiscoveryProducer implementation="com.intellij.execution.testDiscovery.IntellijTestDiscoveryProducer"/>
<implicitUsageProvider implementation="com.intellij.execution.junit2.inspection.JUnitImplicitUsageProvider"/>
<implicitUsageProvider implementation="com.intellij.execution.junit.codeInspection.deadCode.JUnit5ImplicitUsageProvider"/>
<predefinedMigrationMapProvider implementation="com.intellij.execution.junit2.refactoring.JUnit5Migration"/>
<runDashboardCustomizer implementation="com.intellij.execution.junit.JUnitRunDashboardCustomizer"

View File

@@ -0,0 +1,101 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.execution.junit.codeInspection.deadCode
import com.intellij.codeInsight.MetaAnnotationUtil
import com.intellij.codeInsight.daemon.ImplicitUsageProvider
import com.intellij.psi.*
import com.intellij.psi.impl.JavaConstantExpressionEvaluator
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.PsiSearchHelper
import com.intellij.psi.search.PsiSearchHelper.SearchCostResult
import com.intellij.psi.search.SearchScope
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import com.intellij.psi.util.PsiModificationTracker
import com.intellij.psi.util.PsiTreeUtil
import com.siyeh.ig.junit.JUnitCommonClassNames.*
private fun parameterIsUsedByParameterizedTest(parameter: PsiParameter): Boolean {
val declarationScope = parameter.declarationScope
if (declarationScope !is PsiMethod) return false
val annotation = declarationScope.modifierList.findAnnotation(ORG_JUNIT_JUPITER_PARAMS_PARAMETERIZED_TEST)
if (annotation != null) {
val attributeValue = annotation.findDeclaredAttributeValue("name")
if (attributeValue is PsiExpression) {
val indexInDisplayName = "{" + declarationScope.parameterList.getParameterIndex(parameter) + "}"
val value = JavaConstantExpressionEvaluator.computeConstantExpression(attributeValue as PsiExpression?, null, false)
return indexInDisplayName == value
}
}
return false
}
private fun enumReferenceIsUsedByParameterizedTest(element: PsiEnumConstant): Boolean {
fun isCheapEnough(psiClass: PsiClass, name: String, useScope: SearchScope): Boolean {
if (useScope is LocalSearchScope) return true
val searchHelper = PsiSearchHelper.getInstance(psiClass.project)
if (SearchCostResult.ZERO_OCCURRENCES == searchHelper.isCheapEnoughToSearch(ORG_JUNIT_JUPITER_PARAMS_ENUM_SOURCE_SHORT, (useScope as GlobalSearchScope), null, null)) {
return false
}
val cheapEnough = searchHelper.isCheapEnoughToSearch(name, useScope, null, null)
return !(cheapEnough == SearchCostResult.ZERO_OCCURRENCES || cheapEnough == SearchCostResult.TOO_MANY_OCCURRENCES)
}
fun check(psiClass: PsiClass): Boolean {
val className = psiClass.name ?: return false
val useScope = psiClass.useScope
if (!isCheapEnough(psiClass, className, useScope)) return false
return ReferencesSearch.search(psiClass, useScope, false).anyMatch { reference ->
val referenceElement = reference.element
val annotation = PsiTreeUtil.getParentOfType(referenceElement, PsiAnnotation::class.java, true, PsiStatement::class.java, PsiMember::class.java)
?: return@anyMatch false
annotation.qualifiedName == ORG_JUNIT_JUPITER_PARAMS_PROVIDER_ENUM_SOURCE && annotation.attributes.size == 1
}
}
val containingClass = element.containingClass
return containingClass != null && CachedValuesManager.getCachedValue(containingClass) {
CachedValueProvider.Result.create(check(containingClass), PsiModificationTracker.MODIFICATION_COUNT)
}
}
private fun methodSourceIsImplicitlyUsed(element: PsiMethod): Boolean {
fun check(psiMethod: PsiMethod): Boolean {
val methodName = psiMethod.name
var psiClass = psiMethod.containingClass ?: return false
if (psiMethod.getAnnotation(ORG_JUNIT_JUPITER_PARAMS_PROVIDER_METHOD_SOURCE) != null) return false
if (psiMethod.parameterList.parametersCount != 0) return false
if (psiMethod.hasAnnotation("kotlin.jvm.JvmStatic")) {
val parent = psiClass.parent
if (parent is PsiClass) psiClass = parent
}
return psiClass.findMethodsByName(methodName, false).any { otherMethod ->
psiMethod != otherMethod
&& MetaAnnotationUtil.isMetaAnnotated(otherMethod, setOf(ORG_JUNIT_JUPITER_PARAMS_PROVIDER_METHOD_SOURCE))
&& MetaAnnotationUtil.isMetaAnnotated(otherMethod, setOf(ORG_JUNIT_JUPITER_PARAMS_PARAMETERIZED_TEST))
}
}
return CachedValuesManager.getCachedValue(element) {
CachedValueProvider.Result.create(check(element), PsiModificationTracker.MODIFICATION_COUNT)
}
}
class JUnit5ImplicitUsageProvider : ImplicitUsageProvider {
override fun isImplicitUsage(element: PsiElement): Boolean {
return (element is PsiParameter && parameterIsUsedByParameterizedTest(element))
|| (element is PsiEnumConstant && enumReferenceIsUsedByParameterizedTest(element))
|| (element is PsiMethod && methodSourceIsImplicitlyUsed(element))
}
override fun isImplicitRead(element: PsiElement): Boolean = false
override fun isImplicitWrite(element: PsiElement): Boolean {
return element is PsiField && MetaAnnotationUtil.isMetaAnnotated(element, setOf(ORG_JUNIT_JUPITER_API_IO_TEMPDIR))
}
override fun isImplicitlyNotNullInitialized(element: PsiElement): Boolean = isImplicitWrite(element)
}

View File

@@ -1,162 +0,0 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.execution.junit2.inspection;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.MetaAnnotationUtil;
import com.intellij.codeInsight.daemon.ImplicitUsageProvider;
import com.intellij.psi.*;
import com.intellij.psi.impl.JavaConstantExpressionEvaluator;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.PsiSearchHelper;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.junit.JUnitCommonClassNames;
import com.siyeh.ig.psiutils.TestUtils;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static com.siyeh.ig.junit.JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PARAMETERIZED_TEST;
import static com.siyeh.ig.junit.JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PROVIDER_METHOD_SOURCE;
public final class JUnitImplicitUsageProvider implements ImplicitUsageProvider {
private static final String MOCKITO_MOCK = "org.mockito.Mock";
private static final String JUNIT_TEMP_DIR = "org.junit.jupiter.api.io.TempDir";
private static final String KOTLIN_JVM_STATIC = "kotlin.jvm.JvmStatic";
private static final List<String> INJECTED_FIELD_ANNOTATIONS = Arrays.asList(
MOCKITO_MOCK,
"org.mockito.Spy",
"org.mockito.Captor",
"org.mockito.InjectMocks",
"org.easymock.Mock",
"org.assertj.core.api.junit.jupiter.InjectSoftAssertions",
"org.junit.jupiter.api.io.TempDir");
@Override
public boolean isImplicitUsage(@NotNull PsiElement element) {
return element instanceof PsiParameter && isParameterUsedInParameterizedPresentation((PsiParameter)element) || isReferencedInsideEnumSourceAnnotation(element)
|| isReferencedInsideMethodSourceAnnotation(element);
}
private static boolean isParameterUsedInParameterizedPresentation(PsiParameter parameter) {
PsiElement declarationScope = parameter.getDeclarationScope();
if (declarationScope instanceof PsiMethod method) {
PsiAnnotation annotation = method.getModifierList().findAnnotation(ORG_JUNIT_JUPITER_PARAMS_PARAMETERIZED_TEST);
if (annotation != null) {
PsiAnnotationMemberValue attributeValue = annotation.findDeclaredAttributeValue("name");
if (attributeValue instanceof PsiExpression) {
String indexInDisplayName = "{" + method.getParameterList().getParameterIndex(parameter) + "}";
Object value = JavaConstantExpressionEvaluator.computeConstantExpression((PsiExpression)attributeValue, null, false);
return indexInDisplayName.equals(value);
}
}
}
return false;
}
private static boolean isReferencedInsideEnumSourceAnnotation(@NotNull PsiElement element) {
if (element instanceof PsiEnumConstant) {
PsiClass psiClass = ((PsiEnumConstant)element).getContainingClass();
return psiClass != null &&
CachedValuesManager.getCachedValue(psiClass,
() -> CachedValueProvider.Result.create(isEnumClassReferencedInEnumSourceAnnotation(psiClass),
PsiModificationTracker.MODIFICATION_COUNT));
}
return false;
}
private static boolean isReferencedInsideMethodSourceAnnotation(@NotNull PsiElement element) {
if (element instanceof PsiMethod psiMethod) {
return CachedValuesManager.getCachedValue(psiMethod,
() -> CachedValueProvider.Result.create(isReferencedInsideMethodSourceAnnotation(psiMethod),
PsiModificationTracker.MODIFICATION_COUNT));
}
return false;
}
private static boolean isReferencedInsideMethodSourceAnnotation(@NotNull PsiMethod psiMethod) {
String methodName = psiMethod.getName();
PsiClass psiClass = psiMethod.getContainingClass();
if (psiMethod.getAnnotation(ORG_JUNIT_JUPITER_PARAMS_PROVIDER_METHOD_SOURCE) != null) return false;
if (psiMethod.getParameterList().getParametersCount() != 0) return false;
if (!TestUtils.isInTestSourceContent(psiClass)) return false;
if (psiMethod.hasAnnotation(KOTLIN_JVM_STATIC)) {
PsiElement parent = psiClass.getParent();
if (parent instanceof PsiClass) psiClass = (PsiClass)parent;
}
return ContainerUtil.exists(psiClass.findMethodsByName(methodName, false),
it -> psiMethod != it &&
MetaAnnotationUtil.isMetaAnnotated(it, Collections.singleton(
ORG_JUNIT_JUPITER_PARAMS_PROVIDER_METHOD_SOURCE)) &&
MetaAnnotationUtil.isMetaAnnotated(it, Collections.singleton(
ORG_JUNIT_JUPITER_PARAMS_PARAMETERIZED_TEST)));
}
private static boolean isEnumClassReferencedInEnumSourceAnnotation(PsiClass psiClass) {
String className = psiClass.getName();
if (className == null) {
return false;
}
SearchScope useScope = psiClass.getUseScope();
if (!shouldCheckClassUsages(psiClass, className, useScope)) {
return false;
}
return ReferencesSearch.search(psiClass, useScope, false)
.anyMatch(reference -> {
PsiElement referenceElement = reference.getElement();
PsiAnnotation annotation = PsiTreeUtil.getParentOfType(referenceElement, PsiAnnotation.class, true, PsiStatement.class, PsiMember.class);
if (annotation != null) {
String annotationName = annotation.getQualifiedName();
if (JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PROVIDER_ENUM_SOURCE.equals(annotationName) && annotation.getAttributes().size() == 1) {
return true;
}
}
return false;
});
}
private static boolean shouldCheckClassUsages(PsiClass psiClass, String name, SearchScope useScope) {
if (!(useScope instanceof LocalSearchScope)) {
PsiSearchHelper searchHelper = PsiSearchHelper.getInstance(psiClass.getProject());
if (PsiSearchHelper.SearchCostResult.ZERO_OCCURRENCES ==
searchHelper.isCheapEnoughToSearch(JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_ENUM_SOURCE_SHORT, (GlobalSearchScope)useScope, null, null)) {
return false;
}
PsiSearchHelper.SearchCostResult cheapEnough = searchHelper.isCheapEnoughToSearch(name, (GlobalSearchScope)useScope, null, null);
if (cheapEnough == PsiSearchHelper.SearchCostResult.ZERO_OCCURRENCES ||
cheapEnough == PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES) {
return false;
}
}
return true;
}
@Override
public boolean isImplicitRead(@NotNull PsiElement element) {
return false;
}
@Override
public boolean isImplicitWrite(@NotNull PsiElement element) {
return element instanceof PsiParameter && AnnotationUtil.isAnnotated((PsiParameter)element, MOCKITO_MOCK, 0) ||
element instanceof PsiField && AnnotationUtil.isAnnotated((PsiField)element, INJECTED_FIELD_ANNOTATIONS, 0) ||
element instanceof PsiField && MetaAnnotationUtil.isMetaAnnotated((PsiField) element, Collections.singleton(JUNIT_TEMP_DIR));
}
@Override
public boolean isImplicitlyNotNullInitialized(@NotNull PsiElement element) {
return isImplicitWrite(element);
}
}

View File

@@ -0,0 +1,20 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.junit.testFramework
import com.intellij.jvm.analysis.testFramework.JvmImplicitUsageProviderTestBase
import com.intellij.openapi.module.Module
import com.intellij.openapi.roots.ContentEntry
import com.intellij.openapi.roots.ModifiableRootModel
import com.intellij.pom.java.LanguageLevel
import com.intellij.testFramework.LightProjectDescriptor
abstract class JUnit5ImplicitUsageProviderTestBase : JvmImplicitUsageProviderTestBase() {
override fun getProjectDescriptor(): LightProjectDescriptor = JUnit5ProjectDescriptor("5.10.2")
protected open class JUnit5ProjectDescriptor(private val version: String) : ProjectDescriptor(LanguageLevel.HIGHEST) {
override fun configureModule(module: Module, model: ModifiableRootModel, contentEntry: ContentEntry) {
super.configureModule(module, model, contentEntry)
model.addJUnit5Library(version)
}
}
}