Skip to content

Commit 11870b4

Browse files
committed
Add inspections for unnecessary unsafe and CTOR_HEAD over HEAD
1 parent ab98549 commit 11870b4

File tree

8 files changed

+313
-39
lines changed

8 files changed

+313
-39
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Minecraft Development for IntelliJ
3+
*
4+
* https://mcdev.io/
5+
*
6+
* Copyright (C) 2024 minecraft-dev
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU Lesser General Public License as published
10+
* by the Free Software Foundation, version 3.0 only.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Lesser General Public License
18+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
*/
20+
21+
package com.demonwav.mcdev.platform.mixin.inspection.fix
22+
23+
import com.demonwav.mcdev.util.createLiteralExpression
24+
import com.intellij.codeInsight.intention.FileModifier.SafeFieldForPreview
25+
import com.intellij.codeInspection.LocalQuickFixOnPsiElement
26+
import com.intellij.openapi.project.Project
27+
import com.intellij.psi.JavaPsiFacade
28+
import com.intellij.psi.PsiAnnotation
29+
import com.intellij.psi.PsiAnnotationMemberValue
30+
import com.intellij.psi.PsiElement
31+
import com.intellij.psi.PsiFile
32+
import com.intellij.psi.codeStyle.CodeStyleManager
33+
34+
open class AnnotationAttributeFix(
35+
annotation: PsiAnnotation,
36+
vararg attributes: Pair<String, Any?>,
37+
) : LocalQuickFixOnPsiElement(annotation) {
38+
@SafeFieldForPreview
39+
private val attributes = attributes.map { (key, value) ->
40+
key to value?.let {
41+
if (it !is PsiAnnotationMemberValue) {
42+
JavaPsiFacade.getElementFactory(annotation.project).createLiteralExpression(it)
43+
} else {
44+
it
45+
}
46+
}
47+
}
48+
49+
private val description = run {
50+
val added = this.attributes
51+
.filter { (_, value) -> value != null }
52+
.map { (key, value) -> "$key = ${value!!.text}" }
53+
val removed = this.attributes
54+
.filter { (_, value) -> value == null }
55+
.map { (key, _) -> key }
56+
57+
buildString {
58+
if (added.isNotEmpty()) {
59+
append("Add ")
60+
for ((i, add) in added.withIndex()) {
61+
if (i != 0) {
62+
if (i == added.size - 1) {
63+
append(" and ")
64+
} else {
65+
append(", ")
66+
}
67+
}
68+
append(add)
69+
}
70+
if (removed.isNotEmpty()) {
71+
append(", and remove ")
72+
}
73+
} else if (removed.isNotEmpty()) {
74+
append("Remove ")
75+
}
76+
if (removed.isNotEmpty()) {
77+
for ((i, rem) in removed.withIndex()) {
78+
if (i != 0) {
79+
if (i == removed.size - 1) {
80+
append(" and ")
81+
} else {
82+
append(", ")
83+
}
84+
}
85+
append(rem)
86+
}
87+
}
88+
}
89+
}
90+
91+
override fun getFamilyName() = description
92+
override fun getText() = description
93+
94+
override fun invoke(project: Project, file: PsiFile, startElement: PsiElement, endElement: PsiElement) {
95+
val annotation = startElement as? PsiAnnotation ?: return
96+
for ((key, value) in attributes) {
97+
annotation.setDeclaredAttributeValue(key, value)
98+
}
99+
100+
// replace single "value = foo" with "foo"
101+
val attrs = annotation.parameterList.attributes
102+
if (attrs.size == 1 && attrs[0].name == "value") {
103+
attrs[0].value?.let { value ->
104+
val fakeAnnotation = JavaPsiFacade.getElementFactory(project).createAnnotationFromText("@Foo(0)", null)
105+
fakeAnnotation.parameterList.attributes[0].value!!.replace(value)
106+
annotation.parameterList.replace(fakeAnnotation.parameterList)
107+
}
108+
}
109+
110+
CodeStyleManager.getInstance(project).reformat(annotation.parameterList)
111+
}
112+
}

src/main/kotlin/platform/mixin/inspection/injector/CtorHeadNoUnsafeInspection.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ package com.demonwav.mcdev.platform.mixin.inspection.injector
2323
import com.demonwav.mcdev.facet.MinecraftFacet
2424
import com.demonwav.mcdev.platform.fabric.FabricModuleType
2525
import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection
26+
import com.demonwav.mcdev.platform.mixin.inspection.fix.AnnotationAttributeFix
2627
import com.demonwav.mcdev.platform.mixin.util.MixinConstants
2728
import com.demonwav.mcdev.util.constantStringValue
2829
import com.demonwav.mcdev.util.constantValue
@@ -76,7 +77,7 @@ class CtorHeadNoUnsafeInspection : MixinInspection() {
7677
holder.registerProblem(
7778
valueElement,
7879
"CTOR_HEAD is missing unsafe = true",
79-
InjectIntoConstructorInspection.AddUnsafeFix(annotation),
80+
AnnotationAttributeFix(annotation, "unsafe" to true),
8081
)
8182
}
8283
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Minecraft Development for IntelliJ
3+
*
4+
* https://mcdev.io/
5+
*
6+
* Copyright (C) 2024 minecraft-dev
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU Lesser General Public License as published
10+
* by the Free Software Foundation, version 3.0 only.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Lesser General Public License
18+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
*/
20+
21+
package com.demonwav.mcdev.platform.mixin.inspection.injector
22+
23+
import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection
24+
import com.demonwav.mcdev.platform.mixin.inspection.fix.AnnotationAttributeFix
25+
import com.demonwav.mcdev.platform.mixin.util.MixinConstants
26+
import com.demonwav.mcdev.util.constantValue
27+
import com.intellij.codeInspection.ProblemsHolder
28+
import com.intellij.psi.JavaElementVisitor
29+
import com.intellij.psi.PsiAnnotation
30+
import com.intellij.psi.PsiElementVisitor
31+
32+
class CtorHeadUsedForNonConstructorInspection : MixinInspection() {
33+
override fun getStaticDescription() = "Reports when CTOR_HEAD is used without targeting a constructor method"
34+
35+
override fun buildVisitor(holder: ProblemsHolder): PsiElementVisitor = object : JavaElementVisitor() {
36+
override fun visitAnnotation(annotation: PsiAnnotation) {
37+
if (!annotation.hasQualifiedName(MixinConstants.Annotations.AT)) {
38+
return
39+
}
40+
val atValue = annotation.findDeclaredAttributeValue("value") ?: return
41+
if (atValue.constantValue != "CTOR_HEAD") {
42+
return
43+
}
44+
if (!UnnecessaryUnsafeInspection.mightTargetConstructor(holder.project, annotation)) {
45+
holder.registerProblem(
46+
atValue,
47+
"CTOR_HEAD used without targeting a constructor",
48+
ReplaceWithHeadFix(annotation),
49+
)
50+
}
51+
}
52+
}
53+
54+
private class ReplaceWithHeadFix(at: PsiAnnotation) :
55+
AnnotationAttributeFix(at, "value" to "HEAD", "unsafe" to null) {
56+
override fun getFamilyName() = "Replace with \"HEAD\""
57+
override fun getText() = "Replace with \"HEAD\""
58+
}
59+
}

src/main/kotlin/platform/mixin/inspection/injector/InjectIntoConstructorInspection.kt

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,25 +26,19 @@ import com.demonwav.mcdev.platform.mixin.handlers.InjectorAnnotationHandler
2626
import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler
2727
import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.AtResolver
2828
import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection
29+
import com.demonwav.mcdev.platform.mixin.inspection.fix.AnnotationAttributeFix
2930
import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember
3031
import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.INJECT
3132
import com.demonwav.mcdev.platform.mixin.util.findSuperConstructorCall
3233
import com.demonwav.mcdev.platform.mixin.util.isConstructor
3334
import com.demonwav.mcdev.util.constantValue
34-
import com.demonwav.mcdev.util.createLiteralExpression
3535
import com.demonwav.mcdev.util.findAnnotation
3636
import com.demonwav.mcdev.util.findAnnotations
3737
import com.demonwav.mcdev.util.findModule
38-
import com.intellij.codeInspection.LocalQuickFixOnPsiElement
3938
import com.intellij.codeInspection.ProblemsHolder
40-
import com.intellij.openapi.project.Project
4139
import com.intellij.psi.JavaElementVisitor
42-
import com.intellij.psi.JavaPsiFacade
43-
import com.intellij.psi.PsiAnnotation
4440
import com.intellij.psi.PsiClass
45-
import com.intellij.psi.PsiElement
4641
import com.intellij.psi.PsiElementVisitor
47-
import com.intellij.psi.PsiFile
4842
import com.intellij.psi.PsiMethod
4943
import java.awt.FlowLayout
5044
import javax.swing.JCheckBox
@@ -96,7 +90,7 @@ class InjectIntoConstructorInspection : MixinInspection() {
9690
val atHasUnsafe = !atClass?.findMethodsByName("unsafe", false).isNullOrEmpty()
9791

9892
val quickFixes = if (atHasUnsafe) {
99-
arrayOf(AddUnsafeFix(at))
93+
arrayOf(AnnotationAttributeFix(at, "unsafe" to true))
10094
} else {
10195
emptyArray()
10296
}
@@ -130,15 +124,4 @@ class InjectIntoConstructorInspection : MixinInspection() {
130124
}
131125

132126
override fun getStaticDescription() = "@Inject into Constructor"
133-
134-
class AddUnsafeFix(at: PsiAnnotation) : LocalQuickFixOnPsiElement(at) {
135-
override fun getFamilyName() = "Add unsafe = true"
136-
override fun getText() = "Add unsafe = true"
137-
138-
override fun invoke(project: Project, file: PsiFile, startElement: PsiElement, endElement: PsiElement) {
139-
val annotation = startElement as? PsiAnnotation ?: return
140-
val trueExpr = JavaPsiFacade.getElementFactory(project).createLiteralExpression(true)
141-
annotation.setDeclaredAttributeValue("unsafe", trueExpr)
142-
}
143-
}
144127
}

src/main/kotlin/platform/mixin/inspection/injector/ModifyVariableArgsOnlyInspection.kt

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,19 @@ package com.demonwav.mcdev.platform.mixin.inspection.injector
2222

2323
import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler
2424
import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection
25+
import com.demonwav.mcdev.platform.mixin.inspection.fix.AnnotationAttributeFix
2526
import com.demonwav.mcdev.platform.mixin.util.ClassAndMethodNode
2627
import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember
2728
import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.MODIFY_VARIABLE
2829
import com.demonwav.mcdev.platform.mixin.util.hasAccess
2930
import com.demonwav.mcdev.util.constantValue
30-
import com.demonwav.mcdev.util.createLiteralExpression
3131
import com.demonwav.mcdev.util.descriptor
3232
import com.demonwav.mcdev.util.findAnnotation
3333
import com.demonwav.mcdev.util.ifEmpty
34-
import com.intellij.codeInspection.LocalQuickFixOnPsiElement
3534
import com.intellij.codeInspection.ProblemsHolder
36-
import com.intellij.openapi.project.Project
3735
import com.intellij.psi.JavaElementVisitor
38-
import com.intellij.psi.JavaPsiFacade
3936
import com.intellij.psi.PsiAnnotation
40-
import com.intellij.psi.PsiElement
4137
import com.intellij.psi.PsiElementVisitor
42-
import com.intellij.psi.PsiFile
4338
import com.intellij.psi.PsiMethod
4439
import com.intellij.psi.PsiType
4540
import org.objectweb.asm.Opcodes
@@ -64,23 +59,16 @@ class ModifyVariableArgsOnlyInspection : MixinInspection() {
6459

6560
if (shouldReport(modifyVariable, wantedType, methodTargets)) {
6661
val description = "@ModifyVariable may be argsOnly = true"
67-
holder.registerProblem(problemElement, description, AddArgsOnlyFix(modifyVariable))
62+
holder.registerProblem(
63+
problemElement,
64+
description,
65+
AnnotationAttributeFix(modifyVariable, "argsOnly" to true),
66+
)
6867
}
6968
}
7069
}
7170
}
7271

73-
class AddArgsOnlyFix(annotation: PsiAnnotation) : LocalQuickFixOnPsiElement(annotation) {
74-
override fun getFamilyName() = "Add argsOnly = true"
75-
override fun getText() = "Add argsOnly = true"
76-
77-
override fun invoke(project: Project, file: PsiFile, startElement: PsiElement, endElement: PsiElement) {
78-
val annotation = startElement as? PsiAnnotation ?: return
79-
val trueExpr = JavaPsiFacade.getElementFactory(project).createLiteralExpression(true)
80-
annotation.setDeclaredAttributeValue("argsOnly", trueExpr)
81-
}
82-
}
83-
8472
companion object {
8573
fun shouldReport(
8674
annotation: PsiAnnotation,

0 commit comments

Comments
 (0)