Skip to content

Commit

Permalink
Update rpc related IDE plugin checks
Browse files Browse the repository at this point in the history
  • Loading branch information
chippmann committed Jul 15, 2022
1 parent a17910f commit 10a2942
Show file tree
Hide file tree
Showing 13 changed files with 211 additions and 118 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
package godot.tests.rpctests

import godot.Node
import godot.annotation.RegisterClass
import godot.annotation.RegisterFunction
import godot.annotation.RegisterProperty
import godot.annotation.Rpc
import godot.annotation.Sync
import godot.annotation.*

@RegisterClass("RPCTests")
class RpcTests : Node() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package godot.intellij.plugin.annotator.function

import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.lang.annotation.AnnotationHolder
import com.intellij.lang.annotation.Annotator
import com.intellij.psi.PsiElement
import godot.intellij.plugin.GodotPluginBundle
import godot.intellij.plugin.data.model.RPC_ANNOTATION
import godot.intellij.plugin.extension.isInGodotRoot
import godot.intellij.plugin.extension.registerProblem
import godot.intellij.plugin.quickfix.TransferModeIgnoresChannelQuickFix
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
import org.jetbrains.kotlin.idea.util.findAnnotation
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.nj2k.postProcessing.resolve
import org.jetbrains.kotlin.psi.KtAnnotated
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.psiUtil.getChildOfType

class RpcAnnotator : Annotator {
private val transferModeIgnoresChannelQuickFix by lazy { TransferModeIgnoresChannelQuickFix() }

override fun annotate(element: PsiElement, holder: AnnotationHolder) {
if (!element.isInGodotRoot()) return

if (element is KtAnnotated && element.findAnnotation(FqName(RPC_ANNOTATION)) != null) {
val valueArgumentList = element.findAnnotation(FqName(RPC_ANNOTATION))?.valueArgumentList ?: return

val transferModeValueArgument = valueArgumentList
.arguments
.firstOrNull { it.isNamed() && it.getArgumentName()?.text == "transferMode" } // named; so position is not relevant
?: valueArgumentList
.arguments
.getOrNull(2) // not named; so getting by argument position

val isTransferModeUnreliableOrdered = transferModeValueArgument
?.getArgumentExpression()
?.getChildOfType<KtNameReferenceExpression>()
?.resolve()
?.getKotlinFqName()
?.asString() == "godot.annotation.TransferMode.UNRELIABLE_ORDERED"

val channelElement = valueArgumentList
.arguments
.firstOrNull { it.isNamed() && it.getArgumentName()?.text == "transferChannel" } // named; so position is not relevant
?: valueArgumentList
.arguments
.getOrNull(3)// not named; so getting by argument position

val channel = if (channelElement?.isNamed() == true) {
channelElement.text.substringAfterLast("=").trim()
} else {
channelElement?.text
}
?.removeSurrounding("\"")
?.toIntOrNull() ?: 0

if (channelElement != null && !isTransferModeUnreliableOrdered && channel != 0) {
holder.registerProblem(
message = GodotPluginBundle.message("problem.function.rpcChannelSetWhenTransferTypeIgnoresIt"),
errorLocation = channelElement,
quickFixes = arrayOf(transferModeIgnoresChannelQuickFix),
problemHighlightType = ProblemHighlightType.WEAK_WARNING
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ class FunctionReferenceAnnotator : Annotator {
is KtCallableReferenceExpression -> {
SignalFunctionReferenceChecker.checkSignalConnectionFunction(element, holder)
RpcFunctionReferenceChecker.checkRpcTargetFunction(element, holder)
RSetPropertyReferenceChecker.checkRpcTargetProperty(element, holder)
CallFunctionReferenceChecker.checkGeneralTargetFunction(element, holder)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,79 +0,0 @@
package godot.intellij.plugin.annotator.reference

import com.intellij.lang.annotation.AnnotationHolder
import godot.intellij.plugin.GodotPluginBundle
import godot.intellij.plugin.data.model.REGISTER_PROPERTY_ANNOTATION
import godot.intellij.plugin.extension.registerProblem
import godot.intellij.plugin.quickfix.TargetPropertyNotRegisteredQuickFix
import org.jetbrains.kotlin.idea.base.utils.fqname.getKotlinFqName
import org.jetbrains.kotlin.idea.util.findAnnotation
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.nj2k.postProcessing.resolve
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtCallableReferenceExpression
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtValueArgument
import org.jetbrains.kotlin.psi.psiUtil.containingClass
import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull

object RSetPropertyReferenceChecker {
private val rsetFunctionNames = listOf(
"rset",
"rsetId",
"rsetUnreliable",
"rsetUnreliableId",
)

fun checkRpcTargetProperty(element: KtCallableReferenceExpression, holder: AnnotationHolder) {
val relevantParent = element.parent.parent.parent
val propertyReference = relevantParent.children.firstIsInstanceOrNull<KtNameReferenceExpression>()
if (
relevantParent is KtCallExpression &&
rsetFunctionNames.contains(propertyReference?.text) &&
(propertyReference?.resolve() as? KtNamedFunction)?.containingClass()?.fqName?.asString() == "godot.Node"
) {
val targetProperty = element
.callableReference
.resolve() as? KtProperty

val registerPropertyAnnotation = targetProperty?.findAnnotation(FqName(REGISTER_PROPERTY_ANNOTATION))
if (targetProperty != null && registerPropertyAnnotation == null) {
holder.registerProblem(
GodotPluginBundle.message("problem.rpc.calledPropertyNotRegistered"),
element,
TargetPropertyNotRegisteredQuickFix()
)
} else {
if (
registerPropertyAnnotation?.valueArguments?.isEmpty() == true ||
registerPropertyAnnotation
?.valueArgumentList
?.getChildrenOfType<KtValueArgument>()
?.mapNotNull { ktValueArgument ->
ktValueArgument
.getArgumentExpression()
?.getChildOfType<KtNameReferenceExpression>()
?.resolve()
?.getKotlinFqName()
?.asString()
}
?.filter { fqName ->
fqName.startsWith("godot.MultiplayerAPI.RPCMode")
}
?.any { fqName ->
fqName == "godot.MultiplayerAPI.RPCMode.DISABLED"
} == true
) {
holder.registerProblem(
GodotPluginBundle.message("problem.rpc.calledPropertyNotAccessible"),
element
)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ package godot.intellij.plugin.annotator.reference
import com.intellij.lang.annotation.AnnotationHolder
import godot.intellij.plugin.GodotPluginBundle
import godot.intellij.plugin.data.model.REGISTER_FUNCTION_ANNOTATION
import godot.intellij.plugin.data.model.RPC_ANNOTATION
import godot.intellij.plugin.extension.registerProblem
import godot.intellij.plugin.quickfix.TargetFunctionNotRegisteredQuickFix
import godot.intellij.plugin.quickfix.TargetFunctionHasNoRpcAnnotationQuickFix
import godot.intellij.plugin.quickfix.TargetFunctionsRpcAnnotationHasRpcModeDisabled
import org.jetbrains.kotlin.idea.base.utils.fqname.getKotlinFqName
import org.jetbrains.kotlin.idea.util.findAnnotation
import org.jetbrains.kotlin.name.FqName
Expand All @@ -13,9 +16,8 @@ import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtCallableReferenceExpression
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtValueArgument
import org.jetbrains.kotlin.psi.psiUtil.containingClass
import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull

object RpcFunctionReferenceChecker {
Expand All @@ -26,6 +28,10 @@ object RpcFunctionReferenceChecker {
"rpcUnreliableId",
)

private val targetFunctionNotRegisteredQuickFix by lazy { TargetFunctionNotRegisteredQuickFix() }
private val targetFunctionHasNoRpcAnnotationQuickFix by lazy { TargetFunctionHasNoRpcAnnotationQuickFix() }
private val targetFunctionsRpcAnnotationHasRpcModeDisabled by lazy { TargetFunctionsRpcAnnotationHasRpcModeDisabled() }

fun checkRpcTargetFunction(element: KtCallableReferenceExpression, holder: AnnotationHolder) {
val relevantParent = element.parent.parent.parent
val callReference = relevantParent.children.firstIsInstanceOrNull<KtNameReferenceExpression>()
Expand All @@ -39,29 +45,49 @@ object RpcFunctionReferenceChecker {
.resolve() as? KtNamedFunction

val registerFunctionAnnotation = targetFunction?.findAnnotation(FqName(REGISTER_FUNCTION_ANNOTATION))
if (targetFunction != null && registerFunctionAnnotation == null) {
holder.registerProblem(
GodotPluginBundle.message("problem.rpc.calledFunctionNotRegistered"),
element,
TargetFunctionNotRegisteredQuickFix()
)
} else {
if (
registerFunctionAnnotation?.valueArguments?.isEmpty() == true ||
registerFunctionAnnotation
?.valueArgumentList
?.getChildOfType<KtValueArgument>()
?.getArgumentExpression()
?.getChildOfType<KtNameReferenceExpression>()
?.resolve()
?.getKotlinFqName()
?.asString() == "godot.MultiplayerAPI.RPCMode.DISABLED"
) {
val rpcAnnotation = targetFunction?.findAnnotation(FqName(RPC_ANNOTATION))

when {
targetFunction != null && registerFunctionAnnotation == null -> {
holder.registerProblem(
GodotPluginBundle.message("problem.rpc.calledFunctionNotAccessible"),
element
GodotPluginBundle.message("problem.rpc.calledFunctionNotRegistered"),
element,
targetFunctionNotRegisteredQuickFix
)
}
targetFunction != null && rpcAnnotation == null -> {
holder.registerProblem(
GodotPluginBundle.message("problem.rpc.calledFunctionHasNoRpcAnnotation"),
element,
targetFunctionHasNoRpcAnnotationQuickFix
)
}
else -> {
val rpcModeValueArgument = rpcAnnotation
?.valueArgumentList
?.arguments
?.firstOrNull { it.isNamed() && it.getArgumentName()?.text == "rpcMode" } // named; so position is not relevant
?: rpcAnnotation
?.valueArgumentList
?.arguments
?.getOrNull(0) // not named; so getting by argument position

if (
rpcModeValueArgument
?.getArgumentExpression()
?.getChildrenOfType<KtNameReferenceExpression>()
?.lastOrNull()
?.resolve()
?.getKotlinFqName()
?.asString() == "godot.annotation.RpcMode.DISABLED"
) {
holder.registerProblem(
GodotPluginBundle.message("problem.rpc.calledFunctionNotAccessible"),
element,
targetFunctionsRpcAnnotationHasRpcModeDisabled
)
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ class RegisterSignalAnnotator : Annotator {
private fun checkMutability(ktProperty: KtProperty, holder: AnnotationHolder) {
if (ktProperty.isVar) {
holder.registerProblem(
GodotPluginBundle.message("problem.signal.mutability"),
ktProperty.valOrVarKeyword,
mutabilityQuickFix,
ProblemHighlightType.WARNING
message = GodotPluginBundle.message("problem.signal.mutability"),
errorLocation = ktProperty.valOrVarKeyword,
quickFixes = arrayOf(mutabilityQuickFix),
problemHighlightType = ProblemHighlightType.WARNING,
)
}
}
Expand All @@ -44,9 +44,9 @@ class RegisterSignalAnnotator : Annotator {
val type = ktProperty.type() ?: return
if (!type.getJetTypeFqName(false).startsWith("godot.signals.Signal")) {
holder.registerProblem(
GodotPluginBundle.message("problem.signal.wrongType"),
getInitializerProblemLocation(ktProperty),
useDelegateQuickFix
message = GodotPluginBundle.message("problem.signal.wrongType"),
errorLocation = getInitializerProblemLocation(ktProperty),
quickFixes = arrayOf(useDelegateQuickFix)
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ const val REGISTER_PROPERTY_ANNOTATION = "godot.annotation.RegisterProperty"
const val EXPORT_ANNOTATION = "godot.annotation.Export"
const val REGISTER_SIGNAL_ANNOTATION = "godot.annotation.RegisterSignal"
const val CORE_TYPE_HELPER_ANNOTATION = "godot.annotation.CoreTypeHelper"
const val RPC_ANNOTATION = "godot.annotation.Rpc"
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,18 @@ import com.intellij.lang.annotation.AnnotationHolder
import com.intellij.lang.annotation.HighlightSeverity
import com.intellij.psi.PsiElement

fun AnnotationHolder.registerProblem(message: String, errorLocation: PsiElement, quickFix: LocalQuickFix? = null, problemHighlightType: ProblemHighlightType = ProblemHighlightType.GENERIC_ERROR) {
fun AnnotationHolder.registerProblem(message: String, errorLocation: PsiElement, vararg quickFixes: LocalQuickFix = arrayOf(), problemHighlightType: ProblemHighlightType = ProblemHighlightType.GENERIC_ERROR) {
val annotationBuilder = newAnnotation(highlightSeverityFromHighlightType(problemHighlightType), message)
if (quickFix != null) {

quickFixes.forEach { quickFix ->
annotationBuilder
.newLocalQuickFix(
quickFix,
getProblemDescriptor(errorLocation, quickFix, problemHighlightType)
)
.registerFix()
}

annotationBuilder
.range(errorLocation)
.highlightType(problemHighlightType)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package godot.intellij.plugin.quickfix

import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import godot.intellij.plugin.GodotPluginBundle
import godot.intellij.plugin.data.model.RPC_ANNOTATION
import org.jetbrains.kotlin.idea.util.addAnnotation
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.nj2k.postProcessing.resolve
import org.jetbrains.kotlin.psi.KtCallableReferenceExpression
import org.jetbrains.kotlin.psi.KtNamedFunction

class TargetFunctionHasNoRpcAnnotationQuickFix : LocalQuickFix {
override fun getFamilyName(): String = GodotPluginBundle.message("quickFix.function.connectedFunctionHasNoRpcAnnotationRegistered.familyName")

override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val ktNamedFunction = (descriptor.psiElement as? KtCallableReferenceExpression)
?.callableReference
?.resolve() as? KtNamedFunction

ktNamedFunction?.addAnnotation(FqName(RPC_ANNOTATION))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package godot.intellij.plugin.quickfix

import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.project.Project
import godot.intellij.plugin.GodotPluginBundle
import godot.intellij.plugin.data.model.RPC_ANNOTATION
import org.jetbrains.kotlin.idea.util.findAnnotation
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.nj2k.postProcessing.resolve
import org.jetbrains.kotlin.psi.KtCallableReferenceExpression
import org.jetbrains.kotlin.psi.KtNamedFunction

class TargetFunctionsRpcAnnotationHasRpcModeDisabled : LocalQuickFix {
override fun getFamilyName(): String = GodotPluginBundle.message("quickFix.function.connectedFunctionsRpcAnnotationHasRpcModeDisabled.familyName")

override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val ktNamedFunction = (descriptor.psiElement as? KtCallableReferenceExpression)
?.callableReference
?.resolve() as? KtNamedFunction

val rpcAnnotationValueArgumentList = ktNamedFunction
?.findAnnotation(FqName(RPC_ANNOTATION))
?.valueArgumentList

val rpcModeValueArgument = rpcAnnotationValueArgumentList
?.arguments
?.firstOrNull { it.isNamed() && it.getArgumentName()?.text == "rpcMode" } // named; so position is not relevant
?: rpcAnnotationValueArgumentList
?.arguments
?.getOrNull(0) // not named; so getting by argument position

rpcModeValueArgument?.let { rpcAnnotationValueArgumentList?.removeArgument(it) }
}
}
Loading

0 comments on commit 10a2942

Please sign in to comment.