()
+ for (file in FileTypeIndex.getFiles(CtFileType, GlobalSearchScope.projectScope(project))) {
+ val ctFile = psiManager.findFile(file) as? CtFile ?: continue
+ for (entry in PsiTreeUtil.findChildrenOfType(ctFile, CtItfEntry::class.java)) {
+ if (!entry.isTransitive) {
+ continue
+ }
+ val interfaceName = entry.interfaceName ?: continue
+ result += interfaceName.replace('/', '.')
+ }
+ }
+ return result
+ }
+
+ /**
+ * Returns whether the given interface is available on the classes it is injected into from dependent mods, either
+ * because it is directly injected with `transitive-inject-interface`, or because it is a superinterface of such an
+ * interface.
+ */
+ fun isTransitivelyInjected(iface: PsiClass): Boolean {
+ val injected = getTransitivelyInjectedInterfaces(iface.project)
+ if (injected.isEmpty()) {
+ return false
+ }
+
+ if (ClassUtil.getJVMClassName(iface) in injected) {
+ return true
+ }
+
+ return injected.any { name ->
+ findQualifiedClass(iface.project, name)?.isInheritor(iface, true) == true
+ }
+ }
+}
Index: src/main/kotlin/platform/minecraft/ct/psi/mixins/CtEntryMixin.kt
===================================================================
--- src/main/kotlin/platform/minecraft/ct/psi/mixins/CtEntryMixin.kt (revision 9b2e99f7a15e78a9b0f89be0417ea2a124b2224b)
+++ src/main/kotlin/platform/minecraft/ct/psi/mixins/CtEntryMixin.kt (revision 47c71a2b70e78f5bb104fb31869c125a422b7533)
@@ -22,4 +22,7 @@
import com.demonwav.mcdev.platform.minecraft.ct.psi.CtElement
-interface CtEntryMixin : CtElement
+interface CtEntryMixin : CtElement {
+
+ val isTransitive: Boolean
+}
Index: src/main/kotlin/platform/minecraft/ct/psi/mixins/CtItfEntryMixin.kt
===================================================================
--- src/main/kotlin/platform/minecraft/ct/psi/mixins/CtItfEntryMixin.kt (revision 9b2e99f7a15e78a9b0f89be0417ea2a124b2224b)
+++ src/main/kotlin/platform/minecraft/ct/psi/mixins/CtItfEntryMixin.kt (revision 47c71a2b70e78f5bb104fb31869c125a422b7533)
@@ -20,4 +20,10 @@
package com.demonwav.mcdev.platform.minecraft.ct.psi.mixins
-interface CtItfEntryMixin : CtEntryMixin
+import com.demonwav.mcdev.platform.minecraft.ct.gen.psi.CtSignature
+
+interface CtItfEntryMixin : CtEntryMixin {
+
+ val interfaceSignature: CtSignature?
+ val interfaceName: String?
+}
Index: src/main/kotlin/platform/minecraft/ct/psi/mixins/CtSignatureMixin.kt
===================================================================
--- src/main/kotlin/platform/minecraft/ct/psi/mixins/CtSignatureMixin.kt (revision 47c71a2b70e78f5bb104fb31869c125a422b7533)
+++ src/main/kotlin/platform/minecraft/ct/psi/mixins/CtSignatureMixin.kt (revision 47c71a2b70e78f5bb104fb31869c125a422b7533)
@@ -0,0 +1,30 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2026 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.minecraft.ct.psi.mixins
+
+import com.demonwav.mcdev.platform.minecraft.ct.psi.CtElement
+import com.intellij.psi.PsiElement
+
+interface CtSignatureMixin : CtElement {
+
+ val classNameElement: PsiElement
+ val className: String
+}
Index: src/main/kotlin/platform/minecraft/ct/psi/mixins/impl/CtEntryImplMixin.kt
===================================================================
--- src/main/kotlin/platform/minecraft/ct/psi/mixins/impl/CtEntryImplMixin.kt (revision 9b2e99f7a15e78a9b0f89be0417ea2a124b2224b)
+++ src/main/kotlin/platform/minecraft/ct/psi/mixins/impl/CtEntryImplMixin.kt (revision 47c71a2b70e78f5bb104fb31869c125a422b7533)
@@ -24,4 +24,8 @@
import com.intellij.extapi.psi.ASTWrapperPsiElement
import com.intellij.lang.ASTNode
-abstract class CtEntryImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), CtEntryMixin
+abstract class CtEntryImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), CtEntryMixin {
+
+ override val isTransitive: Boolean
+ get() = firstChild?.text?.startsWith("transitive-") == true
+}
Index: src/main/kotlin/platform/minecraft/ct/psi/mixins/impl/CtItfEntryImplMixin.kt
===================================================================
--- src/main/kotlin/platform/minecraft/ct/psi/mixins/impl/CtItfEntryImplMixin.kt (revision 9b2e99f7a15e78a9b0f89be0417ea2a124b2224b)
+++ src/main/kotlin/platform/minecraft/ct/psi/mixins/impl/CtItfEntryImplMixin.kt (revision 47c71a2b70e78f5bb104fb31869c125a422b7533)
@@ -23,4 +23,8 @@
import com.demonwav.mcdev.platform.minecraft.ct.psi.mixins.CtItfEntryMixin
import com.intellij.lang.ASTNode
-abstract class CtItfEntryImplMixin(node: ASTNode): CtEntryImplMixin(node), CtItfEntryMixin
+abstract class CtItfEntryImplMixin(node: ASTNode): CtEntryImplMixin(node), CtItfEntryMixin {
+
+ override val interfaceName: String?
+ get() = interfaceSignature?.className
+}
Index: src/main/kotlin/platform/minecraft/ct/psi/mixins/impl/CtSignatureImplMixin.kt
===================================================================
--- src/main/kotlin/platform/minecraft/ct/psi/mixins/impl/CtSignatureImplMixin.kt (revision 47c71a2b70e78f5bb104fb31869c125a422b7533)
+++ src/main/kotlin/platform/minecraft/ct/psi/mixins/impl/CtSignatureImplMixin.kt (revision 47c71a2b70e78f5bb104fb31869c125a422b7533)
@@ -0,0 +1,31 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2026 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.minecraft.ct.psi.mixins.impl
+
+import com.demonwav.mcdev.platform.minecraft.ct.psi.mixins.CtSignatureMixin
+import com.intellij.extapi.psi.ASTWrapperPsiElement
+import com.intellij.lang.ASTNode
+
+abstract class CtSignatureImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), CtSignatureMixin {
+
+ override val className: String
+ get() = classNameElement.text
+}
Index: src/main/kotlin/platform/mixin/inspection/addedMembers/AddedMembersNameFormatInspection.kt
===================================================================
--- src/main/kotlin/platform/mixin/inspection/addedMembers/AddedMembersNameFormatInspection.kt (revision 9b2e99f7a15e78a9b0f89be0417ea2a124b2224b)
+++ src/main/kotlin/platform/mixin/inspection/addedMembers/AddedMembersNameFormatInspection.kt (revision 47c71a2b70e78f5bb104fb31869c125a422b7533)
@@ -22,6 +22,7 @@
import com.demonwav.mcdev.facet.MinecraftFacet
import com.demonwav.mcdev.platform.fabric.FabricModuleType
+import com.demonwav.mcdev.platform.minecraft.ct.CtInterfaceInjections
import com.demonwav.mcdev.util.RegexConverter
import com.demonwav.mcdev.util.decapitalize
import com.demonwav.mcdev.util.doBindItem
@@ -41,7 +42,10 @@
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
+import com.intellij.openapi.roots.ProjectFileIndex
import com.intellij.openapi.util.text.StringUtil
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiClassType
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiEnumConstant
import com.intellij.psi.PsiField
@@ -50,11 +54,14 @@
import com.intellij.psi.PsiNameIdentifierOwner
import com.intellij.psi.PsiNamedElement
import com.intellij.psi.util.PsiTreeUtil
+import com.intellij.psi.util.PsiUtil
+import com.intellij.psi.util.TypeConversionUtil
import com.intellij.refactoring.rename.RenameProcessor
import com.intellij.ui.EnumComboBoxModel
import com.intellij.ui.components.JBLabel
import com.intellij.ui.dsl.builder.COLUMNS_SHORT
import com.intellij.ui.dsl.builder.RowLayout
+import com.intellij.ui.dsl.builder.bindSelected
import com.intellij.ui.dsl.builder.columns
import com.intellij.ui.dsl.builder.panel
import com.intellij.util.xmlb.annotations.Attribute
@@ -79,6 +86,17 @@
@JvmField
var reportInheritedMethods = ReportMode.ALWAYS
+ @JvmField
+ var ignoreInheritedWithProjectTypeInDescriptor = true
+ @JvmField
+ var ignoreInheritedFromTransitivelyInjectedInterfaces = true
+ @JvmField
+ var ignoreInheritedFromApiInterfaces = true
+
+ @Attribute(converter = RegexConverter::class)
+ @JvmField
+ var apiPackageFormat = DEFAULT_API_PACKAGE_FORMAT.toRegex()
+
override fun visitAddedField(holder: ProblemsHolder, field: PsiField) {
if (field !is PsiEnumConstant && reportFields.shouldReport(field.findModule())) {
visitAdded(holder, field)
@@ -99,17 +117,48 @@
return false
}
- val superMethods = method.findDeepestSuperMethods()
- val isWritableInterfaceMethod = superMethods.any {
- val clazz = it.findContainingClass() ?: return@any false
- clazz.isInterface && clazz.containingFile?.isWritable == true
+ val writableInterfaces = method.findDeepestSuperMethods().mapNotNull { superMethod ->
+ superMethod.findContainingClass()?.takeIf { it.isInterface && it.containingFile?.isWritable == true }
}
- return isWritableInterfaceMethod
+ if (writableInterfaces.isEmpty()) {
+ return false
+ }
+
+ if (ignoreInheritedWithProjectTypeInDescriptor && hasProjectTypeInDescriptor(method)) {
+ return false
+ }
+
+ return writableInterfaces.any { !isIgnoredInterface(it) }
} else {
return reportMethods.shouldReport(module)
}
}
+ private fun hasProjectTypeInDescriptor(method: PsiMethod): Boolean {
+ val fileIndex = ProjectFileIndex.getInstance(method.project)
+ val types = method.parameterList.parameters.map { it.type } + listOfNotNull(method.returnType)
+ return types.any { type ->
+ val erased = TypeConversionUtil.erasure(type).deepComponentType as? PsiClassType ?: return@any false
+ val file = erased.resolve()?.containingFile?.virtualFile ?: return@any false
+ fileIndex.isInSourceContent(file)
+ }
+ }
+
+ private fun isIgnoredInterface(iface: PsiClass): Boolean {
+ if (ignoreInheritedFromApiInterfaces) {
+ val packageName = PsiUtil.getPackageName(iface)
+ if (packageName != null && apiPackageFormat.matches(packageName)) {
+ return true
+ }
+ }
+
+ if (ignoreInheritedFromTransitivelyInjectedInterfaces && CtInterfaceInjections.isTransitivelyInjected(iface)) {
+ return true
+ }
+
+ return false
+ }
+
private fun visitAdded(holder: ProblemsHolder, added: PsiNameIdentifierOwner) {
val name = added.name ?: return
if (validNameFormat.matches(name)) {
@@ -187,9 +236,34 @@
.applyToComponent { toolTipText = "Reports methods that are inherited from duck interfaces" }
comboBox(EnumComboBoxModel(ReportMode::class.java)).doBindItem(::reportInheritedMethods)
}
+ indent {
+ row {
+ checkBox("Ignore if a parameter or return type is from the current project")
+ .bindSelected(::ignoreInheritedWithProjectTypeInDescriptor)
- }
+ }
+ row {
+ checkBox("Ignore if the interface is transitively injected by a class tweaker")
+ .bindSelected(::ignoreInheritedFromTransitivelyInjectedInterfaces)
- }
+ }
+ row {
+ checkBox("Ignore if the interface is in an API package:")
+ .bindSelected(::ignoreInheritedFromApiInterfaces)
+ textField()
+ .doBindText(
+ { apiPackageFormat.pattern },
+ { apiPackageFormat = it.toRegexOrDefault(DEFAULT_API_PACKAGE_FORMAT) }
+ )
+ .columns(COLUMNS_SHORT)
+ .regexValidator()
+ }
+ }
+ }
+ }
+ companion object {
+ private const val DEFAULT_API_PACKAGE_FORMAT = "(.+\\.)?api(\\..+)?"
+ }
+
enum class ReportMode(private val displayName: String) {
ALWAYS("Always"), ON_FABRIC("On Fabric"), NOT_ON_FABRIC("Not on Fabric"), NEVER("Never");
Index: src/main/resources/inspectionDescriptions/AddedMixinMembersNamePattern.html
===================================================================
--- src/main/resources/inspectionDescriptions/AddedMixinMembersNamePattern.html (revision 9b2e99f7a15e78a9b0f89be0417ea2a124b2224b)
+++ src/main/resources/inspectionDescriptions/AddedMixinMembersNamePattern.html (revision 47c71a2b70e78f5bb104fb31869c125a422b7533)
@@ -33,5 +33,14 @@
@Override public void doSomething() { ... } // bad
@Override public void myMod$doSomething() { ... } // good
+
+ By default, methods implemented from interfaces in the current project are not reported in the following cases, each of
+ which can be disabled:
+
+
+ - A parameter or return type of the method is a class from the current project, making a conflicting descriptor very unlikely.
+ - The interface is injected with
transitive-inject-interface in a class tweaker file, meaning it is intended to be used by other mods.
+ - The interface is in a package matching the configured API package pattern.
+