Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Rewrite SideOnly inspection
  • Loading branch information
Earthcomputer committed Aug 24, 2020
commit 8ff2325fabc6421a5ac63d4ac2045ca948f096c0
3 changes: 3 additions & 0 deletions src/main/kotlin/platform/forge/util/ForgeConstants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ object ForgeConstants {

const val SIDE_ONLY_ANNOTATION = "net.minecraftforge.fml.relauncher.SideOnly"
const val SIDE_ANNOTATION = "net.minecraftforge.fml.relauncher.Side"
const val ONLY_IN_ANNOTATION = "net.minecraftforge.api.distmarker.OnlyIn"
const val DIST_ANNOTATION = "net.minecraftforge.api.distmarker.Dist"
const val SIDED_PROXY_ANNOTATION = "net.minecraftforge.fml.common.SidedProxy"
const val DIST_EXECUTOR = "net.minecraftforge.fml.DistExecutor"
const val MOD_ANNOTATION = "net.minecraftforge.fml.common.Mod"
const val CORE_MOD_INTERFACE = "net.minecraftforge.fml.relauncher.IFMLLoadingPlugin"
const val EVENT_HANDLER_ANNOTATION = "net.minecraftforge.fml.common.Mod.EventHandler"
Expand Down
15 changes: 15 additions & 0 deletions src/main/kotlin/sideonly/Side.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Minecraft Dev for IntelliJ
*
* https://minecraftdev.org
*
* Copyright (c) 2020 minecraft-dev
*
* MIT License
*/

package com.demonwav.mcdev.sideonly

enum class Side(val forgeName: String) {
CLIENT("CLIENT"), SERVER("DEDICATED_SERVER"), BOTH("BOTH")
}
19 changes: 19 additions & 0 deletions src/main/kotlin/sideonly/SideHardness.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Minecraft Dev for IntelliJ
*
* https://minecraftdev.org
*
* Copyright (c) 2020 minecraft-dev
*
* MIT License
*/

package com.demonwav.mcdev.sideonly

enum class SideHardness {
/** Stripped at runtime or compile-time */
HARD,
/** Not stripped but should only be loaded on the correct side */
SOFT,
EITHER
}
37 changes: 37 additions & 0 deletions src/main/kotlin/sideonly/SideInstance.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Minecraft Dev for IntelliJ
*
* https://minecraftdev.org
*
* Copyright (c) 2020 minecraft-dev
*
* MIT License
*/

package com.demonwav.mcdev.sideonly

import com.intellij.psi.PsiElement

class SideInstance private constructor(val side: Side, val element: PsiElement, val reason: String) {
companion object {
fun createSideOnly(side: Side, element: PsiElement): SideInstance {
return SideInstance(side, element, "annotated with @SideOnly(Side.$side)")
}
fun createEnvironment(side: Side, element: PsiElement): SideInstance {
return SideInstance(side, element, "annotated with @Environment(EnvType.$side)")
}
fun createOnlyIn(side: Side, element: PsiElement): SideInstance {
return SideInstance(side, element, "annotated with @OnlyIn(Dist.${side.forgeName})")
}
fun createMcDev(side: Side, element: PsiElement): SideInstance {
return SideInstance(side, element, "annotated with @CheckEnv(Env.$side)")
}
fun createImplicitMcDev(side: Side, element: PsiElement): SideInstance {
return SideInstance(side, element, "implicitly annotated with @CheckEnv(Env.$side)")
}

fun createDistExecutor(side: Side, element: PsiElement): SideInstance {
return SideInstance(side, element, "inside DistExecutor ${side.forgeName}")
}
}
}
42 changes: 42 additions & 0 deletions src/main/kotlin/sideonly/SideOnlyInferredAnnotationProvider.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Minecraft Dev for IntelliJ
*
* https://minecraftdev.org
*
* Copyright (c) 2020 minecraft-dev
*
* MIT License
*/

package com.demonwav.mcdev.sideonly

import com.intellij.codeInsight.InferredAnnotationProvider
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiAnnotation
import com.intellij.psi.PsiModifierListOwner

class SideOnlyInferredAnnotationProvider : InferredAnnotationProvider {

override fun findInferredAnnotation(listOwner: PsiModifierListOwner, annotationFQN: String): PsiAnnotation? {
if (annotationFQN != SideOnlyUtil.MCDEV_SIDEONLY_ANNOTATION) {
return null
}
if (SideOnlyUtil.getExplicitAnnotation(listOwner, SideHardness.EITHER) != null) {
return null
}
val inferredAnnotation = SideOnlyUtil.getExplicitOrInferredAnnotation(listOwner, SideHardness.EITHER)
?: return null
val annotationText =
"@${SideOnlyUtil.MCDEV_SIDEONLY_ANNOTATION}(${SideOnlyUtil.MCDEV_SIDE}.${inferredAnnotation.side})"
return JavaPsiFacade.getElementFactory(listOwner.project).createAnnotationFromText(annotationText, listOwner)
}

override fun findInferredAnnotations(listOwner: PsiModifierListOwner): MutableList<PsiAnnotation> {
val annotation = findInferredAnnotation(listOwner, SideOnlyUtil.MCDEV_SIDEONLY_ANNOTATION)
return if (annotation == null) {
mutableListOf()
} else {
mutableListOf(annotation)
}
}
}
187 changes: 187 additions & 0 deletions src/main/kotlin/sideonly/SideOnlyInspection.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
* Minecraft Dev for IntelliJ
*
* https://minecraftdev.org
*
* Copyright (c) 2020 minecraft-dev
*
* MIT License
*/

package com.demonwav.mcdev.sideonly

import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.JavaElementVisitor
import com.intellij.psi.PsiAnnotation
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiClassObjectAccessExpression
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiField
import com.intellij.psi.PsiInstanceOfExpression
import com.intellij.psi.PsiLambdaExpression
import com.intellij.psi.PsiMember
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.PsiMethodReferenceExpression
import com.intellij.psi.PsiNewExpression
import com.intellij.psi.PsiReferenceExpression
import com.intellij.psi.PsiType
import com.intellij.psi.PsiTypeCastExpression
import com.intellij.psi.util.parentOfType
import com.intellij.psi.util.parents

class SideOnlyInspection : AbstractBaseJavaLocalInspectionTool() {
override fun getStaticDescription() = "SideOnly problems"

override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return Visitor(holder)
}

private class Visitor(private val problems: ProblemsHolder) : JavaElementVisitor() {

// CHECK REFERENCES TO HARD AND SOFT SIDEONLY MEMBERS

override fun visitClass(clazz: PsiClass) {
val classSide = SideOnlyUtil.getContextSide(clazz, SideHardness.EITHER)

val superClass = clazz.superClass
if (superClass != null) {
val targetSide = SideOnlyUtil.getContextSide(superClass, SideHardness.EITHER)
val problemElement = clazz.extendsList?.referenceElements?.firstOrNull()
if (problemElement != null && targetSide != null && targetSide.side != classSide?.side) {
problems.registerProblem(
problemElement,
SideOnlyUtil.createInspectionMessage(classSide, targetSide)
)
}
}

val interfaceList = if (clazz.isInterface) {
clazz.extendsList
} else {
clazz.implementsList
}?.let { it.referencedTypes zip it.referenceElements } ?: emptyList()
val sidedInterfaces = SideOnlyUtil.getSidedInterfaces(clazz)
for ((itf, problemElement) in interfaceList) {
val itfClass = itf.resolve() ?: continue
val targetSide = SideOnlyUtil.getContextSide(itfClass, SideHardness.EITHER)
val sidedInterface = sidedInterfaces[itfClass.qualifiedName]
if (targetSide != null && targetSide.side != classSide?.side && targetSide.side != sidedInterface) {
problems.registerProblem(
problemElement,
SideOnlyUtil.createInspectionMessage(classSide, targetSide)
)
}
}
}

override fun visitField(field: PsiField) {
checkEitherAccess(field.type, field.typeElement)
}

override fun visitMethod(method: PsiMethod) {
checkEitherAccess(method.returnType, method.returnTypeElement)
for (parameter in method.parameterList.parameters) {
checkEitherAccess(parameter.type, parameter.typeElement)
}
for ((type, element) in method.throwsList.referencedTypes zip method.throwsList.referenceElements) {
checkEitherAccess(type, element)
}

val body = method.body ?: return
SideOnlyUtil.analyzeBodyForSoftSideProblems(body, problems)
}

override fun visitLambdaExpression(expression: PsiLambdaExpression) {
// TODO: lambda parameter types?

val body = expression.body ?: return
SideOnlyUtil.analyzeBodyForSoftSideProblems(body, problems)
}

private fun checkEitherAccess(targetType: PsiType?, element: PsiElement?) {
targetType ?: return
element ?: return

val targetClass = SideOnlyUtil.getClassInType(targetType) ?: return
val contextSide = SideOnlyUtil.getContextSide(element, SideHardness.EITHER)
val targetSide = SideOnlyUtil.getContextSide(targetClass, SideHardness.EITHER)
if (targetSide != null && targetSide.side != contextSide?.side) {
problems.registerProblem(element, SideOnlyUtil.createInspectionMessage(contextSide, targetSide))
}
}

// CHECK REFERENCES TO HARD SIDEONLY MEMBERS

override fun visitClassObjectAccessExpression(expression: PsiClassObjectAccessExpression) {
// class references in annotations are always legal
if (expression.parentOfType(PsiAnnotation::class, PsiMember::class, PsiClass::class) is PsiAnnotation) {
return
}

checkHardAccess(expression.operand.type, expression.operand)
}

override fun visitInstanceOfExpression(expression: PsiInstanceOfExpression) {
checkHardAccess(expression.checkType?.type, expression.checkType)
}

override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
checkHardAccess(expression.resolveMethod(), expression.methodExpression.referenceNameElement)
}

override fun visitNewExpression(expression: PsiNewExpression) {
checkHardAccess(
expression.classOrAnonymousClassReference?.resolve(),
expression.classOrAnonymousClassReference
)
}

override fun visitReferenceExpression(expression: PsiReferenceExpression) {
val field = expression.resolve() as? PsiField ?: return
checkHardAccess(field, expression.referenceNameElement)
}

override fun visitMethodReferenceExpression(expression: PsiMethodReferenceExpression) {
checkHardAccess(expression.potentiallyApplicableMember, expression.referenceNameElement)
}

override fun visitTypeCastExpression(expression: PsiTypeCastExpression) {
checkHardAccess(expression.castType?.type, expression.castType)
}

private fun checkHardAccess(target: PsiElement?, reference: PsiElement?) {
target ?: return
reference ?: return

val targetSide = SideOnlyUtil.getContextSide(target, SideHardness.HARD) ?: return
val contextSide = getContextSideForHardAccess(reference)
if (targetSide.side != contextSide?.side) {
val contextSideForMsg = SideOnlyUtil.getContextSide(reference, SideHardness.EITHER)
problems.registerProblem(reference, SideOnlyUtil.createInspectionMessage(contextSideForMsg, targetSide))
}
}

private fun checkHardAccess(targetType: PsiType?, element: PsiElement?) {
targetType ?: return
checkHardAccess(SideOnlyUtil.getClassInType(targetType), element)
}

private fun getContextSideForHardAccess(element: PsiElement): SideInstance? {
// Same as SideOnlyUtil.getContextSide(element, SideHardness.EITHER), with the exception that soft-sidedness
// of methods are ignored, as the mere presence of these methods can trip the verifier.
val softSide = SideOnlyUtil.getContextSide(element, SideHardness.SOFT)
val hardSide = SideOnlyUtil.getContextSide(element, SideHardness.HARD)
return if (softSide != null &&
softSide.element !is PsiMethod &&
softSide.element.parents().contains(hardSide?.element)
) {
softSide
} else {
hardSide
}
}
}
}
Loading