User: joe Date: 21 Dec 25 21:49 Revision: e3f97631d28c61e1ef01a3ede7e2674e05727776 Summary: Add inspection for parameter name mismatch to target class in unobf versions TeamCity URL: http://ci.mcdev.io:80/viewModification.html?tab=vcsModificationFiles&modId=10366&personal=false Index: src/main/kotlin/platform/mixin/handlers/InjectorAnnotationHandler.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/InjectorAnnotationHandler.kt (revision bde4418ffe213b400b668c8877c68183b9dae543) +++ src/main/kotlin/platform/mixin/handlers/InjectorAnnotationHandler.kt (revision e3f97631d28c61e1ef01a3ede7e2674e05727776) @@ -201,24 +201,24 @@ val localVariables = targetMethod.localVariables?.sortedBy { it.index } return targetMethod.getGenericParameterTypes(clazz, project).asSequence().withIndex() .map { (index, type) -> - val name = localVariables + val knownName = localVariables ?.getOrNull(index + numLocalsToDrop) ?.name ?.toJavaIdentifier() - ?: "par${index + 1}" - type to name + val name = knownName ?: "par${index + 1}" + sanitizedParameter(type, name, knownName != null) } - .map { (type, name) -> sanitizedParameter(type, name) } .toList() } @JvmStatic - protected fun sanitizedParameter(type: PsiType, name: String?): Parameter { + @JvmOverloads + protected fun sanitizedParameter(type: PsiType, name: String?, knownName: Boolean = false): Parameter { // Parameters should not use ellipsis because others like CallbackInfo may follow return if (type is PsiEllipsisType) { - Parameter(name?.toJavaIdentifier(), type.toArrayType()) + Parameter(name?.toJavaIdentifier(), type.toArrayType(), knownName) } else { - Parameter(name?.toJavaIdentifier(), type) + Parameter(name?.toJavaIdentifier(), type, knownName) } } } Index: src/main/kotlin/platform/mixin/handlers/ModifyVariableHandler.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/ModifyVariableHandler.kt (revision bde4418ffe213b400b668c8877c68183b9dae543) +++ src/main/kotlin/platform/mixin/handlers/ModifyVariableHandler.kt (revision e3f97631d28c61e1ef01a3ede7e2674e05727776) @@ -27,6 +27,7 @@ import com.demonwav.mcdev.platform.mixin.inspection.injector.ParameterGroup import com.demonwav.mcdev.platform.mixin.util.LocalInfo import com.demonwav.mcdev.platform.mixin.util.toPsiType +import com.demonwav.mcdev.util.Parameter import com.demonwav.mcdev.util.constantStringValue import com.demonwav.mcdev.util.findContainingMethod import com.demonwav.mcdev.util.findModule @@ -61,30 +62,27 @@ val localType = method.parameterList.getParameter(0)?.type val info = LocalInfo.fromAnnotation(localType, annotation) - val possibleTypes = mutableSetOf() + val elementFactory = JavaPsiFacade.getElementFactory(annotation.project) + val seenParams = mutableSetOf() + val result = mutableListOf() for (insn in targets) { val matchedLocals = info.matchLocals( module, targetClass, targetMethod, insn.insn, CollectVisitor.Mode.COMPLETION, matchType = false ) ?: continue for (local in matchedLocals) { - possibleTypes += local.desc!! - } - } - - val result = mutableListOf() - - val elementFactory = JavaPsiFacade.getElementFactory(annotation.project) - for (type in possibleTypes) { - val psiType = Type.getType(type).toPsiType(elementFactory) + if (seenParams.add(local.desc + local.name)) { + val localType = Type.getType(local.desc).toPsiType(elementFactory) - result += MethodSignature( - listOf( + result += MethodSignature( + listOf( - ParameterGroup(listOf(sanitizedParameter(psiType, "value"))), + ParameterGroup(listOf(sanitizedParameter(localType, local.name, local.isNamed))), - targetParamsGroup, - ), + targetParamsGroup, + ), - psiType, + localType, - ) - } + ) + } + } + } return result } Index: src/main/kotlin/platform/mixin/handlers/RedirectInjectorHandler.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/RedirectInjectorHandler.kt (revision bde4418ffe213b400b668c8877c68183b9dae543) +++ src/main/kotlin/platform/mixin/handlers/RedirectInjectorHandler.kt (revision e3f97631d28c61e1ef01a3ede7e2674e05727776) @@ -255,11 +255,13 @@ .mapTo(parameters) { (index, type) -> val i = if (firstMatch.opcode == Opcodes.INVOKESTATIC) index else index + 1 val name = sourceClassAndMethod.method.localVariables?.getOrNull(i)?.name?.toJavaIdentifier() - sanitizedParameter(type, name) + sanitizedParameter(type, name, name != null) } } else { - Type.getArgumentTypes(firstMatch.desc).mapTo(parameters) { - sanitizedParameter(it.toPsiType(elementFactory), null) + Type.getArgumentTypes(firstMatch.desc).withIndex().mapTo(parameters) { (index, type) -> + val i = if (firstMatch.opcode == Opcodes.INVOKESTATIC) index else index + 1 + val name = sourceClassAndMethod?.method?.localVariables?.getOrNull(i)?.name?.toJavaIdentifier() + sanitizedParameter(type.toPsiType(elementFactory), name, name != null) } } Index: src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt (revision bde4418ffe213b400b668c8877c68183b9dae543) +++ src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt (revision e3f97631d28c61e1ef01a3ede7e2674e05727776) @@ -319,11 +319,13 @@ genericParams.withIndex().mapTo(parameters) { (index, type) -> val i = if (insn.opcode == Opcodes.INVOKESTATIC) index else index + 1 val name = sourceClassAndMethod.method.localVariables?.getOrNull(i)?.name?.toJavaIdentifier() - sanitizedParameter(type, name) + sanitizedParameter(type, name, name != null) } } else { - Type.getArgumentTypes(insn.desc).mapTo(parameters) { - sanitizedParameter(it.toPsiType(elementFactory), null) + Type.getArgumentTypes(insn.desc).withIndex().mapTo(parameters) { (index, type) -> + val i = if (insn.opcode == Opcodes.INVOKESTATIC) index else index + 1 + val name = sourceClassAndMethod?.method?.localVariables?.getOrNull(i)?.name?.toJavaIdentifier() + sanitizedParameter(type.toPsiType(elementFactory), name, name != null) } } parameters Index: src/main/kotlin/platform/mixin/inspection/injector/InvalidInjectorMethodSignatureInspection.kt =================================================================== --- src/main/kotlin/platform/mixin/inspection/injector/InvalidInjectorMethodSignatureInspection.kt (revision bde4418ffe213b400b668c8877c68183b9dae543) +++ src/main/kotlin/platform/mixin/inspection/injector/InvalidInjectorMethodSignatureInspection.kt (revision e3f97631d28c61e1ef01a3ede7e2674e05727776) @@ -262,8 +262,10 @@ } return isAssignable(methodReturnType, expectedReturnType) } + } - private fun checkParameters( + companion object { + fun checkParameters( parameterList: PsiParameterList, expected: List, allowCoerce: Boolean, @@ -310,7 +312,7 @@ } } - private enum class CheckResult { + enum class CheckResult { OK, WARNING, ERROR } Index: src/main/kotlin/platform/mixin/inspection/injector/MixinParameterNameInspection.kt =================================================================== --- src/main/kotlin/platform/mixin/inspection/injector/MixinParameterNameInspection.kt (revision e3f97631d28c61e1ef01a3ede7e2674e05727776) +++ src/main/kotlin/platform/mixin/inspection/injector/MixinParameterNameInspection.kt (revision e3f97631d28c61e1ef01a3ede7e2674e05727776) @@ -0,0 +1,269 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2025 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.mixin.inspection.injector + +import com.demonwav.mcdev.platform.mixin.handlers.InjectorAnnotationHandler +import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler +import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.CollectVisitor +import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection +import com.demonwav.mcdev.platform.mixin.util.LocalInfo +import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember +import com.demonwav.mcdev.platform.mixin.util.MixinConstants +import com.demonwav.mcdev.platform.mixin.util.MixinTargetMember +import com.demonwav.mcdev.platform.mixin.util.hasNamedLocalVariables +import com.demonwav.mcdev.platform.mixin.util.isMixinExtrasSugar +import com.demonwav.mcdev.util.findModule +import com.intellij.codeInsight.intention.LowPriorityAction +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.codeInspection.options.OptPane +import com.intellij.modcommand.ModCommand +import com.intellij.modcommand.ModCommandQuickFix +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project +import com.intellij.psi.JavaElementVisitor +import com.intellij.psi.PsiAnnotation +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiParameter +import com.siyeh.ig.fixes.RenameFix + +class MixinParameterNameInspection : MixinInspection() { + @JvmField + var reportForMainSignature = true + @JvmField + var reportForLocal = true + + override fun getStaticDescription() = "Reports when a parameter name in a Mixin does not match the target class" + + override fun getOptionsPane() = OptPane.pane( + OptPane.checkbox("reportForMainSignature", "Report for main signature"), + OptPane.checkbox("reportForLocal", "Report for @Local parameters") + ) + + override fun buildVisitor(holder: ProblemsHolder) = object : JavaElementVisitor() { + override fun visitMethod(method: PsiMethod) { + val module = method.findModule() ?: return + val parameters = method.parameterList.parameters + val parametersWithoutSugar = parameters.dropLastWhile { it.isMixinExtrasSugar }.toTypedArray() + + val validNames = arrayOfNulls>(parameters.size) + + for (annotation in method.annotations) { + val handler = MixinAnnotationHandler.forMixinAnnotation(annotation, holder.project) + as? InjectorAnnotationHandler ?: continue + for (target in MixinAnnotationHandler.resolveTarget(annotation)) { + if (!collectValidNames(validNames, module, method, parameters, parametersWithoutSugar, annotation, handler, target)) { + return + } + } + } + + for (index in validNames.indices) { + val names = validNames[index] ?: continue + if (names.isEmpty()) { + continue + } + + if (parameters[index].name !in names) { + val fixes = mutableListOf() + + for (name in names) { + fixes += RenameFix(name) + } + + fixes += if (index < parametersWithoutSugar.size) { + DontReportForMainSignatureFix() + } else { + DontReportForLocalFix() + } + + holder.registerProblem( + parameters[index].nameIdentifier ?: parameters[index], + "Parameter name does not match name ${names.joinToString(" / ") { "'$it'" } } in target class", + *fixes.toTypedArray() + ) + } + } + } + } + + private fun collectValidNames( + validNames: Array?>, + module: Module, method: PsiMethod, + parameters: Array, + parametersWithoutSugar: Array, + annotation: PsiAnnotation, + handler: InjectorAnnotationHandler, + target: MixinTargetMember + ): Boolean { + if (target !is MethodTargetMember) { + return true + } + + if (!method.hasNamedLocalVariables(target.classAndMethod.clazz.name.replace('/', '.'))) { + return true + } + + val validNamesForThisTarget = arrayOfNulls>(parameters.size) + + if (reportForMainSignature) { + val expectedSignatures = + handler.expectedMethodSignature(annotation, target.classAndMethod.clazz, target.classAndMethod.method) + ?: return false + var anyValidSignatures = false + + for ((expectedParams, _) in expectedSignatures) { + if (InvalidInjectorMethodSignatureInspection.checkParameters( + method.parameterList, + expectedParams, + handler.allowCoerce + ) != InvalidInjectorMethodSignatureInspection.CheckResult.OK + ) { + continue + } + + anyValidSignatures = true + + checkExpectedSignatureForKnownNames( + validNamesForThisTarget, + handler, + expectedParams, + parametersWithoutSugar + ) + } + + if (!anyValidSignatures) { + return false + } + } + + if (reportForLocal) { + for (pos in parametersWithoutSugar.size until parameters.size) { + if (!checkSugarForKnownNames( + validNamesForThisTarget, + module, + handler, + annotation, + target, + parameters[pos], + pos + ) + ) { + return false + } + } + } + + for (index in validNamesForThisTarget.indices) { + val names = validNamesForThisTarget[index] ?: continue + if (validNames[index] == null) { + validNames[index] = names + } else { + validNames[index]!!.retainAll(names) + } + } + + return true + } + + private fun checkExpectedSignatureForKnownNames( + validNamesForThisTarget: Array?>, + handler: InjectorAnnotationHandler, + expectedParams: List, + parametersWithoutSugar: Array + ) { + var pos = 0 + for (group in expectedParams) { + if (!group.match(parametersWithoutSugar, pos, handler.allowCoerce)) { + continue + } + for (expectedParam in group.parameters) { + if (expectedParam.knownName && expectedParam.name != null) { + if (validNamesForThisTarget[pos] == null) { + validNamesForThisTarget[pos] = mutableSetOf(expectedParam.name) + } else { + validNamesForThisTarget[pos]!!.add(expectedParam.name) + } + } + pos++ + if (pos >= parametersWithoutSugar.size) { + return + } + } + } + } + + private fun checkSugarForKnownNames( + validNamesForThisTarget: Array?>, + module: Module, + handler: InjectorAnnotationHandler, + annotation: PsiAnnotation, + target: MethodTargetMember, + parameter: PsiParameter, + pos: Int + ): Boolean { + val localAnnotation = parameter.getAnnotation(MixinConstants.MixinExtras.LOCAL) ?: return true + val localInfo = LocalInfo.fromAnnotation(parameter.type, localAnnotation) + + for (insn in handler.resolveInstructions( + annotation, + target.classAndMethod.clazz, + target.classAndMethod.method + )) { + val matchedLocal = localInfo.matchLocals( + module, + target.classAndMethod.clazz, + target.classAndMethod.method, + insn.insn, CollectVisitor.Mode.RESOLUTION + )?.singleOrNull() ?: return false + if (matchedLocal.isNamed) { + if (validNamesForThisTarget[pos] == null) { + validNamesForThisTarget[pos] = mutableSetOf(matchedLocal.name) + } else { + validNamesForThisTarget[pos]!!.add(matchedLocal.name) + } + } + } + + return true + } + + private inner class DontReportForMainSignatureFix : ModCommandQuickFix(), LowPriorityAction { + override fun getFamilyName() = "Don't report for main signature" + + override fun perform(project: Project, descriptor: ProblemDescriptor): ModCommand { + return ModCommand.updateInspectionOption(descriptor.psiElement, this@MixinParameterNameInspection) { + it.reportForMainSignature = false + } + } + } + + private inner class DontReportForLocalFix : ModCommandQuickFix(), LowPriorityAction { + override fun getFamilyName() = "Don't report for @Local parameters" + + override fun perform(project: Project, descriptor: ProblemDescriptor): ModCommand { + return ModCommand.updateInspectionOption(descriptor.psiElement, this@MixinParameterNameInspection) { + it.reportForLocal = false + } + } + } +} Index: src/main/kotlin/util/Parameter.kt =================================================================== --- src/main/kotlin/util/Parameter.kt (revision bde4418ffe213b400b668c8877c68183b9dae543) +++ src/main/kotlin/util/Parameter.kt (revision e3f97631d28c61e1ef01a3ede7e2674e05727776) @@ -20,12 +20,9 @@ package com.demonwav.mcdev.util -import com.intellij.psi.PsiParameter import com.intellij.psi.PsiType -data class Parameter(val name: String?, val type: PsiType) { - constructor(parameter: PsiParameter) : this(parameter.name, parameter.type) - +data class Parameter @JvmOverloads constructor(val name: String?, val type: PsiType, val knownName: Boolean = false) { init { assert(name?.isJavaSoftKeyword() != true) } Index: src/main/resources/META-INF/plugin.xml =================================================================== --- src/main/resources/META-INF/plugin.xml (revision bde4418ffe213b400b668c8877c68183b9dae543) +++ src/main/resources/META-INF/plugin.xml (revision e3f97631d28c61e1ef01a3ede7e2674e05727776) @@ -1070,6 +1070,14 @@ level="WARNING" hasStaticDescription="true" implementationClass="com.demonwav.mcdev.platform.mixin.inspection.injector.MixinCancellableInspection"/> +