2020
2121package com.demonwav.mcdev.platform.mcp.at
2222
23- import com.demonwav.mcdev.facet.MinecraftFacet
24- import com.demonwav.mcdev.platform.mcp.McpModuleType
2523import com.demonwav.mcdev.platform.mcp.at.gen.psi.AtEntry
26- import com.demonwav.mcdev.platform.mcp.at.gen.psi.AtFieldName
27- import com.demonwav.mcdev.platform.mcp.at.gen.psi.AtFunction
24+ import com.demonwav.mcdev.platform.mcp.at.gen.psi.AtTypes
25+ import com.demonwav.mcdev.util.excludeFileTypes
2826import com.intellij.codeInspection.LocalInspectionTool
29- import com.intellij.codeInspection.ProblemHighlightType
27+ import com.intellij.codeInspection.LocalQuickFixOnPsiElement
3028import com.intellij.codeInspection.ProblemsHolder
31- import com.intellij.openapi.module.ModuleUtilCore
29+ import com.intellij.codeInspection.util.IntentionFamilyName
30+ import com.intellij.codeInspection.util.IntentionName
31+ import com.intellij.openapi.project.Project
32+ import com.intellij.psi.PsiClass
3233import com.intellij.psi.PsiElement
3334import com.intellij.psi.PsiElementVisitor
35+ import com.intellij.psi.PsiFile
36+ import com.intellij.psi.PsiMethod
3437import com.intellij.psi.search.GlobalSearchScope
38+ import com.intellij.psi.search.searches.OverridingMethodsSearch
3539import com.intellij.psi.search.searches.ReferencesSearch
40+ import com.intellij.psi.util.elementType
41+ import com.intellij.psi.util.siblings
3642
3743class AtUsageInspection : LocalInspectionTool () {
3844
3945 override fun getStaticDescription (): String {
40- return " The declared access transformer is never used"
46+ return " Reports unused Access Transformer entries"
47+ }
48+
49+ override fun isSuppressedFor (element : PsiElement ): Boolean {
50+ return super .isSuppressedFor(element)
4151 }
4252
4353 override fun buildVisitor (holder : ProblemsHolder , isOnTheFly : Boolean ): PsiElementVisitor {
@@ -47,33 +57,102 @@ class AtUsageInspection : LocalInspectionTool() {
4757 return
4858 }
4959
50- val module = ModuleUtilCore .findModuleForPsiElement(element) ? : return
51- val instance = MinecraftFacet .getInstance(module) ? : return
52- val mcpModule = instance.getModuleOfType(McpModuleType ) ? : return
53- val srgMap = mcpModule.mappingsManager?.mappingsNow ? : return
54-
55- val member = element.function ? : element.fieldName ? : return
56- val reference = AtMemberReference .get(element, member) ? : return
57-
58- val psi = when (member) {
59- is AtFunction ->
60- reference.resolveMember(element.project) ? : srgMap.tryGetMappedMethod(reference)?.resolveMember(
61- element.project,
62- ) ? : return
63- is AtFieldName ->
64- reference.resolveMember(element.project)
65- ? : srgMap.tryGetMappedField(reference)?.resolveMember(element.project) ? : return
66- else ->
60+ val function = element.function
61+ if (function != null ) {
62+ checkElement(element, function)
63+ return
64+ }
65+
66+ val fieldName = element.fieldName
67+ if (fieldName != null ) {
68+ checkElement(element, fieldName)
69+ return
70+ }
71+
72+ // Only check class names if it is the target of the entry
73+ checkElement(element, element.className)
74+ }
75+
76+ private fun checkElement (entry : AtEntry , element : PsiElement ) {
77+ val referenced = element.reference?.resolve() ? : return
78+ val scope = GlobalSearchScope .projectScope(element.project)
79+ .excludeFileTypes(element.project, AtFileType )
80+ val query = ReferencesSearch .search(referenced, scope, true )
81+ if (query.any()) {
82+ return
83+ }
84+
85+ if (referenced is PsiMethod ) {
86+ // The regular references search doesn't cover overridden methods
87+ val overridingQuery = OverridingMethodsSearch .search(referenced, scope, true )
88+ if (overridingQuery.any()) {
6789 return
90+ }
91+
92+ // Also ignore if other entries cover super methods
93+ val superMethods = referenced.findSuperMethods()
94+ for (childEntry in entry.containingFile.children) {
95+ if (childEntry !is AtEntry || childEntry == entry) {
96+ continue
97+ }
98+
99+ val function = childEntry.function ? : continue
100+ val otherResolved = function.reference?.resolve()
101+ if (superMethods.contains(otherResolved)) {
102+ return
103+ }
104+ }
105+ }
106+
107+ if (referenced is PsiClass ) {
108+ // Do not report classes whose members are used in the mod
109+ for (field in referenced.fields) {
110+ if (ReferencesSearch .search(field, scope, true ).any()) {
111+ return
112+ }
113+ }
114+ for (method in referenced.methods) {
115+ if (ReferencesSearch .search(method, scope, true ).any()) {
116+ return
117+ }
118+ }
119+ for (innerClass in referenced.innerClasses) {
120+ if (ReferencesSearch .search(innerClass, scope, true ).any()) {
121+ return
122+ }
123+ }
68124 }
69125
70- val query = ReferencesSearch .search(psi, GlobalSearchScope .projectScope(element.project))
71- query.findFirst()
72- ? : holder.registerProblem(
73- element,
74- " Access Transformer entry is never used" ,
75- ProblemHighlightType .LIKE_UNUSED_SYMBOL ,
76- )
126+ val fix = RemoveAtEntryFix .forWholeLine(entry)
127+ holder.registerProblem(entry, " Access Transformer entry is never used" , fix)
128+ }
129+ }
130+ }
131+
132+ private class RemoveAtEntryFix (startElement : PsiElement , endElement : PsiElement ) :
133+ LocalQuickFixOnPsiElement (startElement, endElement) {
134+
135+ override fun getFamilyName (): @IntentionFamilyName String = " Remove entry"
136+
137+ override fun getText (): @IntentionName String = familyName
138+
139+ override fun invoke (
140+ project : Project ,
141+ file : PsiFile ,
142+ startElement : PsiElement ,
143+ endElement : PsiElement
144+ ) {
145+ startElement.parent.deleteChildRange(startElement, endElement)
146+ }
147+
148+ companion object {
149+
150+ fun forWholeLine (entry : AtEntry ): RemoveAtEntryFix {
151+ val start = entry.siblings(forward = false , withSelf = false )
152+ .firstOrNull { it.elementType == AtTypes .CRLF }?.nextSibling
153+ val end = entry.siblings(forward = true , withSelf = true )
154+ .firstOrNull { it.elementType == AtTypes .CRLF }
155+ return RemoveAtEntryFix (start ? : entry, end ? : entry)
77156 }
78157 }
79158 }
0 commit comments