User: rednesto
Date: 17 Jun 23 08:55
Revision: 638c35fc692d3ebd304aaf9fe4892104e05caf19
Summary:
Add inspection to validate fabric entrypoints
TeamCity URL: https://ci.mcdev.io/viewModification.html?tab=vcsModificationFiles&modId=8528&personal=false
Index: src/main/kotlin/platform/fabric/inspection/FabricEntrypointsInspection.kt
===================================================================
--- src/main/kotlin/platform/fabric/inspection/FabricEntrypointsInspection.kt (revision 638c35fc692d3ebd304aaf9fe4892104e05caf19)
+++ src/main/kotlin/platform/fabric/inspection/FabricEntrypointsInspection.kt (revision 638c35fc692d3ebd304aaf9fe4892104e05caf19)
@@ -0,0 +1,151 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2023 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.fabric.inspection
+
+import com.demonwav.mcdev.platform.fabric.reference.EntryPointReference
+import com.demonwav.mcdev.platform.fabric.util.FabricConstants
+import com.intellij.codeInspection.InspectionManager
+import com.intellij.codeInspection.LocalInspectionTool
+import com.intellij.codeInspection.ProblemDescriptor
+import com.intellij.codeInspection.ProblemHighlightType
+import com.intellij.codeInspection.ProblemsHolder
+import com.intellij.json.psi.JsonElementVisitor
+import com.intellij.json.psi.JsonProperty
+import com.intellij.json.psi.JsonStringLiteral
+import com.intellij.psi.JavaPsiFacade
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiClassType
+import com.intellij.psi.PsiElementVisitor
+import com.intellij.psi.PsiField
+import com.intellij.psi.PsiFile
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.PsiModifier
+import com.intellij.psi.util.parentOfType
+
+class FabricEntrypointsInspection : LocalInspectionTool() {
+
+ override fun getStaticDescription() = "Validates entrypoints declared in Fabric mod JSON files."
+
+ override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
+ if (holder.file.name == FabricConstants.FABRIC_MOD_JSON) {
+ return Visitor(holder)
+ }
+ return PsiElementVisitor.EMPTY_VISITOR
+ }
+
+ override fun processFile(file: PsiFile, manager: InspectionManager): List {
+ if (file.name == FabricConstants.FABRIC_MOD_JSON) {
+ return super.processFile(file, manager)
+ }
+ return emptyList()
+ }
+
+ private class Visitor(private val holder: ProblemsHolder) : JsonElementVisitor() {
+
+ override fun visitStringLiteral(literal: JsonStringLiteral) {
+ for (reference in literal.references) {
+ if (reference !is EntryPointReference.Reference) {
+ continue
+ }
+
+ val resolved = reference.multiResolve(false)
+ if (resolved.size > 1) {
+ holder.registerProblem(
+ literal,
+ "Ambiguous member reference",
+ ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
+ reference.rangeInElement,
+ )
+ }
+
+ val element = resolved.singleOrNull()?.element
+ when {
+ element is PsiClass && !literal.text.contains("::") -> {
+ val propertyKey = literal.parentOfType()?.name
+ val expectedType = propertyKey?.let { FabricConstants.ENTRYPOINT_BY_TYPE[it] }
+ if (propertyKey != null && expectedType != null &&
+ !isEntrypointOfCorrectType(element, propertyKey)
+ ) {
+ holder.registerProblem(
+ literal,
+ "'$propertyKey' entrypoints must implement $expectedType",
+ ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
+ reference.rangeInElement,
+ )
+ } else if (element.constructors.isNotEmpty() &&
+ element.constructors.find { !it.hasParameters() } == null
+ ) {
+ holder.registerProblem(
+ literal,
+ "Entrypoint class must have an empty constructor",
+ ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
+ reference.rangeInElement,
+ )
+ }
+ }
+
+ element is PsiMethod -> {
+ if (element.hasParameters()) {
+ holder.registerProblem(
+ literal,
+ "Entrypoint method must have no parameters",
+ ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
+ reference.rangeInElement,
+ )
+ }
+ }
+
+ element is PsiField -> {
+ if (!element.hasModifierProperty(PsiModifier.STATIC)) {
+ holder.registerProblem(
+ literal,
+ "Entrypoint field must be static",
+ ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
+ reference.rangeInElement,
+ )
+ }
+
+ val propertyKey = literal.parentOfType()?.name
+ val fieldTypeClass = (element.type as? PsiClassType)?.resolve()
+ val expectedType = propertyKey?.let { FabricConstants.ENTRYPOINT_BY_TYPE[it] }
+ if (propertyKey != null && fieldTypeClass != null && expectedType != null &&
+ !isEntrypointOfCorrectType(fieldTypeClass, propertyKey)
+ ) {
+ holder.registerProblem(
+ literal,
+ "'$propertyKey' entrypoints must be of type $expectedType",
+ ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
+ reference.rangeInElement,
+ )
+ }
+ }
+ }
+ }
+ }
+
+ private fun isEntrypointOfCorrectType(element: PsiClass, type: String): Boolean {
+ val entrypointClass = FabricConstants.ENTRYPOINT_BY_TYPE[type]
+ ?: return false
+ val clazz = JavaPsiFacade.getInstance(element.project).findClass(entrypointClass, element.resolveScope)
+ return clazz != null && element.isInheritor(clazz, true)
+ }
+ }
+}
Index: src/main/kotlin/platform/fabric/util/FabricConstants.kt
===================================================================
--- src/main/kotlin/platform/fabric/util/FabricConstants.kt (revision 1b919feac3c261175857ad034684e4121babc6cc)
+++ src/main/kotlin/platform/fabric/util/FabricConstants.kt (revision 638c35fc692d3ebd304aaf9fe4892104e05caf19)
@@ -26,7 +26,17 @@
const val MOD_INITIALIZER = "net.fabricmc.api.ModInitializer"
const val CLIENT_MOD_INITIALIZER = "net.fabricmc.api.ClientModInitializer"
+ const val SERVER_MOD_INITIALIZER = "net.fabricmc.api.DedicatedServerModInitializer"
+ const val PRE_LAUNCH_ENTRYPOINT = "net.fabricmc.loader.api.entrypoint.PreLaunchEntryPoint"
const val ENVIRONMENT_ANNOTATION = "net.fabricmc.api.Environment"
const val ENV_TYPE = "net.fabricmc.api.EnvType"
const val ENVIRONMENT_INTERFACE_ANNOTATION = "net.fabricmc.api.EnvironmentInterface"
+
+ val ENTRYPOINTS = setOf(MOD_INITIALIZER, CLIENT_MOD_INITIALIZER, SERVER_MOD_INITIALIZER, PRE_LAUNCH_ENTRYPOINT)
+ val ENTRYPOINT_BY_TYPE = mapOf(
+ "main" to MOD_INITIALIZER,
+ "client" to CLIENT_MOD_INITIALIZER,
+ "server" to SERVER_MOD_INITIALIZER,
+ "preLaunch" to PRE_LAUNCH_ENTRYPOINT
+ )
}
Index: src/main/resources/META-INF/plugin.xml
===================================================================
--- src/main/resources/META-INF/plugin.xml (revision 1b919feac3c261175857ad034684e4121babc6cc)
+++ src/main/resources/META-INF/plugin.xml (revision 638c35fc692d3ebd304aaf9fe4892104e05caf19)
@@ -625,6 +625,14 @@
level="ERROR"
hasStaticDescription="true"
implementationClass="com.demonwav.mcdev.platform.fabric.inspection.UnresolvedReferenceInspection"/>
+