User: joe
Date: 23 Jan 24 13:42
Revision: 11870b464c1bde4f6ee793cc6ba284a20e5464c0
Summary:
Add inspections for unnecessary unsafe and CTOR_HEAD over HEAD
TeamCity URL: https://ci.mcdev.io/viewModification.html?tab=vcsModificationFiles&modId=9033&personal=false
Index: src/main/kotlin/platform/mixin/inspection/fix/AnnotationAttributeFix.kt
===================================================================
--- src/main/kotlin/platform/mixin/inspection/fix/AnnotationAttributeFix.kt (revision 11870b464c1bde4f6ee793cc6ba284a20e5464c0)
+++ src/main/kotlin/platform/mixin/inspection/fix/AnnotationAttributeFix.kt (revision 11870b464c1bde4f6ee793cc6ba284a20e5464c0)
@@ -0,0 +1,112 @@
+/*
+ * 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.fix
+
+import com.demonwav.mcdev.util.createLiteralExpression
+import com.intellij.codeInsight.intention.FileModifier.SafeFieldForPreview
+import com.intellij.codeInspection.LocalQuickFixOnPsiElement
+import com.intellij.openapi.project.Project
+import com.intellij.psi.JavaPsiFacade
+import com.intellij.psi.PsiAnnotation
+import com.intellij.psi.PsiAnnotationMemberValue
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
+import com.intellij.psi.codeStyle.CodeStyleManager
+
+open class AnnotationAttributeFix(
+ annotation: PsiAnnotation,
+ vararg attributes: Pair,
+) : LocalQuickFixOnPsiElement(annotation) {
+ @SafeFieldForPreview
+ private val attributes = attributes.map { (key, value) ->
+ key to value?.let {
+ if (it !is PsiAnnotationMemberValue) {
+ JavaPsiFacade.getElementFactory(annotation.project).createLiteralExpression(it)
+ } else {
+ it
+ }
+ }
+ }
+
+ private val description = run {
+ val added = this.attributes
+ .filter { (_, value) -> value != null }
+ .map { (key, value) -> "$key = ${value!!.text}" }
+ val removed = this.attributes
+ .filter { (_, value) -> value == null }
+ .map { (key, _) -> key }
+
+ buildString {
+ if (added.isNotEmpty()) {
+ append("Add ")
+ for ((i, add) in added.withIndex()) {
+ if (i != 0) {
+ if (i == added.size - 1) {
+ append(" and ")
+ } else {
+ append(", ")
+ }
+ }
+ append(add)
+ }
+ if (removed.isNotEmpty()) {
+ append(", and remove ")
+ }
+ } else if (removed.isNotEmpty()) {
+ append("Remove ")
+ }
+ if (removed.isNotEmpty()) {
+ for ((i, rem) in removed.withIndex()) {
+ if (i != 0) {
+ if (i == removed.size - 1) {
+ append(" and ")
+ } else {
+ append(", ")
+ }
+ }
+ append(rem)
+ }
+ }
+ }
+ }
+
+ override fun getFamilyName() = description
+ override fun getText() = description
+
+ override fun invoke(project: Project, file: PsiFile, startElement: PsiElement, endElement: PsiElement) {
+ val annotation = startElement as? PsiAnnotation ?: return
+ for ((key, value) in attributes) {
+ annotation.setDeclaredAttributeValue(key, value)
+ }
+
+ // replace single "value = foo" with "foo"
+ val attrs = annotation.parameterList.attributes
+ if (attrs.size == 1 && attrs[0].name == "value") {
+ attrs[0].value?.let { value ->
+ val fakeAnnotation = JavaPsiFacade.getElementFactory(project).createAnnotationFromText("@Foo(0)", null)
+ fakeAnnotation.parameterList.attributes[0].value!!.replace(value)
+ annotation.parameterList.replace(fakeAnnotation.parameterList)
+ }
+ }
+
+ CodeStyleManager.getInstance(project).reformat(annotation.parameterList)
+ }
+}
Index: src/main/kotlin/platform/mixin/inspection/injector/CtorHeadNoUnsafeInspection.kt
===================================================================
--- src/main/kotlin/platform/mixin/inspection/injector/CtorHeadNoUnsafeInspection.kt (revision ab985497ef6095c7c2dcb11a253606fa6da72e96)
+++ src/main/kotlin/platform/mixin/inspection/injector/CtorHeadNoUnsafeInspection.kt (revision 11870b464c1bde4f6ee793cc6ba284a20e5464c0)
@@ -23,6 +23,7 @@
import com.demonwav.mcdev.facet.MinecraftFacet
import com.demonwav.mcdev.platform.fabric.FabricModuleType
import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection
+import com.demonwav.mcdev.platform.mixin.inspection.fix.AnnotationAttributeFix
import com.demonwav.mcdev.platform.mixin.util.MixinConstants
import com.demonwav.mcdev.util.constantStringValue
import com.demonwav.mcdev.util.constantValue
@@ -76,7 +77,7 @@
holder.registerProblem(
valueElement,
"CTOR_HEAD is missing unsafe = true",
- InjectIntoConstructorInspection.AddUnsafeFix(annotation),
+ AnnotationAttributeFix(annotation, "unsafe" to true),
)
}
}
Index: src/main/kotlin/platform/mixin/inspection/injector/CtorHeadUsedForNonConstructorInspection.kt
===================================================================
--- src/main/kotlin/platform/mixin/inspection/injector/CtorHeadUsedForNonConstructorInspection.kt (revision 11870b464c1bde4f6ee793cc6ba284a20e5464c0)
+++ src/main/kotlin/platform/mixin/inspection/injector/CtorHeadUsedForNonConstructorInspection.kt (revision 11870b464c1bde4f6ee793cc6ba284a20e5464c0)
@@ -0,0 +1,59 @@
+/*
+ * 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.injector
+
+import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection
+import com.demonwav.mcdev.platform.mixin.inspection.fix.AnnotationAttributeFix
+import com.demonwav.mcdev.platform.mixin.util.MixinConstants
+import com.demonwav.mcdev.util.constantValue
+import com.intellij.codeInspection.ProblemsHolder
+import com.intellij.psi.JavaElementVisitor
+import com.intellij.psi.PsiAnnotation
+import com.intellij.psi.PsiElementVisitor
+
+class CtorHeadUsedForNonConstructorInspection : MixinInspection() {
+ override fun getStaticDescription() = "Reports when CTOR_HEAD is used without targeting a constructor method"
+
+ override fun buildVisitor(holder: ProblemsHolder): PsiElementVisitor = object : JavaElementVisitor() {
+ override fun visitAnnotation(annotation: PsiAnnotation) {
+ if (!annotation.hasQualifiedName(MixinConstants.Annotations.AT)) {
+ return
+ }
+ val atValue = annotation.findDeclaredAttributeValue("value") ?: return
+ if (atValue.constantValue != "CTOR_HEAD") {
+ return
+ }
+ if (!UnnecessaryUnsafeInspection.mightTargetConstructor(holder.project, annotation)) {
+ holder.registerProblem(
+ atValue,
+ "CTOR_HEAD used without targeting a constructor",
+ ReplaceWithHeadFix(annotation),
+ )
+ }
+ }
+ }
+
+ private class ReplaceWithHeadFix(at: PsiAnnotation) :
+ AnnotationAttributeFix(at, "value" to "HEAD", "unsafe" to null) {
+ override fun getFamilyName() = "Replace with \"HEAD\""
+ override fun getText() = "Replace with \"HEAD\""
+ }
+}
Index: src/main/kotlin/platform/mixin/inspection/injector/InjectIntoConstructorInspection.kt
===================================================================
--- src/main/kotlin/platform/mixin/inspection/injector/InjectIntoConstructorInspection.kt (revision ab985497ef6095c7c2dcb11a253606fa6da72e96)
+++ src/main/kotlin/platform/mixin/inspection/injector/InjectIntoConstructorInspection.kt (revision 11870b464c1bde4f6ee793cc6ba284a20e5464c0)
@@ -26,25 +26,19 @@
import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler
import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.AtResolver
import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection
+import com.demonwav.mcdev.platform.mixin.inspection.fix.AnnotationAttributeFix
import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember
import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.INJECT
import com.demonwav.mcdev.platform.mixin.util.findSuperConstructorCall
import com.demonwav.mcdev.platform.mixin.util.isConstructor
import com.demonwav.mcdev.util.constantValue
-import com.demonwav.mcdev.util.createLiteralExpression
import com.demonwav.mcdev.util.findAnnotation
import com.demonwav.mcdev.util.findAnnotations
import com.demonwav.mcdev.util.findModule
-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.PsiAnnotation
import com.intellij.psi.PsiClass
-import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor
-import com.intellij.psi.PsiFile
import com.intellij.psi.PsiMethod
import java.awt.FlowLayout
import javax.swing.JCheckBox
@@ -96,7 +90,7 @@
val atHasUnsafe = !atClass?.findMethodsByName("unsafe", false).isNullOrEmpty()
val quickFixes = if (atHasUnsafe) {
- arrayOf(AddUnsafeFix(at))
+ arrayOf(AnnotationAttributeFix(at, "unsafe" to true))
} else {
emptyArray()
}
@@ -130,15 +124,4 @@
}
override fun getStaticDescription() = "@Inject into Constructor"
-
- class AddUnsafeFix(at: PsiAnnotation) : LocalQuickFixOnPsiElement(at) {
- override fun getFamilyName() = "Add unsafe = true"
- override fun getText() = "Add unsafe = true"
-
- override fun invoke(project: Project, file: PsiFile, startElement: PsiElement, endElement: PsiElement) {
- val annotation = startElement as? PsiAnnotation ?: return
- val trueExpr = JavaPsiFacade.getElementFactory(project).createLiteralExpression(true)
- annotation.setDeclaredAttributeValue("unsafe", trueExpr)
- }
+}
- }
-}
Index: src/main/kotlin/platform/mixin/inspection/injector/ModifyVariableArgsOnlyInspection.kt
===================================================================
--- src/main/kotlin/platform/mixin/inspection/injector/ModifyVariableArgsOnlyInspection.kt (revision ab985497ef6095c7c2dcb11a253606fa6da72e96)
+++ src/main/kotlin/platform/mixin/inspection/injector/ModifyVariableArgsOnlyInspection.kt (revision 11870b464c1bde4f6ee793cc6ba284a20e5464c0)
@@ -22,24 +22,19 @@
import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler
import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection
+import com.demonwav.mcdev.platform.mixin.inspection.fix.AnnotationAttributeFix
import com.demonwav.mcdev.platform.mixin.util.ClassAndMethodNode
import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember
import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.MODIFY_VARIABLE
import com.demonwav.mcdev.platform.mixin.util.hasAccess
import com.demonwav.mcdev.util.constantValue
-import com.demonwav.mcdev.util.createLiteralExpression
import com.demonwav.mcdev.util.descriptor
import com.demonwav.mcdev.util.findAnnotation
import com.demonwav.mcdev.util.ifEmpty
-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.PsiAnnotation
-import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor
-import com.intellij.psi.PsiFile
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiType
import org.objectweb.asm.Opcodes
@@ -64,23 +59,16 @@
if (shouldReport(modifyVariable, wantedType, methodTargets)) {
val description = "@ModifyVariable may be argsOnly = true"
- holder.registerProblem(problemElement, description, AddArgsOnlyFix(modifyVariable))
+ holder.registerProblem(
+ problemElement,
+ description,
+ AnnotationAttributeFix(modifyVariable, "argsOnly" to true),
+ )
}
}
}
}
- class AddArgsOnlyFix(annotation: PsiAnnotation) : LocalQuickFixOnPsiElement(annotation) {
- override fun getFamilyName() = "Add argsOnly = true"
- override fun getText() = "Add argsOnly = true"
-
- override fun invoke(project: Project, file: PsiFile, startElement: PsiElement, endElement: PsiElement) {
- val annotation = startElement as? PsiAnnotation ?: return
- val trueExpr = JavaPsiFacade.getElementFactory(project).createLiteralExpression(true)
- annotation.setDeclaredAttributeValue("argsOnly", trueExpr)
- }
- }
-
companion object {
fun shouldReport(
annotation: PsiAnnotation,
Index: src/main/kotlin/platform/mixin/inspection/injector/UnnecessaryUnsafeInspection.kt
===================================================================
--- src/main/kotlin/platform/mixin/inspection/injector/UnnecessaryUnsafeInspection.kt (revision 11870b464c1bde4f6ee793cc6ba284a20e5464c0)
+++ src/main/kotlin/platform/mixin/inspection/injector/UnnecessaryUnsafeInspection.kt (revision 11870b464c1bde4f6ee793cc6ba284a20e5464c0)
@@ -0,0 +1,114 @@
+/*
+ * 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.injector
+
+import com.demonwav.mcdev.facet.MinecraftFacet
+import com.demonwav.mcdev.platform.fabric.FabricModuleType
+import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler
+import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection
+import com.demonwav.mcdev.platform.mixin.inspection.fix.AnnotationAttributeFix
+import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember
+import com.demonwav.mcdev.platform.mixin.util.MixinConstants
+import com.demonwav.mcdev.platform.mixin.util.isConstructor
+import com.demonwav.mcdev.util.constantValue
+import com.demonwav.mcdev.util.findModule
+import com.demonwav.mcdev.util.ifEmpty
+import com.intellij.codeInspection.ProblemsHolder
+import com.intellij.openapi.project.Project
+import com.intellij.psi.JavaElementVisitor
+import com.intellij.psi.PsiAnnotation
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiElementVisitor
+import com.intellij.psi.PsiModifierList
+import com.intellij.psi.util.parents
+import java.awt.FlowLayout
+import javax.swing.JCheckBox
+import javax.swing.JComponent
+import javax.swing.JPanel
+
+class UnnecessaryUnsafeInspection : MixinInspection() {
+ @JvmField
+ var alwaysUnnecessaryOnFabric = true
+
+ override fun getStaticDescription() = "Reports unnecessary unsafe = true"
+
+ override fun createOptionsPanel(): JComponent {
+ val panel = JPanel(FlowLayout(FlowLayout.LEFT))
+ val checkbox = JCheckBox("Always unnecessary in Fabric mods", alwaysUnnecessaryOnFabric)
+ checkbox.addActionListener {
+ alwaysUnnecessaryOnFabric = checkbox.isSelected
+ }
+ panel.add(checkbox)
+ return panel
+ }
+
+ override fun buildVisitor(holder: ProblemsHolder): PsiElementVisitor {
+ val isFabric = holder.file.findModule()?.let { MinecraftFacet.getInstance(it) }?.isOfType(FabricModuleType)
+ ?: false
+ val alwaysUnnecessary = isFabric && alwaysUnnecessaryOnFabric
+
+ return object : JavaElementVisitor() {
+ override fun visitAnnotation(annotation: PsiAnnotation) {
+ if (!annotation.hasQualifiedName(MixinConstants.Annotations.AT)) {
+ return
+ }
+ if (!alwaysUnnecessary &&
+ annotation.findDeclaredAttributeValue("value")?.constantValue == "CTOR_HEAD"
+ ) {
+ // this case is handled by a specific inspection for CTOR_HEAD
+ return
+ }
+
+ val unsafeValue = annotation.findDeclaredAttributeValue("unsafe") ?: return
+ if (unsafeValue.constantValue != true) {
+ return
+ }
+
+ if (alwaysUnnecessary || !mightTargetConstructor(holder.project, annotation)) {
+ holder.registerProblem(
+ unsafeValue,
+ "Unnecessary unsafe = true",
+ AnnotationAttributeFix(annotation, "unsafe" to null)
+ )
+ }
+ }
+ }
+ }
+
+ companion object {
+ fun mightTargetConstructor(project: Project, at: PsiAnnotation): Boolean {
+ val injectorAnnotation = at.parents(false)
+ .takeWhile { it !is PsiClass }
+ .filterIsInstance()
+ .firstOrNull { it.parent is PsiModifierList }
+ ?: return true
+ val handler = injectorAnnotation.qualifiedName?.let {
+ MixinAnnotationHandler.forMixinAnnotation(it, project)
+ } ?: return true
+
+ val targets = handler.resolveTarget(injectorAnnotation)
+ .filterIsInstance()
+ .ifEmpty { return true }
+
+ return targets.any { it.classAndMethod.method.isConstructor }
+ }
+ }
+}
Index: src/main/kotlin/platform/mixin/inspection/mixinextras/LocalArgsOnlyInspection.kt
===================================================================
--- src/main/kotlin/platform/mixin/inspection/mixinextras/LocalArgsOnlyInspection.kt (revision ab985497ef6095c7c2dcb11a253606fa6da72e96)
+++ src/main/kotlin/platform/mixin/inspection/mixinextras/LocalArgsOnlyInspection.kt (revision 11870b464c1bde4f6ee793cc6ba284a20e5464c0)
@@ -22,6 +22,7 @@
import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler
import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection
+import com.demonwav.mcdev.platform.mixin.inspection.fix.AnnotationAttributeFix
import com.demonwav.mcdev.platform.mixin.inspection.injector.ModifyVariableArgsOnlyInspection
import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember
import com.demonwav.mcdev.platform.mixin.util.MixinConstants
@@ -66,7 +67,7 @@
holder.registerProblem(
localAnnotation.nameReferenceElement ?: localAnnotation,
"@Local may be argsOnly = true",
- ModifyVariableArgsOnlyInspection.AddArgsOnlyFix(localAnnotation)
+ AnnotationAttributeFix(localAnnotation, "argsOnly" to true)
)
}
}
Index: src/main/resources/META-INF/plugin.xml
===================================================================
--- src/main/resources/META-INF/plugin.xml (revision ab985497ef6095c7c2dcb11a253606fa6da72e96)
+++ src/main/resources/META-INF/plugin.xml (revision 11870b464c1bde4f6ee793cc6ba284a20e5464c0)
@@ -896,6 +896,22 @@
level="ERROR"
hasStaticDescription="true"
implementationClass="com.demonwav.mcdev.platform.mixin.inspection.injector.CtorHeadNoUnsafeInspection"/>
+
+