Skip to content

Commit 4c4697b

Browse files
committed
Rewrite AtUsageInspection
Removed redundant code and fixes some false positives
1 parent b4ae2c5 commit 4c4697b

File tree

5 files changed

+237
-32
lines changed

5 files changed

+237
-32
lines changed

src/main/kotlin/platform/mcp/at/AtUsageInspection.kt

Lines changed: 110 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,34 @@
2020

2121
package com.demonwav.mcdev.platform.mcp.at
2222

23-
import com.demonwav.mcdev.facet.MinecraftFacet
24-
import com.demonwav.mcdev.platform.mcp.McpModuleType
2523
import 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
2826
import com.intellij.codeInspection.LocalInspectionTool
29-
import com.intellij.codeInspection.ProblemHighlightType
27+
import com.intellij.codeInspection.LocalQuickFixOnPsiElement
3028
import 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
3233
import com.intellij.psi.PsiElement
3334
import com.intellij.psi.PsiElementVisitor
35+
import com.intellij.psi.PsiFile
36+
import com.intellij.psi.PsiMethod
3437
import com.intellij.psi.search.GlobalSearchScope
38+
import com.intellij.psi.search.searches.OverridingMethodsSearch
3539
import com.intellij.psi.search.searches.ReferencesSearch
40+
import com.intellij.psi.util.elementType
41+
import com.intellij.psi.util.siblings
3642

3743
class 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
}

src/main/kotlin/platform/mcp/at/psi/mixins/impl/AtClassNameImplMixin.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ abstract class AtClassNameImplMixin(node: ASTNode) : ASTWrapperPsiElement(node),
4141
}
4242

4343
override fun getReference(): PsiReference? {
44-
return references.firstOrNull()
44+
return references.lastOrNull()
4545
}
4646

4747
override fun getReferences(): Array<out PsiReference?> {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Minecraft Development for IntelliJ
3+
*
4+
* https://mcdev.io/
5+
*
6+
* Copyright (C) 2024 minecraft-dev
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU Lesser General Public License as published
10+
* by the Free Software Foundation, version 3.0 only.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Lesser General Public License
18+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
*/
20+
21+
package com.demonwav.mcdev.util
22+
23+
import com.intellij.openapi.fileTypes.FileType
24+
import com.intellij.openapi.project.Project
25+
import com.intellij.psi.search.GlobalSearchScope
26+
27+
fun GlobalSearchScope.excludeFileTypes(project: Project, vararg fileTypes: FileType): GlobalSearchScope =
28+
this.intersectWith(GlobalSearchScope.everythingScope(project).restrictByFileTypes(*fileTypes).not())
29+
30+
fun GlobalSearchScope.restrictByFileTypes(vararg fileTypes: FileType): GlobalSearchScope =
31+
GlobalSearchScope.getScopeRestrictedByFileTypes(this, *fileTypes)
32+
33+
fun GlobalSearchScope.not(): GlobalSearchScope = GlobalSearchScope.notScope(this)

src/main/resources/META-INF/plugin.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -996,6 +996,7 @@
996996
language="Access Transformers"
997997
enabledByDefault="true"
998998
level="WARNING"
999+
editorAttributes="NOT_USED_ELEMENT_ATTRIBUTES"
9991000
hasStaticDescription="true"
10001001
implementationClass="com.demonwav.mcdev.platform.mcp.at.AtUsageInspection"/>
10011002
<localInspection displayName="Invalid empty ItemStack comparison with ItemStack.EMPTY"
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Minecraft Development for IntelliJ
3+
*
4+
* https://mcdev.io/
5+
*
6+
* Copyright (C) 2024 minecraft-dev
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU Lesser General Public License as published
10+
* by the Free Software Foundation, version 3.0 only.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Lesser General Public License
18+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
*/
20+
21+
package com.demonwav.mcdev.platform.mcp.at
22+
23+
import com.demonwav.mcdev.facet.MinecraftFacet
24+
import com.demonwav.mcdev.framework.BaseMinecraftTest
25+
import com.demonwav.mcdev.platform.PlatformType
26+
import com.demonwav.mcdev.platform.mcp.McpModuleSettings
27+
import com.demonwav.mcdev.platform.mcp.McpModuleType
28+
import org.junit.jupiter.api.DisplayName
29+
import org.junit.jupiter.api.Test
30+
31+
@DisplayName("Access Transformer Usage Inspection Tests")
32+
class AtUsageInspectionTest : BaseMinecraftTest(PlatformType.MCP, PlatformType.NEOFORGE) {
33+
34+
@Test
35+
@DisplayName("Usage Inspection")
36+
fun usageInspection() {
37+
buildProject {
38+
java(
39+
"net/minecraft/Used.java",
40+
"""
41+
package net.minecraft;
42+
public class Used {
43+
public int usedField;
44+
public int unusedField;
45+
public void usedMethod() {}
46+
public void unusedMethod() {}
47+
}
48+
""".trimIndent(),
49+
allowAst = true
50+
)
51+
java(
52+
"net/minecraft/server/Unused.java",
53+
"""
54+
package net.minecraft.server;
55+
public class Unused {}
56+
""".trimIndent(),
57+
allowAst = true
58+
)
59+
java(
60+
"com/demonwav/mcdev/mcp/test/TestMod.java",
61+
"""
62+
package com.demonwav.mcdev.mcp.test;
63+
public class TestMod {
64+
public TestMod () {
65+
net.minecraft.Used mc = new net.minecraft.Used();
66+
int value = mc.usedField;
67+
mc.usedMethod();
68+
}
69+
}
70+
""".trimIndent(),
71+
allowAst = true
72+
)
73+
at(
74+
"test_at.cfg",
75+
"""
76+
public net.minecraft.Used
77+
public net.minecraft.Used usedField
78+
<warning descr="Access Transformer entry is never used">public net.minecraft.Used unusedField</warning>
79+
public net.minecraft.Used usedMethod()V
80+
<warning descr="Access Transformer entry is never used">public net.minecraft.Used unusedMethod()V</warning>
81+
<warning descr="Access Transformer entry is never used">public net.minecraft.server.Unused</warning>
82+
""".trimIndent())
83+
}
84+
85+
// Force 1.20.2 because we test the non-SRG member names with NeoForge
86+
MinecraftFacet.getInstance(fixture.module, McpModuleType)!!
87+
.updateSettings(McpModuleSettings.State(minecraftVersion = "1.20.2"))
88+
89+
fixture.enableInspections(AtUsageInspection::class.java)
90+
fixture.checkHighlighting()
91+
}
92+
}

0 commit comments

Comments
 (0)