Skip to content

Commit b555af3

Browse files
committed
Add AT duplicate entry inspection
1 parent 03821c6 commit b555af3

File tree

8 files changed

+199
-37
lines changed

8 files changed

+199
-37
lines changed

changelog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
- many lexing errors should now be fixed
1313
- class names and member names now have their own references, replacing the custom Goto handler
1414
- SRG names are no longer used on NeoForge 1.20.2+ and a new copy action is available for it
15+
- the usage inspection no longer incorrectly reports methods overridden in your code or entries covering super methods
16+
- suppressing inspections is now possible by adding `# Suppress:AtInspectionName` after an entry, or using the built-in suppress action
17+
- added an inspection to report unresolved references, to help find out old, superfluous entries
18+
- added an inspection to report duplicate entries in the same file
1519

1620
## [1.8.1] - 2024-08-10
1721

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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.platform.mcp.at.gen.psi.AtEntry
24+
import com.demonwav.mcdev.platform.mcp.at.gen.psi.AtVisitor
25+
import com.demonwav.mcdev.util.childrenOfType
26+
import com.intellij.codeInspection.LocalInspectionTool
27+
import com.intellij.codeInspection.ProblemsHolder
28+
import com.intellij.psi.PsiElementVisitor
29+
30+
class AtDuplicateEntryInspection : LocalInspectionTool() {
31+
32+
override fun getStaticDescription(): String? = "Reports duplicate AT entries in the same file"
33+
34+
override fun buildVisitor(
35+
holder: ProblemsHolder,
36+
isOnTheFly: Boolean
37+
): PsiElementVisitor = object : AtVisitor() {
38+
39+
override fun visitEntry(entry: AtEntry) {
40+
// Either a MemberReference or the class name text for class-level entries
41+
val entryMemberReference = entry.memberReference ?: entry.className.text
42+
val allMemberReferences = entry.containingFile.childrenOfType<AtEntry>()
43+
.map { it.memberReference ?: it.className.text }
44+
if (allMemberReferences.count { it == entryMemberReference } > 1) {
45+
holder.registerProblem(entry, "Duplicate entry", RemoveAtEntryFix.forWholeLine(entry, false))
46+
}
47+
}
48+
}
49+
}

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

Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,16 @@
2121
package com.demonwav.mcdev.platform.mcp.at
2222

2323
import com.demonwav.mcdev.platform.mcp.at.gen.psi.AtEntry
24-
import com.demonwav.mcdev.platform.mcp.at.gen.psi.AtTypes
2524
import com.demonwav.mcdev.util.excludeFileTypes
2625
import com.intellij.codeInspection.LocalInspectionTool
27-
import com.intellij.codeInspection.LocalQuickFixOnPsiElement
2826
import com.intellij.codeInspection.ProblemsHolder
29-
import com.intellij.codeInspection.util.IntentionFamilyName
30-
import com.intellij.codeInspection.util.IntentionName
31-
import com.intellij.openapi.project.Project
3227
import com.intellij.psi.PsiClass
3328
import com.intellij.psi.PsiElement
3429
import com.intellij.psi.PsiElementVisitor
35-
import com.intellij.psi.PsiFile
3630
import com.intellij.psi.PsiMethod
3731
import com.intellij.psi.search.GlobalSearchScope
3832
import com.intellij.psi.search.searches.OverridingMethodsSearch
3933
import com.intellij.psi.search.searches.ReferencesSearch
40-
import com.intellij.psi.util.elementType
41-
import com.intellij.psi.util.siblings
4234

4335
class AtUsageInspection : LocalInspectionTool() {
4436

@@ -119,37 +111,9 @@ class AtUsageInspection : LocalInspectionTool() {
119111
}
120112
}
121113

122-
val fix = RemoveAtEntryFix.forWholeLine(entry)
114+
val fix = RemoveAtEntryFix.forWholeLine(entry, true)
123115
holder.registerProblem(entry, "Access Transformer entry is never used", fix)
124116
}
125117
}
126118
}
127-
128-
private class RemoveAtEntryFix(startElement: PsiElement, endElement: PsiElement) :
129-
LocalQuickFixOnPsiElement(startElement, endElement) {
130-
131-
override fun getFamilyName(): @IntentionFamilyName String = "Remove entry"
132-
133-
override fun getText(): @IntentionName String = familyName
134-
135-
override fun invoke(
136-
project: Project,
137-
file: PsiFile,
138-
startElement: PsiElement,
139-
endElement: PsiElement
140-
) {
141-
startElement.parent.deleteChildRange(startElement, endElement)
142-
}
143-
144-
companion object {
145-
146-
fun forWholeLine(entry: AtEntry): RemoveAtEntryFix {
147-
val start = entry.siblings(forward = false, withSelf = false)
148-
.firstOrNull { it.elementType == AtTypes.CRLF }?.nextSibling
149-
val end = entry.siblings(forward = true, withSelf = true)
150-
.firstOrNull { it.elementType == AtTypes.CRLF }
151-
return RemoveAtEntryFix(start ?: entry, end ?: entry)
152-
}
153-
}
154-
}
155119
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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.platform.mcp.at.gen.psi.AtEntry
24+
import com.demonwav.mcdev.platform.mcp.at.gen.psi.AtTypes
25+
import com.intellij.codeInspection.LocalQuickFixOnPsiElement
26+
import com.intellij.codeInspection.util.IntentionFamilyName
27+
import com.intellij.codeInspection.util.IntentionName
28+
import com.intellij.openapi.project.Project
29+
import com.intellij.psi.PsiElement
30+
import com.intellij.psi.PsiFile
31+
import com.intellij.psi.util.elementType
32+
import com.intellij.psi.util.siblings
33+
34+
class RemoveAtEntryFix(startElement: PsiElement, endElement: PsiElement, val inBatchMode: Boolean) :
35+
LocalQuickFixOnPsiElement(startElement, endElement) {
36+
37+
override fun getFamilyName(): @IntentionFamilyName String = "Remove entry"
38+
39+
override fun getText(): @IntentionName String = familyName
40+
41+
override fun invoke(
42+
project: Project,
43+
file: PsiFile,
44+
startElement: PsiElement,
45+
endElement: PsiElement
46+
) {
47+
startElement.parent.deleteChildRange(startElement, endElement)
48+
}
49+
50+
override fun availableInBatchMode(): Boolean = inBatchMode
51+
52+
companion object {
53+
54+
fun forWholeLine(entry: AtEntry, inBatchMode: Boolean): RemoveAtEntryFix {
55+
val start = entry.siblings(forward = false, withSelf = false)
56+
.firstOrNull { it.elementType == AtTypes.CRLF }?.nextSibling
57+
val end = entry.siblings(forward = true, withSelf = true)
58+
.firstOrNull { it.elementType == AtTypes.CRLF }
59+
return RemoveAtEntryFix(start ?: entry, end ?: entry, inBatchMode)
60+
}
61+
}
62+
}

src/main/kotlin/platform/mcp/at/psi/mixins/AtEntryMixin.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import com.demonwav.mcdev.platform.mcp.at.gen.psi.AtFieldName
2727
import com.demonwav.mcdev.platform.mcp.at.gen.psi.AtFunction
2828
import com.demonwav.mcdev.platform.mcp.at.gen.psi.AtKeyword
2929
import com.demonwav.mcdev.platform.mcp.at.psi.AtElement
30+
import com.demonwav.mcdev.util.MemberReference
3031
import com.intellij.psi.PsiComment
3132

3233
interface AtEntryMixin : AtElement {
@@ -38,6 +39,7 @@ interface AtEntryMixin : AtElement {
3839
val keyword: AtKeyword
3940
val comment: PsiComment?
4041
val commentText: String?
42+
val memberReference: MemberReference?
4143

4244
fun setEntry(entry: String)
4345
fun setKeyword(keyword: AtElementFactory.Keyword)

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@
2121
package com.demonwav.mcdev.platform.mcp.at.psi.mixins.impl
2222

2323
import com.demonwav.mcdev.platform.mcp.at.AtElementFactory
24+
import com.demonwav.mcdev.platform.mcp.at.AtMemberReference
25+
import com.demonwav.mcdev.platform.mcp.at.gen.psi.AtEntry
2426
import com.demonwav.mcdev.platform.mcp.at.psi.mixins.AtEntryMixin
27+
import com.demonwav.mcdev.util.MemberReference
2528
import com.intellij.extapi.psi.ASTWrapperPsiElement
2629
import com.intellij.lang.ASTNode
2730
import com.intellij.psi.PsiComment
@@ -35,6 +38,9 @@ abstract class AtEntryImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), AtE
3538
override val commentText: String?
3639
get() = comment?.text?.substring(1)
3740

41+
override val memberReference: MemberReference?
42+
get() = (function ?: fieldName ?: asterisk)?.let { AtMemberReference.get(this as AtEntry, it) }
43+
3844
override fun setEntry(entry: String) {
3945
replace(AtElementFactory.createEntry(project, entry))
4046
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,14 @@
10071007
level="ERROR"
10081008
hasStaticDescription="true"
10091009
implementationClass="com.demonwav.mcdev.platform.mcp.at.AtUnresolvedReferenceInspection"/>
1010+
<localInspection displayName="Duplicate entry"
1011+
groupName="MCP"
1012+
language="Access Transformers"
1013+
enabledByDefault="true"
1014+
level="WARNING"
1015+
editorAttributes="NOT_USED_ELEMENT_ATTRIBUTES"
1016+
hasStaticDescription="true"
1017+
implementationClass="com.demonwav.mcdev.platform.mcp.at.AtDuplicateEntryInspection"/>
10101018
<localInspection displayName="Invalid empty ItemStack comparison with ItemStack.EMPTY"
10111019
groupName="MCP"
10121020
language="JAVA"
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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.framework.BaseMinecraftTest
24+
import org.junit.jupiter.api.DisplayName
25+
import org.junit.jupiter.api.Test
26+
27+
@DisplayName("Access Transformer Duplicate Entry Inspection")
28+
class AtDuplicateEntryInspectionTest : BaseMinecraftTest() {
29+
30+
@Test
31+
@DisplayName("Duplicate Entries")
32+
fun duplicateEntries() {
33+
buildProject {
34+
at(
35+
"test_at.cfg",
36+
"""
37+
public test.value.UniqueClass
38+
<warning descr="Duplicate entry">public test.value.DuplicateClass</warning>
39+
<warning descr="Duplicate entry">public test.value.DuplicateClass</warning>
40+
41+
public test.value.UniqueClass *
42+
<warning descr="Duplicate entry">public test.value.DuplicateClass *</warning>
43+
<warning descr="Duplicate entry">public test.value.DuplicateClass *</warning>
44+
45+
public test.value.UniqueClass *()
46+
<warning descr="Duplicate entry">public test.value.DuplicateClass *()</warning>
47+
<warning descr="Duplicate entry">public test.value.DuplicateClass *()</warning>
48+
49+
public test.value.UniqueClass field
50+
<warning descr="Duplicate entry">public test.value.DuplicateClass field</warning>
51+
<warning descr="Duplicate entry">public test.value.DuplicateClass field</warning>
52+
53+
public test.value.UniqueClass method()V
54+
<warning descr="Duplicate entry">public test.value.DuplicateClass method()V</warning>
55+
<warning descr="Duplicate entry">public test.value.DuplicateClass method()V</warning>
56+
57+
public test.value.UniqueClass method(II)V
58+
<warning descr="Duplicate entry">public test.value.DuplicateClass method(II)V</warning>
59+
<warning descr="Duplicate entry">public test.value.DuplicateClass method(II)V</warning>
60+
""".trimIndent()
61+
)
62+
}
63+
64+
fixture.enableInspections(AtDuplicateEntryInspection::class.java)
65+
fixture.checkHighlighting()
66+
}
67+
}

0 commit comments

Comments
 (0)