Skip to content

Commit 8ff2325

Browse files
committed
Rewrite SideOnly inspection
1 parent 1c7be02 commit 8ff2325

File tree

9 files changed

+887
-0
lines changed

9 files changed

+887
-0
lines changed

src/main/kotlin/platform/forge/util/ForgeConstants.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ object ForgeConstants {
1414

1515
const val SIDE_ONLY_ANNOTATION = "net.minecraftforge.fml.relauncher.SideOnly"
1616
const val SIDE_ANNOTATION = "net.minecraftforge.fml.relauncher.Side"
17+
const val ONLY_IN_ANNOTATION = "net.minecraftforge.api.distmarker.OnlyIn"
18+
const val DIST_ANNOTATION = "net.minecraftforge.api.distmarker.Dist"
1719
const val SIDED_PROXY_ANNOTATION = "net.minecraftforge.fml.common.SidedProxy"
20+
const val DIST_EXECUTOR = "net.minecraftforge.fml.DistExecutor"
1821
const val MOD_ANNOTATION = "net.minecraftforge.fml.common.Mod"
1922
const val CORE_MOD_INTERFACE = "net.minecraftforge.fml.relauncher.IFMLLoadingPlugin"
2023
const val EVENT_HANDLER_ANNOTATION = "net.minecraftforge.fml.common.Mod.EventHandler"

src/main/kotlin/sideonly/Side.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
* Minecraft Dev for IntelliJ
3+
*
4+
* https://minecraftdev.org
5+
*
6+
* Copyright (c) 2020 minecraft-dev
7+
*
8+
* MIT License
9+
*/
10+
11+
package com.demonwav.mcdev.sideonly
12+
13+
enum class Side(val forgeName: String) {
14+
CLIENT("CLIENT"), SERVER("DEDICATED_SERVER"), BOTH("BOTH")
15+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Minecraft Dev for IntelliJ
3+
*
4+
* https://minecraftdev.org
5+
*
6+
* Copyright (c) 2020 minecraft-dev
7+
*
8+
* MIT License
9+
*/
10+
11+
package com.demonwav.mcdev.sideonly
12+
13+
enum class SideHardness {
14+
/** Stripped at runtime or compile-time */
15+
HARD,
16+
/** Not stripped but should only be loaded on the correct side */
17+
SOFT,
18+
EITHER
19+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Minecraft Dev for IntelliJ
3+
*
4+
* https://minecraftdev.org
5+
*
6+
* Copyright (c) 2020 minecraft-dev
7+
*
8+
* MIT License
9+
*/
10+
11+
package com.demonwav.mcdev.sideonly
12+
13+
import com.intellij.psi.PsiElement
14+
15+
class SideInstance private constructor(val side: Side, val element: PsiElement, val reason: String) {
16+
companion object {
17+
fun createSideOnly(side: Side, element: PsiElement): SideInstance {
18+
return SideInstance(side, element, "annotated with @SideOnly(Side.$side)")
19+
}
20+
fun createEnvironment(side: Side, element: PsiElement): SideInstance {
21+
return SideInstance(side, element, "annotated with @Environment(EnvType.$side)")
22+
}
23+
fun createOnlyIn(side: Side, element: PsiElement): SideInstance {
24+
return SideInstance(side, element, "annotated with @OnlyIn(Dist.${side.forgeName})")
25+
}
26+
fun createMcDev(side: Side, element: PsiElement): SideInstance {
27+
return SideInstance(side, element, "annotated with @CheckEnv(Env.$side)")
28+
}
29+
fun createImplicitMcDev(side: Side, element: PsiElement): SideInstance {
30+
return SideInstance(side, element, "implicitly annotated with @CheckEnv(Env.$side)")
31+
}
32+
33+
fun createDistExecutor(side: Side, element: PsiElement): SideInstance {
34+
return SideInstance(side, element, "inside DistExecutor ${side.forgeName}")
35+
}
36+
}
37+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Minecraft Dev for IntelliJ
3+
*
4+
* https://minecraftdev.org
5+
*
6+
* Copyright (c) 2020 minecraft-dev
7+
*
8+
* MIT License
9+
*/
10+
11+
package com.demonwav.mcdev.sideonly
12+
13+
import com.intellij.codeInsight.InferredAnnotationProvider
14+
import com.intellij.psi.JavaPsiFacade
15+
import com.intellij.psi.PsiAnnotation
16+
import com.intellij.psi.PsiModifierListOwner
17+
18+
class SideOnlyInferredAnnotationProvider : InferredAnnotationProvider {
19+
20+
override fun findInferredAnnotation(listOwner: PsiModifierListOwner, annotationFQN: String): PsiAnnotation? {
21+
if (annotationFQN != SideOnlyUtil.MCDEV_SIDEONLY_ANNOTATION) {
22+
return null
23+
}
24+
if (SideOnlyUtil.getExplicitAnnotation(listOwner, SideHardness.EITHER) != null) {
25+
return null
26+
}
27+
val inferredAnnotation = SideOnlyUtil.getExplicitOrInferredAnnotation(listOwner, SideHardness.EITHER)
28+
?: return null
29+
val annotationText =
30+
"@${SideOnlyUtil.MCDEV_SIDEONLY_ANNOTATION}(${SideOnlyUtil.MCDEV_SIDE}.${inferredAnnotation.side})"
31+
return JavaPsiFacade.getElementFactory(listOwner.project).createAnnotationFromText(annotationText, listOwner)
32+
}
33+
34+
override fun findInferredAnnotations(listOwner: PsiModifierListOwner): MutableList<PsiAnnotation> {
35+
val annotation = findInferredAnnotation(listOwner, SideOnlyUtil.MCDEV_SIDEONLY_ANNOTATION)
36+
return if (annotation == null) {
37+
mutableListOf()
38+
} else {
39+
mutableListOf(annotation)
40+
}
41+
}
42+
}
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
/*
2+
* Minecraft Dev for IntelliJ
3+
*
4+
* https://minecraftdev.org
5+
*
6+
* Copyright (c) 2020 minecraft-dev
7+
*
8+
* MIT License
9+
*/
10+
11+
package com.demonwav.mcdev.sideonly
12+
13+
import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool
14+
import com.intellij.codeInspection.ProblemsHolder
15+
import com.intellij.psi.JavaElementVisitor
16+
import com.intellij.psi.PsiAnnotation
17+
import com.intellij.psi.PsiClass
18+
import com.intellij.psi.PsiClassObjectAccessExpression
19+
import com.intellij.psi.PsiElement
20+
import com.intellij.psi.PsiElementVisitor
21+
import com.intellij.psi.PsiField
22+
import com.intellij.psi.PsiInstanceOfExpression
23+
import com.intellij.psi.PsiLambdaExpression
24+
import com.intellij.psi.PsiMember
25+
import com.intellij.psi.PsiMethod
26+
import com.intellij.psi.PsiMethodCallExpression
27+
import com.intellij.psi.PsiMethodReferenceExpression
28+
import com.intellij.psi.PsiNewExpression
29+
import com.intellij.psi.PsiReferenceExpression
30+
import com.intellij.psi.PsiType
31+
import com.intellij.psi.PsiTypeCastExpression
32+
import com.intellij.psi.util.parentOfType
33+
import com.intellij.psi.util.parents
34+
35+
class SideOnlyInspection : AbstractBaseJavaLocalInspectionTool() {
36+
override fun getStaticDescription() = "SideOnly problems"
37+
38+
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
39+
return Visitor(holder)
40+
}
41+
42+
private class Visitor(private val problems: ProblemsHolder) : JavaElementVisitor() {
43+
44+
// CHECK REFERENCES TO HARD AND SOFT SIDEONLY MEMBERS
45+
46+
override fun visitClass(clazz: PsiClass) {
47+
val classSide = SideOnlyUtil.getContextSide(clazz, SideHardness.EITHER)
48+
49+
val superClass = clazz.superClass
50+
if (superClass != null) {
51+
val targetSide = SideOnlyUtil.getContextSide(superClass, SideHardness.EITHER)
52+
val problemElement = clazz.extendsList?.referenceElements?.firstOrNull()
53+
if (problemElement != null && targetSide != null && targetSide.side != classSide?.side) {
54+
problems.registerProblem(
55+
problemElement,
56+
SideOnlyUtil.createInspectionMessage(classSide, targetSide)
57+
)
58+
}
59+
}
60+
61+
val interfaceList = if (clazz.isInterface) {
62+
clazz.extendsList
63+
} else {
64+
clazz.implementsList
65+
}?.let { it.referencedTypes zip it.referenceElements } ?: emptyList()
66+
val sidedInterfaces = SideOnlyUtil.getSidedInterfaces(clazz)
67+
for ((itf, problemElement) in interfaceList) {
68+
val itfClass = itf.resolve() ?: continue
69+
val targetSide = SideOnlyUtil.getContextSide(itfClass, SideHardness.EITHER)
70+
val sidedInterface = sidedInterfaces[itfClass.qualifiedName]
71+
if (targetSide != null && targetSide.side != classSide?.side && targetSide.side != sidedInterface) {
72+
problems.registerProblem(
73+
problemElement,
74+
SideOnlyUtil.createInspectionMessage(classSide, targetSide)
75+
)
76+
}
77+
}
78+
}
79+
80+
override fun visitField(field: PsiField) {
81+
checkEitherAccess(field.type, field.typeElement)
82+
}
83+
84+
override fun visitMethod(method: PsiMethod) {
85+
checkEitherAccess(method.returnType, method.returnTypeElement)
86+
for (parameter in method.parameterList.parameters) {
87+
checkEitherAccess(parameter.type, parameter.typeElement)
88+
}
89+
for ((type, element) in method.throwsList.referencedTypes zip method.throwsList.referenceElements) {
90+
checkEitherAccess(type, element)
91+
}
92+
93+
val body = method.body ?: return
94+
SideOnlyUtil.analyzeBodyForSoftSideProblems(body, problems)
95+
}
96+
97+
override fun visitLambdaExpression(expression: PsiLambdaExpression) {
98+
// TODO: lambda parameter types?
99+
100+
val body = expression.body ?: return
101+
SideOnlyUtil.analyzeBodyForSoftSideProblems(body, problems)
102+
}
103+
104+
private fun checkEitherAccess(targetType: PsiType?, element: PsiElement?) {
105+
targetType ?: return
106+
element ?: return
107+
108+
val targetClass = SideOnlyUtil.getClassInType(targetType) ?: return
109+
val contextSide = SideOnlyUtil.getContextSide(element, SideHardness.EITHER)
110+
val targetSide = SideOnlyUtil.getContextSide(targetClass, SideHardness.EITHER)
111+
if (targetSide != null && targetSide.side != contextSide?.side) {
112+
problems.registerProblem(element, SideOnlyUtil.createInspectionMessage(contextSide, targetSide))
113+
}
114+
}
115+
116+
// CHECK REFERENCES TO HARD SIDEONLY MEMBERS
117+
118+
override fun visitClassObjectAccessExpression(expression: PsiClassObjectAccessExpression) {
119+
// class references in annotations are always legal
120+
if (expression.parentOfType(PsiAnnotation::class, PsiMember::class, PsiClass::class) is PsiAnnotation) {
121+
return
122+
}
123+
124+
checkHardAccess(expression.operand.type, expression.operand)
125+
}
126+
127+
override fun visitInstanceOfExpression(expression: PsiInstanceOfExpression) {
128+
checkHardAccess(expression.checkType?.type, expression.checkType)
129+
}
130+
131+
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
132+
checkHardAccess(expression.resolveMethod(), expression.methodExpression.referenceNameElement)
133+
}
134+
135+
override fun visitNewExpression(expression: PsiNewExpression) {
136+
checkHardAccess(
137+
expression.classOrAnonymousClassReference?.resolve(),
138+
expression.classOrAnonymousClassReference
139+
)
140+
}
141+
142+
override fun visitReferenceExpression(expression: PsiReferenceExpression) {
143+
val field = expression.resolve() as? PsiField ?: return
144+
checkHardAccess(field, expression.referenceNameElement)
145+
}
146+
147+
override fun visitMethodReferenceExpression(expression: PsiMethodReferenceExpression) {
148+
checkHardAccess(expression.potentiallyApplicableMember, expression.referenceNameElement)
149+
}
150+
151+
override fun visitTypeCastExpression(expression: PsiTypeCastExpression) {
152+
checkHardAccess(expression.castType?.type, expression.castType)
153+
}
154+
155+
private fun checkHardAccess(target: PsiElement?, reference: PsiElement?) {
156+
target ?: return
157+
reference ?: return
158+
159+
val targetSide = SideOnlyUtil.getContextSide(target, SideHardness.HARD) ?: return
160+
val contextSide = getContextSideForHardAccess(reference)
161+
if (targetSide.side != contextSide?.side) {
162+
val contextSideForMsg = SideOnlyUtil.getContextSide(reference, SideHardness.EITHER)
163+
problems.registerProblem(reference, SideOnlyUtil.createInspectionMessage(contextSideForMsg, targetSide))
164+
}
165+
}
166+
167+
private fun checkHardAccess(targetType: PsiType?, element: PsiElement?) {
168+
targetType ?: return
169+
checkHardAccess(SideOnlyUtil.getClassInType(targetType), element)
170+
}
171+
172+
private fun getContextSideForHardAccess(element: PsiElement): SideInstance? {
173+
// Same as SideOnlyUtil.getContextSide(element, SideHardness.EITHER), with the exception that soft-sidedness
174+
// of methods are ignored, as the mere presence of these methods can trip the verifier.
175+
val softSide = SideOnlyUtil.getContextSide(element, SideHardness.SOFT)
176+
val hardSide = SideOnlyUtil.getContextSide(element, SideHardness.HARD)
177+
return if (softSide != null &&
178+
softSide.element !is PsiMethod &&
179+
softSide.element.parents().contains(hardSide?.element)
180+
) {
181+
softSide
182+
} else {
183+
hardSide
184+
}
185+
}
186+
}
187+
}

0 commit comments

Comments
 (0)