mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-30 02:09:59 +07:00
junit5: cleanup before uast conversion
GitOrigin-RevId: 8cb8639f61d79b6ec74b629eabc4b95d78bbf1a1
This commit is contained in:
committed by
intellij-monorepo-bot
parent
6710ca4319
commit
7ce115a4c1
@@ -18,31 +18,30 @@ import com.intellij.execution.junit.codeInsight.references.MethodSourceReference
|
||||
import com.intellij.lang.jvm.JvmModifier
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.JavaSdkVersion
|
||||
import com.intellij.openapi.projectRoots.JavaVersionService
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager
|
||||
import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReference
|
||||
import com.intellij.psi.search.searches.ClassInheritorsSearch
|
||||
import com.intellij.psi.util.InheritanceUtil
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.psi.util.PsiUtil
|
||||
import com.intellij.psi.util.TypeConversionUtil
|
||||
import com.siyeh.ig.junit.JUnitCommonClassNames
|
||||
import com.siyeh.ig.psiutils.TestUtils
|
||||
import java.util.*
|
||||
import java.util.stream.Collectors
|
||||
|
||||
class JUnit5MalformedParameterizedInspection : AbstractBaseJavaLocalInspectionTool() {
|
||||
private object Annotations {
|
||||
internal object Annotations {
|
||||
const val TEST_INSTANCE_PER_CLASS = "@org.junit.jupiter.api.TestInstance(TestInstance.Lifecycle.PER_CLASS)"
|
||||
val EXTENDS_WITH = listOf(JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_EXTENSION_EXTEND_WITH)
|
||||
}
|
||||
|
||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
||||
val file = holder.file
|
||||
if (!JavaVersionService.getInstance().isAtLeast(file, JavaSdkVersion.JDK_1_8)) return PsiElementVisitor.EMPTY_VISITOR
|
||||
if (JavaPsiFacade.getInstance(file.project).findClass(JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PARAMETERIZED_TEST, file.resolveScope) == null) {
|
||||
if (JavaPsiFacade.getInstance(file.project).findClass(JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PARAMETERIZED_TEST,
|
||||
file.resolveScope) == null) {
|
||||
return PsiElementVisitor.EMPTY_VISITOR
|
||||
}
|
||||
return object : JavaElementVisitor() {
|
||||
@@ -53,56 +52,33 @@ class JUnit5MalformedParameterizedInspection : AbstractBaseJavaLocalInspectionTo
|
||||
if (parameterizedAnnotation.isNotEmpty()) {
|
||||
if (testAnnotation.isNotEmpty() && method.parameterList.parametersCount > 0) {
|
||||
holder.registerProblem(testAnnotation[0],
|
||||
JUnitBundle.message("junit5.malformed.parameterized.inspection.description.suspicious.combination.test.and.parameterizedtest"),
|
||||
JUnitBundle.message(
|
||||
"junit5.malformed.parameterized.inspection.description.suspicious.combination.test.and.parameterizedtest"),
|
||||
DeleteElementFix(testAnnotation[0]))
|
||||
}
|
||||
|
||||
var noMultiArgsProvider = true
|
||||
var source: PsiAnnotation? = null
|
||||
MetaAnnotationUtil.findMetaAnnotations(method, JUnitCommonClassNames.SOURCE_ANNOTATIONS).forEach {
|
||||
val singleParameterProviderChecker = SingleParameterChecker(holder)
|
||||
val methodSourceChecker = MethodSourceChecker(holder)
|
||||
val csvChecker = CsvChecker(holder)
|
||||
val usedSourceAnnotations = MetaAnnotationUtil.findMetaAnnotations(method, JUnitCommonClassNames.SOURCE_ANNOTATIONS).map {
|
||||
when (it.qualifiedName) {
|
||||
JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PROVIDER_METHOD_SOURCE -> {
|
||||
checkMethodSource(method, it)
|
||||
noMultiArgsProvider = false
|
||||
methodSourceChecker.checkMethodSource(method, it)
|
||||
}
|
||||
JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_VALUES_SOURCE -> {
|
||||
checkValuesSource(method, it)
|
||||
source = it
|
||||
singleParameterProviderChecker.checkValuesSource(method, it)
|
||||
}
|
||||
JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_ENUM_SOURCE -> {
|
||||
checkEnumSource(method, it)
|
||||
source = it
|
||||
singleParameterProviderChecker.checkEnumSource(method, it)
|
||||
}
|
||||
JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PROVIDER_CSV_FILE_SOURCE -> {
|
||||
checkFileSource(it)
|
||||
noMultiArgsProvider = false
|
||||
}
|
||||
JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PROVIDER_CSV_SOURCE -> {
|
||||
noMultiArgsProvider = false
|
||||
}
|
||||
JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PROVIDER_ARGUMENTS_SOURCE -> {
|
||||
if (source == null) {
|
||||
noMultiArgsProvider = false
|
||||
}
|
||||
}
|
||||
JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PROVIDER_ARGUMENTS_SOURCES -> {
|
||||
if (source == null) {
|
||||
val attributes = it.findAttributeValue(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME)
|
||||
noMultiArgsProvider = (attributes as? PsiArrayInitializerMemberValue)?.initializers?.isEmpty() ?: false
|
||||
}
|
||||
csvChecker.checkFileSource(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
it
|
||||
}.collect(Collectors.toMap({ it }, { it.qualifiedName }))
|
||||
|
||||
if (noMultiArgsProvider) {
|
||||
if (source == null) {
|
||||
holder.registerProblem(parameterizedAnnotation[0],
|
||||
JUnitBundle.message("junit5.malformed.parameterized.inspection.description.no.sources.are.provided"))
|
||||
}
|
||||
else if (hasMultipleParameters(method)) {
|
||||
holder.registerProblem(source!!, JUnitBundle.message("junit5.malformed.parameterized.inspection.description.multiple.parameters.are.not.supported.by.this.source"))
|
||||
}
|
||||
}
|
||||
checkConflictingSourceAnnotations(usedSourceAnnotations, method, parameterizedAnnotation[0])
|
||||
}
|
||||
else if (testAnnotation.isNotEmpty() && MetaAnnotationUtil.isMetaAnnotated(method, JUnitCommonClassNames.SOURCE_ANNOTATIONS)) {
|
||||
holder.registerProblem(testAnnotation[0],
|
||||
@@ -111,268 +87,55 @@ class JUnit5MalformedParameterizedInspection : AbstractBaseJavaLocalInspectionTo
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkEnumSource(method: PsiMethod, enumSource: PsiAnnotation) {
|
||||
// @EnumSource#value type is Class<?>, not a array
|
||||
val value = enumSource.findAttributeValue(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME)
|
||||
if (value is PsiClassObjectAccessExpression) {
|
||||
val enumType = value.operand.type
|
||||
checkSourceTypeAndParameterTypeAgree(method, value, enumType)
|
||||
checkEnumConstants(enumSource, enumType, method)
|
||||
private fun checkConflictingSourceAnnotations(usedSourceAnnotations: MutableMap<PsiAnnotation, @NlsSafe String?>,
|
||||
method: PsiMethod,
|
||||
elementToHighlight: PsiAnnotation) {
|
||||
val singleParameterProviders = usedSourceAnnotations.containsValue(JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_ENUM_SOURCE) ||
|
||||
usedSourceAnnotations.containsValue(JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_VALUES_SOURCE)
|
||||
|
||||
val multipleParametersProvider = usedSourceAnnotations.containsValue(JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PROVIDER_METHOD_SOURCE) ||
|
||||
usedSourceAnnotations.containsValue(JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PROVIDER_CSV_FILE_SOURCE) ||
|
||||
usedSourceAnnotations.containsValue(JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PROVIDER_CSV_SOURCE)
|
||||
|
||||
if (!multipleParametersProvider && !singleParameterProviders &&
|
||||
hasCustomProvider(usedSourceAnnotations)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!multipleParametersProvider) {
|
||||
if (!singleParameterProviders) {
|
||||
holder.registerProblem(elementToHighlight,
|
||||
JUnitBundle.message("junit5.malformed.parameterized.inspection.description.no.sources.are.provided"))
|
||||
}
|
||||
else if (hasMultipleParameters(method)) {
|
||||
holder.registerProblem(elementToHighlight, JUnitBundle.message(
|
||||
"junit5.malformed.parameterized.inspection.description.multiple.parameters.are.not.supported.by.this.source"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkValuesSource(method: PsiMethod, valuesSource: PsiAnnotation) {
|
||||
val possibleValues = mapOf(
|
||||
"strings" to PsiType.getJavaLangString(method.manager, method.resolveScope),
|
||||
"ints" to PsiType.INT,
|
||||
"longs" to PsiType.LONG,
|
||||
"doubles" to PsiType.DOUBLE,
|
||||
"shorts" to PsiType.SHORT,
|
||||
"bytes" to PsiType.BYTE,
|
||||
"floats" to PsiType.FLOAT,
|
||||
"chars" to PsiType.CHAR,
|
||||
"booleans" to PsiType.BOOLEAN,
|
||||
"classes" to PsiType.getJavaLangClass(method.manager, method.resolveScope))
|
||||
|
||||
for (valueKey in possibleValues.keys) {
|
||||
processArrayInAnnotationParameter(valuesSource.findDeclaredAttributeValue(valueKey)) { value -> checkSourceTypeAndParameterTypeAgree(method, value, possibleValues[valueKey]!!) }
|
||||
}
|
||||
|
||||
val attributesNumber = valuesSource.parameterList.attributes.size
|
||||
if (attributesNumber > 1) {
|
||||
holder.registerProblem(getElementToHighlight(valuesSource, method), JUnitBundle.message("junit5.malformed.parameterized.inspection.description.exactly.one.type.of.input.must.be.provided"))
|
||||
}
|
||||
else if (attributesNumber == 0) {
|
||||
holder.registerProblem(getElementToHighlight(valuesSource, method), JUnitBundle.message("junit5.malformed.parameterized.inspection.description.no.value.source.is.defined"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkFileSource(methodSource: PsiAnnotation) {
|
||||
val annotationMemberValue = methodSource.findDeclaredAttributeValue("resources")
|
||||
processArrayInAnnotationParameter(annotationMemberValue) { attributeValue ->
|
||||
for (ref in attributeValue.references) {
|
||||
if (ref.isSoft) continue
|
||||
if (ref is FileReference && ref.multiResolve(false).isEmpty()) {
|
||||
holder.registerProblem(ref.element, ref.rangeInElement,
|
||||
JUnitBundle.message("junit5.malformed.parameterized.inspection.description.file.source", attributeValue.text), *ref.quickFixes)
|
||||
private fun hasCustomProvider(annotations: MutableMap<PsiAnnotation, String?>): Boolean {
|
||||
annotations.forEach { (anno, qName) ->
|
||||
when (qName) {
|
||||
JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PROVIDER_ARGUMENTS_SOURCE -> {
|
||||
return@hasCustomProvider true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkMethodSource(method: PsiMethod, methodSource: PsiAnnotation) {
|
||||
val containingClass = method.containingClass!!
|
||||
val annotationMemberValue = methodSource.findDeclaredAttributeValue("value")
|
||||
if (annotationMemberValue == null) {
|
||||
if (methodSource.findAttributeValue(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME) == null) return
|
||||
val providerName = method.name
|
||||
val foundMethod = containingClass.findMethodBySignature(
|
||||
JavaPsiFacade.getElementFactory(method.project).createMethodFromText("void $providerName()", method), true)
|
||||
if (foundMethod != null) {
|
||||
doCheckSourceProvider(foundMethod, containingClass, methodSource, method)
|
||||
}
|
||||
else {
|
||||
highlightAbsentSourceProvider(containingClass, methodSource, providerName, method)
|
||||
}
|
||||
}
|
||||
else {
|
||||
processArrayInAnnotationParameter(annotationMemberValue) { attributeValue ->
|
||||
for (reference in attributeValue.references) {
|
||||
if (reference is MethodSourceReference) {
|
||||
val resolve = reference.resolve()
|
||||
if (resolve !is PsiMethod) {
|
||||
highlightAbsentSourceProvider(containingClass, attributeValue, reference.value, method)
|
||||
}
|
||||
else {
|
||||
val sourceProvider : PsiMethod = resolve
|
||||
doCheckSourceProvider(sourceProvider, containingClass, attributeValue, method)
|
||||
}
|
||||
JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PROVIDER_ARGUMENTS_SOURCES -> {
|
||||
val attributes = anno.findAttributeValue(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME)
|
||||
if ((attributes as? PsiArrayInitializerMemberValue)?.initializers?.isNotEmpty() == true) {
|
||||
return@hasCustomProvider true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun highlightAbsentSourceProvider(containingClass: PsiClass,
|
||||
attributeValue: PsiElement,
|
||||
sourceProviderName: String,
|
||||
method: PsiMethod) {
|
||||
var createFix: CreateMethodQuickFix? = null
|
||||
if (holder.isOnTheFly) {
|
||||
val staticModifier = if (!TestUtils.testInstancePerClass(containingClass)) " static" else ""
|
||||
createFix = CreateMethodQuickFix.createFix(containingClass,
|
||||
"private$staticModifier java.util.stream.Stream<org.junit.jupiter.params.provider.Arguments> $sourceProviderName()",
|
||||
"return null;")
|
||||
}
|
||||
holder.registerProblem(getElementToHighlight(attributeValue, method),
|
||||
JUnitBundle.message("junit5.malformed.parameterized.inspection.description.method.source.unresolved", sourceProviderName),
|
||||
createFix)
|
||||
}
|
||||
|
||||
private fun doCheckSourceProvider(sourceProvider: PsiMethod,
|
||||
containingClass: PsiClass?,
|
||||
attributeValue: PsiElement,
|
||||
method: PsiMethod) {
|
||||
val providerName = sourceProvider.name
|
||||
|
||||
if (!sourceProvider.hasModifierProperty(PsiModifier.STATIC) &&
|
||||
containingClass != null && !TestUtils.testInstancePerClass(containingClass) &&
|
||||
!implementationsTestInstanceAnnotated(containingClass)) {
|
||||
val annotation: PsiAnnotation = JavaPsiFacade.getElementFactory(containingClass.project).createAnnotationFromText(Annotations.TEST_INSTANCE_PER_CLASS, containingClass)
|
||||
val attributes: Array<PsiNameValuePair> = annotation.parameterList.attributes
|
||||
holder.registerProblem(attributeValue, JUnitBundle.message("junit5.malformed.parameterized.inspection.description.method.source.static", providerName),
|
||||
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
|
||||
QuickFixFactory.getInstance().createModifierListFix(sourceProvider, PsiModifier.STATIC, true, false),
|
||||
AddAnnotationPsiFix(JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_TEST_INSTANCE, containingClass, attributes))
|
||||
}
|
||||
else if (sourceProvider.parameterList.parametersCount != 0) {
|
||||
holder.registerProblem(getElementToHighlight(attributeValue, method), JUnitBundle.message("junit5.malformed.parameterized.inspection.description.method.source.no.params", providerName))
|
||||
}
|
||||
else {
|
||||
val componentType = getComponentType(sourceProvider.returnType, method)
|
||||
if (componentType == null) {
|
||||
holder.registerProblem(getElementToHighlight(attributeValue, method),
|
||||
JUnitBundle.message("junit5.malformed.parameterized.inspection.description.method.source.return.type", providerName))
|
||||
}
|
||||
else if (hasMultipleParameters(method) && !isArgumentsInheritor(componentType) &&
|
||||
!componentType.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) &&
|
||||
!componentType.deepComponentType.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) {
|
||||
holder.registerProblem(getElementToHighlight(attributeValue, method), JUnitBundle.message("junit5.malformed.parameterized.inspection.description.wrapped.in.arguments"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun implementationsTestInstanceAnnotated(containingClass: PsiClass) : Boolean {
|
||||
val implementations = ClassInheritorsSearch.search(containingClass, containingClass.resolveScope, true).firstOrNull {
|
||||
TestUtils.testInstancePerClass(it)
|
||||
}
|
||||
return implementations != null
|
||||
}
|
||||
|
||||
private fun processArrayInAnnotationParameter(attributeValue: PsiAnnotationMemberValue?,
|
||||
checker: (value : PsiAnnotationMemberValue) -> Unit) {
|
||||
if (attributeValue is PsiLiteral || attributeValue is PsiClassObjectAccessExpression) {
|
||||
checker.invoke(attributeValue)
|
||||
}
|
||||
else if (attributeValue is PsiArrayInitializerMemberValue) {
|
||||
for (memberValue in attributeValue.initializers) {
|
||||
processArrayInAnnotationParameter(memberValue, checker)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkEnumConstants(enumSource: PsiAnnotation,
|
||||
enumType: PsiType,
|
||||
method: PsiMethod) {
|
||||
val mode = enumSource.findAttributeValue("mode")
|
||||
if (mode is PsiReferenceExpression && ("INCLUDE" == mode.referenceName || "EXCLUDE" == mode.referenceName)) {
|
||||
val allEnumConstants = (PsiUtil.resolveClassInClassTypeOnly(enumType) ?: return).fields
|
||||
.filterIsInstance<PsiEnumConstant>()
|
||||
.map { it.name }
|
||||
.toSet()
|
||||
val definedConstants = mutableSetOf<String>()
|
||||
processArrayInAnnotationParameter(enumSource.findAttributeValue("names")) { name ->
|
||||
if (name is PsiLiteralExpression) {
|
||||
val value = name.value
|
||||
if (value is String) {
|
||||
if (!allEnumConstants.contains(value)) {
|
||||
holder.registerProblem(getElementToHighlight(name, method), JUnitBundle.message("junit5.malformed.parameterized.inspection.description.unresolve.enum"))
|
||||
}
|
||||
else if (!definedConstants.add(value)) {
|
||||
holder.registerProblem(getElementToHighlight(name, method), JUnitBundle.message("junit5.malformed.parameterized.inspection.description.duplicated.enum"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkSourceTypeAndParameterTypeAgree(method: PsiMethod,
|
||||
attributeValue: PsiAnnotationMemberValue,
|
||||
componentType: PsiType) {
|
||||
val parameters = method.parameterList.parameters
|
||||
if (parameters.size == 1) {
|
||||
val paramType = parameters[0].type
|
||||
if (!paramType.isAssignableFrom(componentType) && !isArgumentsInheritor(componentType)) {
|
||||
if (componentType.equalsToText(CommonClassNames.JAVA_LANG_STRING)) {
|
||||
//implicit conversion to primitive/wrapper
|
||||
if (TypeConversionUtil.isPrimitiveAndNotNullOrWrapper(paramType)) return
|
||||
val psiClass = PsiUtil.resolveClassInClassTypeOnly(paramType)
|
||||
//implicit conversion to enum
|
||||
if (psiClass != null) {
|
||||
if (psiClass.isEnum && psiClass.findFieldByName((attributeValue as PsiLiteral).value as String?, false) != null) return
|
||||
//implicit java time conversion
|
||||
val qualifiedName = psiClass.qualifiedName
|
||||
if (qualifiedName != null) {
|
||||
if (qualifiedName.startsWith("java.time.")) return
|
||||
if (qualifiedName == "java.nio.file.Path") return
|
||||
}
|
||||
|
||||
val factoryMethod: (PsiMethod) -> Boolean = {
|
||||
!it.hasModifier(JvmModifier.PRIVATE) &&
|
||||
it.parameterList.parametersCount == 1 &&
|
||||
it.parameterList.parameters[0].type.equalsToText(CommonClassNames.JAVA_LANG_STRING)
|
||||
}
|
||||
|
||||
if (!psiClass.hasModifier(JvmModifier.ABSTRACT) && psiClass.constructors.find(factoryMethod) != null) return
|
||||
if (psiClass.methods.find { it.hasModifier(JvmModifier.STATIC) && factoryMethod(it) } != null) return
|
||||
}
|
||||
}
|
||||
else if (componentType.equalsToText("org.junit.jupiter.params.provider.NullEnum")) {
|
||||
val psiClass = PsiUtil.resolveClassInClassTypeOnly(paramType)
|
||||
if (psiClass != null && psiClass.isEnum) return
|
||||
}
|
||||
if (AnnotationUtil.isAnnotated(parameters[0], JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_CONVERTER_CONVERT_WITH, 0)) return
|
||||
holder.registerProblem(getElementToHighlight(attributeValue, method, parameters[0]),
|
||||
JUnitBundle.message("junit5.malformed.parameterized.inspection.description.method.source.assignable", componentType.presentableText, paramType.presentableText))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isArgumentsInheritor(componentType: PsiType): Boolean {
|
||||
return InheritanceUtil.isInheritor(componentType, JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PROVIDER_ARGUMENTS)
|
||||
}
|
||||
|
||||
private fun getComponentType(returnType: PsiType?, method: PsiMethod): PsiType? {
|
||||
val collectionItemType = JavaGenericsUtil.getCollectionItemType(returnType, method.resolveScope)
|
||||
if (collectionItemType != null) {
|
||||
return collectionItemType
|
||||
}
|
||||
|
||||
if (InheritanceUtil.isInheritor(returnType, CommonClassNames.JAVA_UTIL_STREAM_INT_STREAM)) return PsiType.INT
|
||||
if (InheritanceUtil.isInheritor(returnType, CommonClassNames.JAVA_UTIL_STREAM_LONG_STREAM)) return PsiType.LONG
|
||||
if (InheritanceUtil.isInheritor(returnType, CommonClassNames.JAVA_UTIL_STREAM_DOUBLE_STREAM)) return PsiType.DOUBLE
|
||||
|
||||
val streamItemType = PsiUtil.substituteTypeParameter(returnType, CommonClassNames.JAVA_UTIL_STREAM_STREAM, 0, true)
|
||||
if (streamItemType != null) {
|
||||
return streamItemType
|
||||
}
|
||||
|
||||
return PsiUtil.substituteTypeParameter(returnType, CommonClassNames.JAVA_UTIL_ITERATOR, 0, true)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getElementToHighlight(attributeValue: PsiElement,
|
||||
method: PsiMethod,
|
||||
default: PsiNameIdentifierOwner = method) =
|
||||
if (PsiTreeUtil.isAncestor(method, attributeValue, true)) attributeValue else default.nameIdentifier ?: default
|
||||
|
||||
private fun hasMultipleParameters(method: PsiMethod): Boolean {
|
||||
val containingClass = method.containingClass
|
||||
return containingClass != null &&
|
||||
method.parameterList.parameters
|
||||
.filter {
|
||||
!InheritanceUtil.isInheritor(it.type, JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_TEST_INFO) &&
|
||||
!InheritanceUtil.isInheritor(it.type, JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_TEST_REPORTER)
|
||||
}
|
||||
.count() > 1
|
||||
&& !MetaAnnotationUtil.isMetaAnnotated(method, Annotations.EXTENDS_WITH)
|
||||
&& !MetaAnnotationUtil.isMetaAnnotatedInHierarchy(containingClass, Annotations.EXTENDS_WITH)
|
||||
}
|
||||
}
|
||||
|
||||
class ChangeAnnotationFix(testAnnotation: PsiAnnotation, private val targetAnnotation: String) : LocalQuickFixAndIntentionActionOnPsiElement(testAnnotation) {
|
||||
class ChangeAnnotationFix(testAnnotation: PsiAnnotation,
|
||||
private val targetAnnotation: String) : LocalQuickFixAndIntentionActionOnPsiElement(testAnnotation) {
|
||||
override fun getFamilyName(): String = JUnitBundle.message("junit5.malformed.parameterized.fix.family.name")
|
||||
|
||||
override fun invoke(project: Project, file: PsiFile, editor: Editor?, startElement: PsiElement, endElement: PsiElement) {
|
||||
@@ -383,3 +146,249 @@ class ChangeAnnotationFix(testAnnotation: PsiAnnotation, private val targetAnnot
|
||||
override fun getText(): String = JUnitBundle.message("junit5.malformed.parameterized.fix.text", StringUtil.getShortName(targetAnnotation))
|
||||
|
||||
}
|
||||
|
||||
private class CsvChecker(val holder: ProblemsHolder) {
|
||||
fun checkFileSource(methodSource: PsiAnnotation) {
|
||||
val annotationMemberValue = methodSource.findDeclaredAttributeValue("resources")
|
||||
processArrayInAnnotationParameter(annotationMemberValue) { attributeValue ->
|
||||
for (ref in attributeValue.references) {
|
||||
if (ref.isSoft) continue
|
||||
if (ref is FileReference && ref.multiResolve(false).isEmpty()) {
|
||||
holder.registerProblem(ref.element, ref.rangeInElement,
|
||||
JUnitBundle.message("junit5.malformed.parameterized.inspection.description.file.source",
|
||||
attributeValue.text), *ref.quickFixes)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SingleParameterChecker(val holder: ProblemsHolder) {
|
||||
fun checkEnumSource(method: PsiMethod, enumSource: PsiAnnotation) {
|
||||
// @EnumSource#value type is Class<?>, not a array
|
||||
val value = enumSource.findAttributeValue(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME)
|
||||
if (value is PsiClassObjectAccessExpression) {
|
||||
val enumType = value.operand.type
|
||||
checkSourceTypeAndParameterTypeAgree(method, value, enumType)
|
||||
checkEnumConstants(enumSource, enumType, method)
|
||||
}
|
||||
}
|
||||
|
||||
fun checkValuesSource(method: PsiMethod, valuesSource: PsiAnnotation) {
|
||||
val possibleValues = mapOf(
|
||||
"strings" to PsiType.getJavaLangString(method.manager, method.resolveScope),
|
||||
"ints" to PsiType.INT,
|
||||
"longs" to PsiType.LONG,
|
||||
"doubles" to PsiType.DOUBLE,
|
||||
"shorts" to PsiType.SHORT,
|
||||
"bytes" to PsiType.BYTE,
|
||||
"floats" to PsiType.FLOAT,
|
||||
"chars" to PsiType.CHAR,
|
||||
"booleans" to PsiType.BOOLEAN,
|
||||
"classes" to PsiType.getJavaLangClass(method.manager, method.resolveScope))
|
||||
|
||||
for (valueKey in possibleValues.keys) {
|
||||
processArrayInAnnotationParameter(valuesSource.findDeclaredAttributeValue(valueKey)) { value ->
|
||||
checkSourceTypeAndParameterTypeAgree(method, value, possibleValues[valueKey]!!)
|
||||
}
|
||||
}
|
||||
|
||||
val attributesNumber = valuesSource.parameterList.attributes.size
|
||||
if (attributesNumber > 1) {
|
||||
holder.registerProblem(getElementToHighlight(valuesSource, method), JUnitBundle.message(
|
||||
"junit5.malformed.parameterized.inspection.description.exactly.one.type.of.input.must.be.provided"))
|
||||
}
|
||||
else if (attributesNumber == 0) {
|
||||
holder.registerProblem(getElementToHighlight(valuesSource, method),
|
||||
JUnitBundle.message("junit5.malformed.parameterized.inspection.description.no.value.source.is.defined"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkEnumConstants(enumSource: PsiAnnotation,
|
||||
enumType: PsiType,
|
||||
method: PsiMethod) {
|
||||
val mode = enumSource.findAttributeValue("mode")
|
||||
if (mode is PsiReferenceExpression && ("INCLUDE" == mode.referenceName || "EXCLUDE" == mode.referenceName)) {
|
||||
val allEnumConstants = (PsiUtil.resolveClassInClassTypeOnly(enumType) ?: return).fields
|
||||
.filterIsInstance<PsiEnumConstant>()
|
||||
.map { it.name }
|
||||
.toSet()
|
||||
val definedConstants = mutableSetOf<String>()
|
||||
processArrayInAnnotationParameter(enumSource.findAttributeValue("names")) { name ->
|
||||
if (name is PsiLiteralExpression) {
|
||||
val value = name.value
|
||||
if (value is String) {
|
||||
if (!allEnumConstants.contains(value)) {
|
||||
holder.registerProblem(getElementToHighlight(name, method),
|
||||
JUnitBundle.message("junit5.malformed.parameterized.inspection.description.unresolve.enum"))
|
||||
}
|
||||
else if (!definedConstants.add(value)) {
|
||||
holder.registerProblem(getElementToHighlight(name, method),
|
||||
JUnitBundle.message("junit5.malformed.parameterized.inspection.description.duplicated.enum"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkSourceTypeAndParameterTypeAgree(method: PsiMethod,
|
||||
attributeValue: PsiAnnotationMemberValue,
|
||||
componentType: PsiType) {
|
||||
val parameters = method.parameterList.parameters
|
||||
if (parameters.size == 1) {
|
||||
val paramType = parameters[0].type
|
||||
if (!paramType.isAssignableFrom(componentType) && !InheritanceUtil.isInheritor(componentType,
|
||||
JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PROVIDER_ARGUMENTS)) {
|
||||
if (componentType.equalsToText(CommonClassNames.JAVA_LANG_STRING)) {
|
||||
//implicit conversion to primitive/wrapper
|
||||
if (TypeConversionUtil.isPrimitiveAndNotNullOrWrapper(paramType)) return
|
||||
val psiClass = PsiUtil.resolveClassInClassTypeOnly(paramType)
|
||||
//implicit conversion to enum
|
||||
if (psiClass != null) {
|
||||
if (psiClass.isEnum && psiClass.findFieldByName((attributeValue as PsiLiteral).value as String?, false) != null) return
|
||||
//implicit java time conversion
|
||||
val qualifiedName = psiClass.qualifiedName
|
||||
if (qualifiedName != null) {
|
||||
if (qualifiedName.startsWith("java.time.")) return
|
||||
if (qualifiedName == "java.nio.file.Path") return
|
||||
}
|
||||
|
||||
val factoryMethod: (PsiMethod) -> Boolean = {
|
||||
!it.hasModifier(JvmModifier.PRIVATE) &&
|
||||
it.parameterList.parametersCount == 1 &&
|
||||
it.parameterList.parameters[0].type.equalsToText(CommonClassNames.JAVA_LANG_STRING)
|
||||
}
|
||||
|
||||
if (!psiClass.hasModifier(JvmModifier.ABSTRACT) && psiClass.constructors.find(factoryMethod) != null) return
|
||||
if (psiClass.methods.find { it.hasModifier(JvmModifier.STATIC) && factoryMethod(it) } != null) return
|
||||
}
|
||||
}
|
||||
else if (componentType.equalsToText("org.junit.jupiter.params.provider.NullEnum")) {
|
||||
val psiClass = PsiUtil.resolveClassInClassTypeOnly(paramType)
|
||||
if (psiClass != null && psiClass.isEnum) return
|
||||
}
|
||||
if (AnnotationUtil.isAnnotated(parameters[0], JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_CONVERTER_CONVERT_WITH, 0)) return
|
||||
holder.registerProblem(getElementToHighlight(attributeValue, method, parameters[0]),
|
||||
JUnitBundle.message("junit5.malformed.parameterized.inspection.description.method.source.assignable",
|
||||
componentType.presentableText, paramType.presentableText))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MethodSourceChecker(val problemsHolder: ProblemsHolder) {
|
||||
fun checkMethodSource(method: PsiMethod, methodSource: PsiAnnotation) {
|
||||
val containingClass = method.containingClass!!
|
||||
val annotationMemberValue = methodSource.findDeclaredAttributeValue("value")
|
||||
if (annotationMemberValue == null) {
|
||||
if (methodSource.findAttributeValue(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME) == null) return
|
||||
val foundMethod = containingClass.findMethodsByName(method.name, true).singleOrNull { it.parameters.isEmpty() }
|
||||
if (foundMethod != null) {
|
||||
doCheckSourceProvider(foundMethod, containingClass, methodSource, method)
|
||||
}
|
||||
else {
|
||||
highlightAbsentSourceProvider(containingClass, methodSource, method.name, method)
|
||||
}
|
||||
}
|
||||
else {
|
||||
processArrayInAnnotationParameter(annotationMemberValue) { attributeValue ->
|
||||
for (reference in attributeValue.references) {
|
||||
if (reference is MethodSourceReference) {
|
||||
val resolve = reference.resolve()
|
||||
if (resolve !is PsiMethod) {
|
||||
highlightAbsentSourceProvider(containingClass, attributeValue, reference.value, method)
|
||||
}
|
||||
else {
|
||||
doCheckSourceProvider(resolve, containingClass, attributeValue, method)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun implementationsTestInstanceAnnotated(containingClass: PsiClass): Boolean {
|
||||
val implementations = ClassInheritorsSearch.search(containingClass, containingClass.resolveScope, true).firstOrNull {
|
||||
TestUtils.testInstancePerClass(it)
|
||||
}
|
||||
return implementations != null
|
||||
}
|
||||
|
||||
private fun getComponentType(returnType: PsiType?, method: PsiMethod): PsiType? {
|
||||
val collectionItemType = JavaGenericsUtil.getCollectionItemType(returnType, method.resolveScope)
|
||||
if (collectionItemType != null) {
|
||||
return collectionItemType
|
||||
}
|
||||
|
||||
if (InheritanceUtil.isInheritor(returnType, CommonClassNames.JAVA_UTIL_STREAM_INT_STREAM)) return PsiType.INT
|
||||
if (InheritanceUtil.isInheritor(returnType, CommonClassNames.JAVA_UTIL_STREAM_LONG_STREAM)) return PsiType.LONG
|
||||
if (InheritanceUtil.isInheritor(returnType, CommonClassNames.JAVA_UTIL_STREAM_DOUBLE_STREAM)) return PsiType.DOUBLE
|
||||
|
||||
val streamItemType = PsiUtil.substituteTypeParameter(returnType, CommonClassNames.JAVA_UTIL_STREAM_STREAM, 0, true)
|
||||
if (streamItemType != null) {
|
||||
return streamItemType
|
||||
}
|
||||
|
||||
return PsiUtil.substituteTypeParameter(returnType, CommonClassNames.JAVA_UTIL_ITERATOR, 0, true)
|
||||
}
|
||||
|
||||
private fun doCheckSourceProvider(sourceProvider: PsiMethod,
|
||||
containingClass: PsiClass?,
|
||||
attributeValue: PsiElement,
|
||||
method: PsiMethod) {
|
||||
val providerName = sourceProvider.name
|
||||
|
||||
if (!sourceProvider.hasModifierProperty(PsiModifier.STATIC) &&
|
||||
containingClass != null && !TestUtils.testInstancePerClass(containingClass) &&
|
||||
!implementationsTestInstanceAnnotated(containingClass)) {
|
||||
val annotation: PsiAnnotation = JavaPsiFacade.getElementFactory(containingClass.project).createAnnotationFromText(
|
||||
JUnit5MalformedParameterizedInspection.Annotations.TEST_INSTANCE_PER_CLASS, containingClass)
|
||||
val attributes: Array<PsiNameValuePair> = annotation.parameterList.attributes
|
||||
problemsHolder.registerProblem(attributeValue,
|
||||
JUnitBundle.message("junit5.malformed.parameterized.inspection.description.method.source.static",
|
||||
providerName),
|
||||
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
|
||||
QuickFixFactory.getInstance().createModifierListFix(sourceProvider, PsiModifier.STATIC, true, false),
|
||||
AddAnnotationPsiFix(JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_TEST_INSTANCE, containingClass,
|
||||
attributes))
|
||||
}
|
||||
else if (sourceProvider.parameterList.parametersCount != 0) {
|
||||
problemsHolder.registerProblem(getElementToHighlight(attributeValue, method),
|
||||
JUnitBundle.message("junit5.malformed.parameterized.inspection.description.method.source.no.params",
|
||||
providerName))
|
||||
}
|
||||
else {
|
||||
val componentType = getComponentType(sourceProvider.returnType, method)
|
||||
if (componentType == null) {
|
||||
problemsHolder.registerProblem(getElementToHighlight(attributeValue, method),
|
||||
JUnitBundle.message(
|
||||
"junit5.malformed.parameterized.inspection.description.method.source.return.type", providerName))
|
||||
}
|
||||
else if (hasMultipleParameters(
|
||||
method) && !InheritanceUtil.isInheritor(componentType, JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PROVIDER_ARGUMENTS) &&
|
||||
!componentType.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) &&
|
||||
!componentType.deepComponentType.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) {
|
||||
problemsHolder.registerProblem(getElementToHighlight(attributeValue, method),
|
||||
JUnitBundle.message("junit5.malformed.parameterized.inspection.description.wrapped.in.arguments"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun highlightAbsentSourceProvider(containingClass: PsiClass,
|
||||
attributeValue: PsiElement,
|
||||
sourceProviderName: String,
|
||||
method: PsiMethod) {
|
||||
var createFix: CreateMethodQuickFix? = null
|
||||
if (problemsHolder.isOnTheFly) {
|
||||
val staticModifier = if (!TestUtils.testInstancePerClass(containingClass)) " static" else ""
|
||||
createFix = CreateMethodQuickFix.createFix(containingClass,
|
||||
"private$staticModifier java.util.stream.Stream<org.junit.jupiter.params.provider.Arguments> $sourceProviderName()",
|
||||
"return null;")
|
||||
}
|
||||
problemsHolder.registerProblem(getElementToHighlight(attributeValue, method),
|
||||
JUnitBundle.message("junit5.malformed.parameterized.inspection.description.method.source.unresolved",
|
||||
sourceProviderName),
|
||||
createFix)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.execution.junit.codeInsight
|
||||
|
||||
import com.intellij.codeInsight.MetaAnnotationUtil
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.util.InheritanceUtil
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.siyeh.ig.junit.JUnitCommonClassNames
|
||||
|
||||
internal fun processArrayInAnnotationParameter(attributeValue: PsiAnnotationMemberValue?,
|
||||
checker: (value: PsiAnnotationMemberValue) -> Unit) {
|
||||
if (attributeValue is PsiLiteral || attributeValue is PsiClassObjectAccessExpression) {
|
||||
checker.invoke(attributeValue)
|
||||
}
|
||||
else if (attributeValue is PsiArrayInitializerMemberValue) {
|
||||
for (memberValue in attributeValue.initializers) {
|
||||
processArrayInAnnotationParameter(memberValue, checker)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun hasMultipleParameters(method: PsiMethod): Boolean {
|
||||
val containingClass = method.containingClass
|
||||
return containingClass != null &&
|
||||
method.parameterList.parameters
|
||||
.filter {
|
||||
!InheritanceUtil.isInheritor(it.type, JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_TEST_INFO) &&
|
||||
!InheritanceUtil.isInheritor(it.type, JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_TEST_REPORTER)
|
||||
}
|
||||
.count() > 1
|
||||
&& !MetaAnnotationUtil.isMetaAnnotated(method, JUnit5MalformedParameterizedInspection.Annotations.EXTENDS_WITH)
|
||||
&& !MetaAnnotationUtil.isMetaAnnotatedInHierarchy(containingClass, JUnit5MalformedParameterizedInspection.Annotations.EXTENDS_WITH)
|
||||
}
|
||||
|
||||
internal fun getElementToHighlight(attributeValue: PsiElement,
|
||||
method: PsiMethod,
|
||||
default: PsiNameIdentifierOwner = method): PsiElement =
|
||||
if (PsiTreeUtil.isAncestor(method, attributeValue, true)) attributeValue else default.nameIdentifier ?: default
|
||||
@@ -33,8 +33,8 @@ class ValueSourcesTest {
|
||||
<warning descr="No value source is defined">@ValueSource()</warning>
|
||||
void testWithNoValues(int i) { }
|
||||
|
||||
@ParameterizedTest
|
||||
<warning descr="Multiple parameters are not supported by this source">@ValueSource(ints = 1)</warning>
|
||||
<warning descr="Multiple parameters are not supported by this source">@ParameterizedTest</warning>
|
||||
@ValueSource(ints = 1)
|
||||
void testWithValuesMultipleParams(int i, int j) { }
|
||||
|
||||
@ParameterizedTest
|
||||
|
||||
@@ -7,8 +7,8 @@ class MyJunit5 {
|
||||
@ValueSource(strings = "foo")
|
||||
void testWithRegularParameterResolver(String argument, TestInfo testReporter) {
|
||||
}
|
||||
@ParameterizedTest
|
||||
<warning descr="Multiple parameters are not supported by this source">@ValueSource(strings = "foo")</warning>
|
||||
<warning descr="Multiple parameters are not supported by this source">@ParameterizedTest</warning>
|
||||
@ValueSource(strings = "foo")
|
||||
void testWithMultipleParams(String argument, int i) {
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user