From 1d77c05b003a8bcfdafa94a2f7417df02057ab5e Mon Sep 17 00:00:00 2001 From: 7410 <85879080+O7410@users.noreply.github.com> Date: Sun, 11 Aug 2024 02:07:09 +0300 Subject: [PATCH 1/2] Add inspection for targeting the same class multiple times in a mixin --- .../MixinDuplicateTargetInspection.kt | 145 ++++++++++++++++++ src/main/resources/META-INF/plugin.xml | 8 + 2 files changed, 153 insertions(+) create mode 100644 src/main/kotlin/platform/mixin/inspection/MixinDuplicateTargetInspection.kt diff --git a/src/main/kotlin/platform/mixin/inspection/MixinDuplicateTargetInspection.kt b/src/main/kotlin/platform/mixin/inspection/MixinDuplicateTargetInspection.kt new file mode 100644 index 000000000..cad19feda --- /dev/null +++ b/src/main/kotlin/platform/mixin/inspection/MixinDuplicateTargetInspection.kt @@ -0,0 +1,145 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.inspection + +import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.MIXIN +import com.demonwav.mcdev.platform.mixin.util.findClassNodeByPsiClass +import com.demonwav.mcdev.platform.mixin.util.findClassNodeByQualifiedName +import com.demonwav.mcdev.platform.mixin.util.mixinTargets +import com.demonwav.mcdev.util.computeStringArray +import com.demonwav.mcdev.util.constantStringValue +import com.demonwav.mcdev.util.findModule +import com.demonwav.mcdev.util.resolveClass +import com.demonwav.mcdev.util.resolveClassArray +import com.intellij.codeInsight.intention.QuickFixFactory +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.psi.JavaElementVisitor +import com.intellij.psi.PsiAnnotation +import com.intellij.psi.PsiArrayInitializerMemberValue +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiElementVisitor +import com.intellij.psi.PsiExpression +import org.objectweb.asm.tree.ClassNode + +class MixinDuplicateTargetInspection : MixinInspection() { + + override fun getStaticDescription() = + "Targeting the same class multiple times in a mixin is redundant" + + override fun buildVisitor(holder: ProblemsHolder): PsiElementVisitor = Visitor(holder) + + private class Visitor(private val holder: ProblemsHolder) : JavaElementVisitor() { + + override fun visitClass(psiClass: PsiClass) { + val mixinAnnotation = psiClass.modifierList?.findAnnotation(MIXIN) ?: return + + if (psiClass.mixinTargets.size != psiClass.mixinTargets.distinct().size) { + goOverValues(psiClass, mixinAnnotation) + goOverTargets(psiClass, mixinAnnotation) + } + } + + private fun goOverValues(psiClass: PsiClass, mixinAnnotation: PsiAnnotation) { + + // Read class targets (value) + val value = mixinAnnotation.findDeclaredAttributeValue("value") ?: return + if (value is PsiArrayInitializerMemberValue) { + val classesElements = value.children.filterIsInstance() + val classTargets = value.resolveClassArray() + for (classTargetIndex in classTargets.indices) { + val classMixinTarget = classTargets[classTargetIndex] + val classNode = findClassNodeByPsiClass(classMixinTarget) + registerProblemIfProblematic( + psiClass, + classNode, + classTargetIndex, + classesElements[classTargetIndex] + ) + } + } else { + val targetClass = value.resolveClass() ?: return + val classNode = findClassNodeByPsiClass(targetClass) + val elementToDelete = mixinAnnotation.parameterList.attributes.find { it.name == "value" } ?: value + registerProblemIfProblematic(psiClass, classNode, 0, elementToDelete) + } + } + + private fun goOverTargets(psiClass: PsiClass, mixinAnnotation: PsiAnnotation) { + // Read string targets (targets) + val targets = mixinAnnotation.findDeclaredAttributeValue("targets") ?: return + val classTargetNames = targets.computeStringArray() + val valueTargetCount = mixinAnnotation.findDeclaredAttributeValue("value")?.resolveClassArray()?.size ?: 0 + if (targets is PsiArrayInitializerMemberValue) { + + val classChildren = targets.children.filterIsInstance() + + for (i in classTargetNames.indices) { + val targetName = classTargetNames[i] + + val classNode = findClassNodeByQualifiedName( + psiClass.project, + psiClass.findModule(), + targetName.replace('/', '.'), + ) + registerProblemIfProblematic(psiClass, classNode, valueTargetCount + i, classChildren[i]) + } + } else { + if (targets.constantStringValue == null) return + val classNode = findClassNodeByQualifiedName( + psiClass.project, + psiClass.findModule(), + targets.constantStringValue!!.replace('/', '.'), + ) + val elementToDelete = mixinAnnotation.parameterList.attributes.find { it.name == "targets" } ?: targets + registerProblemIfProblematic(psiClass, classNode, valueTargetCount, elementToDelete) + } + } + + private fun isClassNodeInTargets( + psiClass: PsiClass, + classNodeToCheck: ClassNode?, + indexToIgnore: Int + ): Boolean { + return classNodeToCheck in psiClass.mixinTargets.subList(0, indexToIgnore) || + classNodeToCheck in psiClass.mixinTargets.subList(indexToIgnore + 1, psiClass.mixinTargets.size) + } + + private fun registerProblemForDuplicate(duplicateExpression: PsiElement) { + holder.registerProblem( + duplicateExpression, + "Duplicate target is redundant", + QuickFixFactory.getInstance().createDeleteFix(duplicateExpression) + ) + } + + private fun registerProblemIfProblematic( + psiClass: PsiClass, + classNode: ClassNode?, + indexToIgnore: Int, + possibleDuplicate: PsiElement + ) { + if (isClassNodeInTargets(psiClass, classNode, indexToIgnore)) { + registerProblemForDuplicate(possibleDuplicate) + } + } + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 27a38c6a3..420595b23 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -830,6 +830,14 @@ level="ERROR" hasStaticDescription="true" implementationClass="com.demonwav.mcdev.platform.mixin.inspection.MixinTargetInspection"/> + Date: Wed, 14 Aug 2024 01:50:58 +0300 Subject: [PATCH 2/2] WIP - Added tests and modified way of detecting duplicates --- .../multipleTargetClasses/SimpleClass1.java | 14 + .../multipleTargetClasses/SimpleClass2.java | 14 + .../multipleTargetClasses/SimpleClass3.java | 14 + .../MixinDuplicateTargetInspection.kt | 121 +++--- .../MixinDuplicateTargetInspectionTest.kt | 350 ++++++++++++++++++ 5 files changed, 469 insertions(+), 44 deletions(-) create mode 100644 mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/multipleTargetClasses/SimpleClass1.java create mode 100644 mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/multipleTargetClasses/SimpleClass2.java create mode 100644 mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/multipleTargetClasses/SimpleClass3.java create mode 100644 src/test/kotlin/platform/mixin/MixinDuplicateTargetInspectionTest.kt diff --git a/mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/multipleTargetClasses/SimpleClass1.java b/mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/multipleTargetClasses/SimpleClass1.java new file mode 100644 index 000000000..99381ce46 --- /dev/null +++ b/mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/multipleTargetClasses/SimpleClass1.java @@ -0,0 +1,14 @@ +/* + * Minecraft Dev for IntelliJ + * + * https://minecraftdev.org + * + * Copyright (c) 2021 minecraft-dev + * + * MIT License + */ + +package com.demonwav.mcdev.mixintestdata.multipleTargetClasses; + +public class SimpleClass1 { +} diff --git a/mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/multipleTargetClasses/SimpleClass2.java b/mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/multipleTargetClasses/SimpleClass2.java new file mode 100644 index 000000000..e3d530ca4 --- /dev/null +++ b/mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/multipleTargetClasses/SimpleClass2.java @@ -0,0 +1,14 @@ +/* + * Minecraft Dev for IntelliJ + * + * https://minecraftdev.org + * + * Copyright (c) 2021 minecraft-dev + * + * MIT License + */ + +package com.demonwav.mcdev.mixintestdata.multipleTargetClasses; + +public class SimpleClass2 { +} diff --git a/mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/multipleTargetClasses/SimpleClass3.java b/mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/multipleTargetClasses/SimpleClass3.java new file mode 100644 index 000000000..c62650ffd --- /dev/null +++ b/mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/multipleTargetClasses/SimpleClass3.java @@ -0,0 +1,14 @@ +/* + * Minecraft Dev for IntelliJ + * + * https://minecraftdev.org + * + * Copyright (c) 2021 minecraft-dev + * + * MIT License + */ + +package com.demonwav.mcdev.mixintestdata.multipleTargetClasses; + +public class SimpleClass3 { +} diff --git a/src/main/kotlin/platform/mixin/inspection/MixinDuplicateTargetInspection.kt b/src/main/kotlin/platform/mixin/inspection/MixinDuplicateTargetInspection.kt index cad19feda..e8ed272d0 100644 --- a/src/main/kotlin/platform/mixin/inspection/MixinDuplicateTargetInspection.kt +++ b/src/main/kotlin/platform/mixin/inspection/MixinDuplicateTargetInspection.kt @@ -28,22 +28,27 @@ import com.demonwav.mcdev.util.computeStringArray import com.demonwav.mcdev.util.constantStringValue import com.demonwav.mcdev.util.findModule import com.demonwav.mcdev.util.resolveClass -import com.demonwav.mcdev.util.resolveClassArray import com.intellij.codeInsight.intention.QuickFixFactory +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement import com.intellij.codeInspection.ProblemsHolder +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project import com.intellij.psi.JavaElementVisitor import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiArrayInitializerMemberValue import com.intellij.psi.PsiClass +import com.intellij.psi.PsiClassObjectAccessExpression import com.intellij.psi.PsiElement import com.intellij.psi.PsiElementVisitor import com.intellij.psi.PsiExpression +import com.intellij.psi.PsiFile +import org.jetbrains.plugins.groovy.intentions.style.inference.resolve import org.objectweb.asm.tree.ClassNode class MixinDuplicateTargetInspection : MixinInspection() { - override fun getStaticDescription() = - "Targeting the same class multiple times in a mixin is redundant" + override fun getStaticDescription() = "Targeting the same class multiple times in a mixin is redundant" override fun buildVisitor(holder: ProblemsHolder): PsiElementVisitor = Visitor(holder) @@ -52,94 +57,122 @@ class MixinDuplicateTargetInspection : MixinInspection() { override fun visitClass(psiClass: PsiClass) { val mixinAnnotation = psiClass.modifierList?.findAnnotation(MIXIN) ?: return - if (psiClass.mixinTargets.size != psiClass.mixinTargets.distinct().size) { - goOverValues(psiClass, mixinAnnotation) - goOverTargets(psiClass, mixinAnnotation) + val mixinTargets = psiClass.mixinTargets + + if (mixinTargets.size != mixinTargets.distinct().size) { + val nonDuplicates = mutableListOf() + for (i in mixinTargets.indices) { + val mixinTarget = mixinTargets[i] + if (mixinTarget !in mixinTargets.subList( + i + 1, mixinTargets.size + ) && mixinTarget !in mixinTargets.subList(0, i) + ) { + nonDuplicates.add(mixinTarget) + } + } + + goOverValues(nonDuplicates, mixinAnnotation) + goOverTargets(psiClass, nonDuplicates, mixinAnnotation) } } - private fun goOverValues(psiClass: PsiClass, mixinAnnotation: PsiAnnotation) { + private fun goOverValues(nonDuplicates: List, mixinAnnotation: PsiAnnotation) { // Read class targets (value) val value = mixinAnnotation.findDeclaredAttributeValue("value") ?: return if (value is PsiArrayInitializerMemberValue) { - val classesElements = value.children.filterIsInstance() - val classTargets = value.resolveClassArray() - for (classTargetIndex in classTargets.indices) { - val classMixinTarget = classTargets[classTargetIndex] - val classNode = findClassNodeByPsiClass(classMixinTarget) + val classElements = value.children.filterIsInstance() + for (classTargetIndex in classElements.indices) { + val expression = classElements[classTargetIndex] as? PsiClassObjectAccessExpression ?: continue + val targetPsiClass = expression.operand.type.resolve() ?: continue + val targetClassNode = findClassNodeByPsiClass(targetPsiClass) + registerProblemIfProblematic( - psiClass, - classNode, - classTargetIndex, - classesElements[classTargetIndex] + nonDuplicates, + targetClassNode, + expression, + QuickFixFactory.getInstance().createDeleteFix(expression) ) } } else { val targetClass = value.resolveClass() ?: return val classNode = findClassNodeByPsiClass(targetClass) - val elementToDelete = mixinAnnotation.parameterList.attributes.find { it.name == "value" } ?: value - registerProblemIfProblematic(psiClass, classNode, 0, elementToDelete) + val possibleQuickFix = RemoveDuplicateTargetFix(mixinAnnotation, "value") + registerProblemIfProblematic(nonDuplicates, classNode, value, possibleQuickFix) } } - private fun goOverTargets(psiClass: PsiClass, mixinAnnotation: PsiAnnotation) { + private fun goOverTargets(psiClass: PsiClass, nonDuplicates: List, mixinAnnotation: PsiAnnotation) { // Read string targets (targets) val targets = mixinAnnotation.findDeclaredAttributeValue("targets") ?: return val classTargetNames = targets.computeStringArray() - val valueTargetCount = mixinAnnotation.findDeclaredAttributeValue("value")?.resolveClassArray()?.size ?: 0 if (targets is PsiArrayInitializerMemberValue) { - val classChildren = targets.children.filterIsInstance() - for (i in classTargetNames.indices) { - val targetName = classTargetNames[i] + // TODO: may be misaligned if there are numbers for example in the class array because they will have different lengths + for (classTargetIndex in classTargetNames.indices) { + val targetName = classTargetNames[classTargetIndex] val classNode = findClassNodeByQualifiedName( psiClass.project, psiClass.findModule(), targetName.replace('/', '.'), ) - registerProblemIfProblematic(psiClass, classNode, valueTargetCount + i, classChildren[i]) + registerProblemIfProblematic( + nonDuplicates, + classNode, + classChildren[classTargetIndex], + QuickFixFactory.getInstance().createDeleteFix(classChildren[classTargetIndex]) + ) } } else { - if (targets.constantStringValue == null) return + val stringValue = targets.constantStringValue ?: return val classNode = findClassNodeByQualifiedName( psiClass.project, psiClass.findModule(), - targets.constantStringValue!!.replace('/', '.'), + stringValue.replace('/', '.'), ) - val elementToDelete = mixinAnnotation.parameterList.attributes.find { it.name == "targets" } ?: targets - registerProblemIfProblematic(psiClass, classNode, valueTargetCount, elementToDelete) + val removeDuplicateTargetFix = RemoveDuplicateTargetFix(mixinAnnotation, "targets") + registerProblemIfProblematic(nonDuplicates, classNode, targets, removeDuplicateTargetFix) } } - private fun isClassNodeInTargets( - psiClass: PsiClass, - classNodeToCheck: ClassNode?, - indexToIgnore: Int - ): Boolean { - return classNodeToCheck in psiClass.mixinTargets.subList(0, indexToIgnore) || - classNodeToCheck in psiClass.mixinTargets.subList(indexToIgnore + 1, psiClass.mixinTargets.size) - } - - private fun registerProblemForDuplicate(duplicateExpression: PsiElement) { + private fun registerProblemForDuplicate(duplicateExpression: PsiElement, quickFix: LocalQuickFix) { holder.registerProblem( duplicateExpression, "Duplicate target is redundant", - QuickFixFactory.getInstance().createDeleteFix(duplicateExpression) + quickFix, ) } private fun registerProblemIfProblematic( - psiClass: PsiClass, + nonDuplicates: List, classNode: ClassNode?, - indexToIgnore: Int, - possibleDuplicate: PsiElement + possibleDuplicate: PsiElement, + possibleQuickFix: LocalQuickFix ) { - if (isClassNodeInTargets(psiClass, classNode, indexToIgnore)) { - registerProblemForDuplicate(possibleDuplicate) + if (classNode != null && classNode !in nonDuplicates) { + registerProblemForDuplicate(possibleDuplicate, possibleQuickFix) } } } + + private class RemoveDuplicateTargetFix(annotation: PsiAnnotation, val annotationAttributeName: String) : + LocalQuickFixAndIntentionActionOnPsiElement(annotation) { + + override fun getFamilyName() = "Remove duplicate target" + + override fun getText() = familyName + + override fun invoke( + project: Project, + file: PsiFile, + editor: Editor?, + startElement: PsiElement, + endElement: PsiElement + ) { + val annotation = startElement as? PsiAnnotation ?: return + annotation.setDeclaredAttributeValue(annotationAttributeName, null) + } + } } diff --git a/src/test/kotlin/platform/mixin/MixinDuplicateTargetInspectionTest.kt b/src/test/kotlin/platform/mixin/MixinDuplicateTargetInspectionTest.kt new file mode 100644 index 000000000..5b498b391 --- /dev/null +++ b/src/test/kotlin/platform/mixin/MixinDuplicateTargetInspectionTest.kt @@ -0,0 +1,350 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin + +import com.demonwav.mcdev.framework.EdtInterceptor +import com.demonwav.mcdev.platform.mixin.inspection.MixinDuplicateTargetInspection +import org.intellij.lang.annotations.Language +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(EdtInterceptor::class) +@DisplayName("Mixin Duplicate Target Inspection Test") +class MixinDuplicateTargetInspectionTest : BaseMixinTest() { + + private fun doTest(@Language("JAVA") code: String) { + buildProject { + dir("test") { + java("TestMixin.java", code) + } + } + + fixture.enableInspections(MixinDuplicateTargetInspection::class) + fixture.checkHighlighting(false, false, true) + } + + @Test + @DisplayName("Simple class target") + fun simpleClassTarget() { + doTest( + """ + package test; + + import com.demonwav.mcdev.mixintestdata.multipleTargetClasses.SimpleClass1; + import org.spongepowered.asm.mixin.Mixin; + + @Mixin(SimpleClass1.class) + public class TestMixin { + } + """, + ) + } + + @Test + @DisplayName("Simple class target in array") + fun simpleClassTargetInArray() { + doTest( + """ + package test; + + import com.demonwav.mcdev.mixintestdata.multipleTargetClasses.SimpleClass1; + import org.spongepowered.asm.mixin.Mixin; + + @Mixin({SimpleClass1.class}) + public class TestMixin { + } + """, + ) + } + + @Test + @DisplayName("Simple class target in array with attribute name") + fun simpleClassTargetInArrayWithAttributeName() { + doTest( + """ + package test; + + import com.demonwav.mcdev.mixintestdata.multipleTargetClasses.SimpleClass1; + import org.spongepowered.asm.mixin.Mixin; + + @Mixin(value = {SimpleClass1.class}) + public class TestMixin { + } + """, + ) + } + + @Test + @DisplayName("Simple class target with attribute name") + fun simpleClassTargetWithAttributeName() { + doTest( + """ + package test; + + import com.demonwav.mcdev.mixintestdata.multipleTargetClasses.SimpleClass1; + import org.spongepowered.asm.mixin.Mixin; + + @Mixin(value = SimpleClass1.class) + public class TestMixin { + } + """, + ) + } + + @Test + @DisplayName("Different targets in array") + fun differentTargetsInArray() { + doTest( + """ + package test; + + import com.demonwav.mcdev.mixintestdata.multipleTargetClasses.SimpleClass1; + import com.demonwav.mcdev.mixintestdata.multipleTargetClasses.SimpleClass2; + import com.demonwav.mcdev.mixintestdata.multipleTargetClasses.SimpleClass3; + import org.spongepowered.asm.mixin.Mixin; + + @Mixin({SimpleClass1.class, SimpleClass2.class, SimpleClass3.class}) + public class TestMixin { + } + """, + ) + } + + @Test + @DisplayName("Different targets in array with attribute name") + fun differentTargetsInArrayWithAttributeName() { + doTest( + """ + package test; + + import com.demonwav.mcdev.mixintestdata.multipleTargetClasses.SimpleClass1; + import com.demonwav.mcdev.mixintestdata.multipleTargetClasses.SimpleClass2; + import com.demonwav.mcdev.mixintestdata.multipleTargetClasses.SimpleClass3; + import org.spongepowered.asm.mixin.Mixin; + + @Mixin(value = {SimpleClass1.class, SimpleClass2.class, SimpleClass3.class}) + public class TestMixin { + } + """, + ) + } + + @Test + @DisplayName("Different targets in string targets") + fun differentTargetsInStringTargets() { + doTest( + """ + package test; + + import org.spongepowered.asm.mixin.Mixin; + + @Mixin(targets = { + "com.demonwav.mcdev.mixintestdata.multipleTargetClasses.SimpleClass1", + "com.demonwav.mcdev.mixintestdata.multipleTargetClasses.SimpleClass2", + "com.demonwav.mcdev.mixintestdata.multipleTargetClasses.SimpleClass3" + }) + public class TestMixin { + } + """, + ) + } + + @Test + @DisplayName("Unresolved duplicates") + fun unreslovedDuplicates() { + doTest( + """ + package test; + + import org.spongepowered.asm.mixin.Mixin; + + @Mixin( + value = { + UnresolvedClass1.class, + UnresolvedClass1.class, + UnresolvedClass2.class, + UnresolvedClass2.class + }, + targets = { + "UnresolvedClass1", + "UnresolvedClass1", + "UnresolvedClass2", + "UnresolvedClass2" + } + ) + public class TestMixin { + } + """, + ) + } + + @Test + @DisplayName("Target twice in array") + fun targetTwiceInArray() { + doTest( + """ + package test; + + import com.demonwav.mcdev.mixintestdata.multipleTargetClasses.SimpleClass1; + import org.spongepowered.asm.mixin.Mixin; + + @Mixin({SimpleClass1.class, SimpleClass1.class}) + public class TestMixin { + } + """, + ) + } + + @Test + @DisplayName("Target twice in array with attribute name") + fun targetTwiceInArrayWithAttributeName() { + doTest( + """ + package test; + + import com.demonwav.mcdev.mixintestdata.multipleTargetClasses.SimpleClass1; + import org.spongepowered.asm.mixin.Mixin; + + @Mixin(value = {SimpleClass1.class, SimpleClass1.class}) + public class TestMixin { + } + """, + ) + } + + @Test + @DisplayName("Target twice with unresolved targets next to it") + fun targetTwiceWithUnresolvedTargetsNextToIt() { + doTest( + """ + package test; + + import com.demonwav.mcdev.mixintestdata.multipleTargetClasses.SimpleClass1; + import org.spongepowered.asm.mixin.Mixin; + + @Mixin(value = { + UnresolvedClass1.class, + SimpleClass1.class, + UnresolvedClass2.class, + SimpleClass1.class, + UnresolvedClass3.class + }) + public class TestMixin { + } + """, + ) + } + + @Test + @DisplayName("Target three times with unresolved targets next to it") + fun targetThreeTimesWithUnresolvedTargetsNextToIt() { + doTest( + """ + package test; + + import com.demonwav.mcdev.mixintestdata.multipleTargetClasses.SimpleClass1; + import org.spongepowered.asm.mixin.Mixin; + + @Mixin(value = { + UnresolvedClass1.class, + SimpleClass1.class, + UnresolvedClass2.class, + SimpleClass1.class, + UnresolvedClass3.class, + SimpleClass1.class, + UnresolvedClass4.class + }) + public class TestMixin { + } + """, + )/* + UnresolvedClass4.class + is the expected + */ + } + + @Test + @DisplayName("Multiple targets multiple times") + fun multipleTargetsMultipleTimes() { + doTest( + """ + package test; + + import com.demonwav.mcdev.mixintestdata.multipleTargetClasses.SimpleClass1; + import com.demonwav.mcdev.mixintestdata.multipleTargetClasses.SimpleClass2; + import org.spongepowered.asm.mixin.Mixin; + + @Mixin(value = { + SimpleClass2.class, + UnresolvedClass1.class, + SimpleClass1.class, + UnresolvedClass2.class, + SimpleClass1.class, + SimpleClass2.class, + UnresolvedClass3.class + }) + public class TestMixin { + } + """, + ) + } + + @Test + @DisplayName("String target and class target") + fun stringTargetAndClassTarget() { + doTest( + """ + package test; + + import com.demonwav.mcdev.mixintestdata.multipleTargetClasses.SimpleClass1; + import org.spongepowered.asm.mixin.Mixin; + + @Mixin(value = SimpleClass1.class, + targets = "com.demonwav.mcdev.mixintestdata.multipleTargetClasses.SimpleClass1") + public class TestMixin { + } + """, + ) + } + + @Test + @DisplayName("Multiple string targets") + fun multipleStringTargets() { + doTest( + """ + package test; + + import org.spongepowered.asm.mixin.Mixin; + + @Mixin(targets = { + "com.demonwav.mcdev.mixintestdata.multipleTargetClasses.SimpleClass2", + "com.demonwav.mcdev.mixintestdata.multipleTargetClasses.SimpleClass1", + "com.demonwav.mcdev.mixintestdata.multipleTargetClasses.SimpleClass1", + "com.demonwav.mcdev.mixintestdata.multipleTargetClasses.SimpleClass3" + } + ) + public class TestMixin { + } + """, + ) + } +}