User: joe
Date: 20 Dec 25 16:38
Revision: bde4418ffe213b400b668c8877c68183b9dae543
Summary:
Add inspection for wrong mixin class type. Closes #2549
TeamCity URL: http://ci.mcdev.io:80/viewModification.html?tab=vcsModificationFiles&modId=10364&personal=false
Index: src/main/kotlin/platform/mixin/inspection/MixinClassTypeInspection.kt
===================================================================
--- src/main/kotlin/platform/mixin/inspection/MixinClassTypeInspection.kt (revision bde4418ffe213b400b668c8877c68183b9dae543)
+++ src/main/kotlin/platform/mixin/inspection/MixinClassTypeInspection.kt (revision bde4418ffe213b400b668c8877c68183b9dae543)
@@ -0,0 +1,118 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2025 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.hasAccess
+import com.demonwav.mcdev.platform.mixin.util.isAccessorMixin
+import com.demonwav.mcdev.platform.mixin.util.isMixin
+import com.demonwav.mcdev.platform.mixin.util.mixinTargets
+import com.intellij.codeInspection.LocalQuickFix
+import com.intellij.codeInspection.LocalQuickFixOnPsiElement
+import com.intellij.codeInspection.ProblemsHolder
+import com.intellij.openapi.project.Project
+import com.intellij.psi.JavaElementVisitor
+import com.intellij.psi.JavaPsiFacade
+import com.intellij.psi.JavaTokenType
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
+import com.intellij.psi.PsiKeyword
+import com.intellij.psi.tree.TokenSet
+import org.objectweb.asm.Opcodes
+
+class MixinClassTypeInspection : MixinInspection() {
+ override fun getStaticDescription() = "Reports when a Mixin uses the wrong class type"
+
+ override fun buildVisitor(holder: ProblemsHolder) = object : JavaElementVisitor() {
+ override fun visitClass(mixinClass: PsiClass) {
+ if (!mixinClass.isMixin) {
+ return
+ }
+ if (mixinClass.isAccessorMixin) {
+ return
+ }
+
+ val classKeywordElement = mixinClass.children.firstOrNull {
+ (it as? PsiKeyword)?.tokenType in CLASS_KEYWORD_SET
+ }
+ val problemElement = classKeywordElement ?: mixinClass.nameIdentifier ?: mixinClass
+
+ val needsToBeClass = mixinClass.mixinTargets.any { !it.hasAccess(Opcodes.ACC_INTERFACE) }
+ val needsToBeInterface = mixinClass.mixinTargets.any { it.hasAccess(Opcodes.ACC_INTERFACE) }
+
+ val fixes = mutableListOf()
+ if (classKeywordElement != null) {
+ if (needsToBeClass && !needsToBeInterface) {
+ fixes += ChangeClassTypeFix(classKeywordElement, PsiKeyword.CLASS)
+ } else if (needsToBeInterface && !needsToBeClass) {
+ fixes += ChangeClassTypeFix(classKeywordElement, PsiKeyword.INTERFACE)
+ }
+ }
+
+ if (mixinClass.isEnum) {
+ holder.registerProblem(problemElement, "Mixins cannot be enums", *fixes.toTypedArray())
+ return
+ }
+
+ if (mixinClass.isAnnotationType) {
+ holder.registerProblem(problemElement, "Mixins cannot be annotation types", *fixes.toTypedArray())
+ return
+ }
+
+ if (mixinClass.isRecord) {
+ holder.registerProblem(problemElement, "Mixins cannot be records", *fixes.toTypedArray())
+ return
+ }
+
+ if (mixinClass.isInterface) {
+ if (needsToBeClass) {
+ holder.registerProblem(problemElement, "Interface Mixin targets a class", *fixes.toTypedArray())
+ }
+ } else {
+ if (needsToBeInterface) {
+ holder.registerProblem(problemElement, "Class Mixin targets an interface", *fixes.toTypedArray())
+ }
+ }
+ }
+ }
+
+ companion object {
+ private val CLASS_KEYWORD_SET = TokenSet.create(
+ JavaTokenType.CLASS_KEYWORD,
+ JavaTokenType.INTERFACE_KEYWORD,
+ JavaTokenType.ENUM_KEYWORD,
+ JavaTokenType.RECORD_KEYWORD,
+ )
+ }
+
+ private class ChangeClassTypeFix(
+ classKeywordElement: PsiElement,
+ private val newKeyword: String
+ ) : LocalQuickFixOnPsiElement(classKeywordElement) {
+ override fun getFamilyName() = "Change class type to $newKeyword"
+ override fun getText() = "Change class type to $newKeyword"
+
+ override fun invoke(project: Project, file: PsiFile, startElement: PsiElement, endElement: PsiElement) {
+ val factory = JavaPsiFacade.getElementFactory(project)
+ startElement.replace(factory.createKeyword(newKeyword))
+ }
+ }
+}
Index: src/main/kotlin/platform/mixin/inspection/MixinSuperClassInspection.kt
===================================================================
--- src/main/kotlin/platform/mixin/inspection/MixinSuperClassInspection.kt (revision fae48f91c50cead06d12269e8cd2673f41183ef2)
+++ src/main/kotlin/platform/mixin/inspection/MixinSuperClassInspection.kt (revision bde4418ffe213b400b668c8877c68183b9dae543)
@@ -46,6 +46,11 @@
return
}
+ if (psiClass.isEnum || psiClass.isAnnotationType || psiClass.isRecord) {
+ // these will be reported by MixinClassTypeInspection
+ return
+ }
+
val superClass = psiClass.superClass ?: return
if (superClass.qualifiedName == CommonClassNames.JAVA_LANG_OBJECT) {
return
@@ -87,7 +92,7 @@
}
private fun reportSuperClass(psiClass: PsiClass, description: String) {
- holder.registerProblem(psiClass.extendsList?.referenceElements?.firstOrNull() ?: psiClass, description)
+ holder.registerProblem(psiClass.extendsList?.referenceElements?.firstOrNull() ?: psiClass.nameIdentifier ?: psiClass, description)
}
}
}
Index: src/main/kotlin/platform/mixin/util/Mixin.kt
===================================================================
--- src/main/kotlin/platform/mixin/util/Mixin.kt (revision fae48f91c50cead06d12269e8cd2673f41183ef2)
+++ src/main/kotlin/platform/mixin/util/Mixin.kt (revision bde4418ffe213b400b668c8877c68183b9dae543)
@@ -116,7 +116,7 @@
* 1. The class given is a Mixin.
* 2. The class given is an interface.
* 3. All member methods are decorated with either `@Accessor` or `@Invoker`.
- * 4. All Mixin targets are classes.
+ * 4. None of the Mixin targets are interfaces.
*
* @receiver The class to check
* @return True if the above checks are satisfied.
Index: src/main/resources/META-INF/plugin.xml
===================================================================
--- src/main/resources/META-INF/plugin.xml (revision fae48f91c50cead06d12269e8cd2673f41183ef2)
+++ src/main/resources/META-INF/plugin.xml (revision bde4418ffe213b400b668c8877c68183b9dae543)
@@ -1102,6 +1102,14 @@
level="ERROR"
hasStaticDescription="true"
implementationClass="com.demonwav.mcdev.platform.mixin.inspection.MixinInnerClassInspection"/>
+