Skip to content

Commit 224e880

Browse files
committed
Support suppressing AT inspections for the whole file
Also fixes AtParserDefinition#spaceExistenceTypeBetweenTokens
1 parent b555af3 commit 224e880

File tree

6 files changed

+247
-16
lines changed

6 files changed

+247
-16
lines changed

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,51 @@ package com.demonwav.mcdev.platform.mcp.at
2323
import com.demonwav.mcdev.asset.PlatformAssets
2424
import com.demonwav.mcdev.facet.MinecraftFacet
2525
import com.demonwav.mcdev.platform.mcp.McpModuleType
26+
import com.demonwav.mcdev.platform.mcp.at.gen.psi.AtEntry
2627
import com.intellij.extapi.psi.PsiFileBase
2728
import com.intellij.openapi.application.ApplicationManager
2829
import com.intellij.openapi.module.ModuleUtilCore
2930
import com.intellij.psi.FileViewProvider
31+
import com.intellij.psi.PsiComment
32+
import com.intellij.psi.PsiElement
3033

3134
class AtFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, AtLanguage) {
3235

3336
init {
3437
setup()
3538
}
3639

40+
val headComments: List<PsiComment>
41+
get() {
42+
val comments = mutableListOf<PsiComment>()
43+
for (child in children) {
44+
if (child is AtEntry) {
45+
break
46+
}
47+
48+
if (child is PsiComment) {
49+
comments.add(child)
50+
}
51+
}
52+
53+
return comments
54+
}
55+
56+
fun addHeadComment(text: String) {
57+
val toAdd = text.lines().flatMap { listOf(AtElementFactory.createComment(project, it)) }
58+
val lastHeadComment = headComments.lastOrNull()
59+
if (lastHeadComment == null) {
60+
for (comment in toAdd.reversed()) {
61+
addAfter(comment, null)
62+
}
63+
} else {
64+
var previousComment: PsiElement? = lastHeadComment
65+
for (comment in toAdd) {
66+
previousComment = addAfter(comment, previousComment)
67+
}
68+
}
69+
}
70+
3771
private fun setup() {
3872
if (ApplicationManager.getApplication().isUnitTestMode) {
3973
return

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

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,26 @@ import com.intellij.codeInspection.util.IntentionName
2929
import com.intellij.openapi.project.Project
3030
import com.intellij.psi.PsiElement
3131
import com.intellij.psi.PsiFile
32+
import com.intellij.psi.util.PsiTreeUtil
3233
import com.intellij.psi.util.parentOfType
3334

3435
class AtInspectionSuppressor : InspectionSuppressor {
3536

3637
override fun isSuppressedFor(element: PsiElement, toolId: String): Boolean {
3738
val entry = element.parentOfType<AtEntry>(withSelf = true) ?: return false
38-
val comment = entry.commentText ?: return false
39-
val suppressed = comment.substringAfter("Suppress:").substringBefore(' ').split(',')
39+
val entryComment = entry.commentText
40+
if (entryComment != null) {
41+
if (isSuppressing(entryComment, toolId)) {
42+
return true
43+
}
44+
}
45+
46+
val file = element.containingFile as AtFile
47+
return file.headComments.any { comment -> isSuppressing(comment.text, toolId) }
48+
}
49+
50+
private fun isSuppressing(entryComment: String, toolId: String): Boolean {
51+
val suppressed = entryComment.substringAfter("Suppress:").substringBefore(' ').split(',')
4052
return toolId in suppressed
4153
}
4254

@@ -48,12 +60,24 @@ class AtInspectionSuppressor : InspectionSuppressor {
4860
return SuppressQuickFix.EMPTY_ARRAY
4961
}
5062

51-
return arrayOf(AtSuppressQuickFix(element, toolId))
63+
val entry = element as? AtEntry
64+
?: element.parentOfType<AtEntry>(withSelf = true)
65+
?: PsiTreeUtil.getPrevSiblingOfType(element, AtEntry::class.java) // For when we are at a CRLF
66+
return if (entry != null) {
67+
arrayOf(AtSuppressQuickFix(entry, toolId), AtSuppressQuickFix(element.containingFile, toolId))
68+
} else {
69+
arrayOf(AtSuppressQuickFix(element.containingFile, toolId))
70+
}
5271
}
5372

54-
class AtSuppressQuickFix(element: PsiElement, val toolId: String) : LocalQuickFixOnPsiElement(element), SuppressQuickFix {
73+
class AtSuppressQuickFix(element: PsiElement, val toolId: String) :
74+
LocalQuickFixOnPsiElement(element), SuppressQuickFix {
5575

56-
override fun getText(): @IntentionName String = "Suppress $toolId"
76+
override fun getText(): @IntentionName String = when (startElement) {
77+
is AtEntry -> "Suppress $toolId for entry"
78+
is AtFile -> "Suppress $toolId for file"
79+
else -> "Suppress $toolId"
80+
}
5781

5882
override fun getFamilyName(): @IntentionFamilyName String = "Suppress inspection"
5983

@@ -63,7 +87,13 @@ class AtInspectionSuppressor : InspectionSuppressor {
6387
startElement: PsiElement,
6488
endElement: PsiElement
6589
) {
66-
val entry = startElement.parentOfType<AtEntry>(withSelf = true) ?: return
90+
when (startElement) {
91+
is AtEntry -> suppressForEntry(startElement)
92+
is AtFile -> suppressForFile(startElement)
93+
}
94+
}
95+
96+
private fun suppressForEntry(entry: AtEntry) {
6797
val commentText = entry.commentText?.trim()
6898
if (commentText == null) {
6999
entry.setComment("Suppress:$toolId")
@@ -77,10 +107,32 @@ class AtInspectionSuppressor : InspectionSuppressor {
77107
}
78108

79109
val suppressEnd = commentText.indexOf(' ', suppressStart).takeUnless { it == -1 } ?: commentText.length
80-
val newComment = commentText.substring(suppressStart, suppressEnd) + ",$toolId" + commentText.substring(suppressEnd)
110+
val newComment =
111+
commentText.substring(suppressStart, suppressEnd) + ",$toolId" + commentText.substring(suppressEnd)
81112
entry.setComment(newComment)
82113
}
83114

115+
private fun suppressForFile(file: AtFile) {
116+
val existingSuppressComment = file.headComments.firstOrNull { it.text.contains("Suppress:") }
117+
if (existingSuppressComment == null) {
118+
file.addHeadComment("Suppress:$toolId")
119+
return
120+
}
121+
122+
val commentText = existingSuppressComment.text
123+
val suppressStart = commentText.indexOf("Suppress:")
124+
if (suppressStart == -1) {
125+
file.addHeadComment("Suppress:$toolId")
126+
return
127+
}
128+
129+
val suppressEnd = commentText.indexOf(' ', suppressStart).takeUnless { it == -1 } ?: commentText.length
130+
val newCommentText =
131+
commentText.substring(suppressStart, suppressEnd) + ",$toolId" + commentText.substring(suppressEnd)
132+
val newComment = AtElementFactory.createComment(file.project, newCommentText)
133+
existingSuppressComment.replace(newComment)
134+
}
135+
84136
override fun isAvailable(
85137
project: Project,
86138
context: PsiElement

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,7 @@ class AtParserDefinition : ParserDefinition {
4545
override fun createElement(node: ASTNode): PsiElement = AtTypes.Factory.createElement(node)
4646

4747
override fun spaceExistenceTypeBetweenTokens(left: ASTNode, right: ASTNode) =
48-
map.entries.firstOrNull { e -> left.elementType == e.key.first || right.elementType == e.key.second }?.value
49-
?: ParserDefinition.SpaceRequirements.MUST_NOT
48+
map[left.elementType to right.elementType] ?: ParserDefinition.SpaceRequirements.MUST_NOT
5049

5150
companion object {
5251
private val WHITE_SPACES = TokenSet.create(TokenType.WHITE_SPACE)
@@ -55,13 +54,14 @@ class AtParserDefinition : ParserDefinition {
5554
private val FILE = IFileElementType(Language.findInstance(AtLanguage::class.java))
5655

5756
private val map: Map<Pair<IElementType, IElementType>, ParserDefinition.SpaceRequirements> = mapOf(
58-
(AtTypes.KEYWORD to AtTypes.CLASS_NAME) to ParserDefinition.SpaceRequirements.MUST,
59-
(AtTypes.CLASS_NAME to AtTypes.FIELD_NAME) to ParserDefinition.SpaceRequirements.MUST,
60-
(AtTypes.CLASS_NAME to AtTypes.FUNCTION) to ParserDefinition.SpaceRequirements.MUST,
61-
(AtTypes.CLASS_NAME to AtTypes.ASTERISK) to ParserDefinition.SpaceRequirements.MUST,
57+
(AtTypes.KEYWORD_ELEMENT to AtTypes.CLASS_NAME_ELEMENT) to ParserDefinition.SpaceRequirements.MUST,
58+
(AtTypes.CLASS_NAME_ELEMENT to AtTypes.FIELD_NAME) to ParserDefinition.SpaceRequirements.MUST,
59+
(AtTypes.CLASS_NAME_ELEMENT to AtTypes.FUNCTION) to ParserDefinition.SpaceRequirements.MUST,
60+
(AtTypes.CLASS_NAME_ELEMENT to AtTypes.ASTERISK_ELEMENT) to ParserDefinition.SpaceRequirements.MUST,
61+
(AtTypes.CLASS_NAME_ELEMENT to AtTypes.COMMENT) to ParserDefinition.SpaceRequirements.MUST,
6262
(AtTypes.FIELD_NAME to AtTypes.COMMENT) to ParserDefinition.SpaceRequirements.MUST,
63-
(AtTypes.ASTERISK to AtTypes.COMMENT) to ParserDefinition.SpaceRequirements.MUST,
64-
(AtTypes.COMMENT to AtTypes.KEYWORD) to ParserDefinition.SpaceRequirements.MUST_LINE_BREAK,
63+
(AtTypes.ASTERISK_ELEMENT to AtTypes.COMMENT) to ParserDefinition.SpaceRequirements.MUST,
64+
(AtTypes.COMMENT to AtTypes.KEYWORD_ELEMENT) to ParserDefinition.SpaceRequirements.MUST_LINE_BREAK,
6565
(AtTypes.COMMENT to AtTypes.COMMENT) to ParserDefinition.SpaceRequirements.MUST_LINE_BREAK,
6666
(AtTypes.FUNCTION to AtTypes.COMMENT) to ParserDefinition.SpaceRequirements.MUST,
6767
)

src/test/kotlin/framework/test-util.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ package com.demonwav.mcdev.framework
2424

2525
import com.intellij.ide.highlighter.JavaFileType
2626
import com.intellij.lexer.Lexer
27+
import com.intellij.openapi.fileTypes.FileType
2728
import com.intellij.openapi.project.Project
2829
import com.intellij.openapi.roots.OrderRootType
2930
import com.intellij.openapi.roots.libraries.Library
@@ -130,6 +131,19 @@ fun testInspectionFix(fixture: JavaCodeInsightTestFixture, basePath: String, fix
130131
fixture.checkResult(expected)
131132
}
132133

134+
fun testInspectionFix(
135+
fixture: JavaCodeInsightTestFixture,
136+
fixName: String,
137+
fileType: FileType,
138+
before: String,
139+
after: String
140+
) {
141+
fixture.configureByText(fileType, before)
142+
val intention = fixture.findSingleIntention(fixName)
143+
fixture.launchAction(intention)
144+
fixture.checkResult(after)
145+
}
146+
133147
fun <T> assertEqualsUnordered(expected: Collection<T>, actual: Collection<T>) {
134148
val expectedSet = expected.toSet()
135149
val actualSet = actual.toSet()

src/test/kotlin/platform/mcp/at/AtDuplicateEntryInspectionTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import com.demonwav.mcdev.framework.BaseMinecraftTest
2424
import org.junit.jupiter.api.DisplayName
2525
import org.junit.jupiter.api.Test
2626

27-
@DisplayName("Access Transformer Duplicate Entry Inspection")
27+
@DisplayName("Access Transformer Duplicate Entry Inspection Tests")
2828
class AtDuplicateEntryInspectionTest : BaseMinecraftTest() {
2929

3030
@Test
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
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 com.demonwav.mcdev.framework.testInspectionFix
25+
import org.junit.jupiter.api.DisplayName
26+
import org.junit.jupiter.api.Test
27+
28+
@DisplayName("Access Transformer Inspection Suppressor Tests")
29+
class AtInspectionSuppressorTest : BaseMinecraftTest() {
30+
31+
@Test
32+
@DisplayName("Entry-Level Suppress")
33+
fun entryLevelSuppress() {
34+
fixture.configureByText(
35+
"test_at.cfg",
36+
"""
37+
public Unresolved # Suppress:AtUnresolvedReference
38+
public <error descr="Cannot resolve symbol 'Unresolved'">Unresolved</error>
39+
""".trimIndent()
40+
)
41+
42+
fixture.enableInspections(AtUnresolvedReferenceInspection::class.java)
43+
fixture.checkHighlighting()
44+
}
45+
46+
@Test
47+
@DisplayName("Entry-Level Suppress Fix")
48+
fun entryLevelSuppressFix() {
49+
fixture.enableInspections(AtUnresolvedReferenceInspection::class.java)
50+
testInspectionFix(
51+
fixture,
52+
"Suppress AtUnresolvedReference for entry",
53+
AtFileType,
54+
"public <caret>Unresolved",
55+
"public Unresolved # Suppress:AtUnresolvedReference"
56+
)
57+
}
58+
59+
@Test
60+
@DisplayName("File-Level Suppress")
61+
fun fileLevelSuppress() {
62+
fixture.configureByText(
63+
"test_at.cfg",
64+
"""
65+
# Suppress:AtUnresolvedReference
66+
public Unresolved
67+
public Unresolved
68+
""".trimIndent()
69+
)
70+
71+
fixture.enableInspections(AtUnresolvedReferenceInspection::class.java)
72+
fixture.checkHighlighting()
73+
}
74+
75+
@Test
76+
@DisplayName("File-Level Suppress Fix With No Existing Comments")
77+
fun fileLevelSuppressFixNoComments() {
78+
fixture.enableInspections(AtUnresolvedReferenceInspection::class.java)
79+
testInspectionFix(
80+
fixture,
81+
"Suppress AtUnresolvedReference for file",
82+
AtFileType,
83+
"public <caret>Unresolved",
84+
"""
85+
# Suppress:AtUnresolvedReference
86+
public Unresolved
87+
""".trimIndent()
88+
)
89+
}
90+
91+
@Test
92+
@DisplayName("File-Level Suppress Fix With Unrelated Comment")
93+
fun fileLevelSuppressFixWithUnrelatedComment() {
94+
fixture.enableInspections(AtUnresolvedReferenceInspection::class.java)
95+
testInspectionFix(
96+
fixture,
97+
"Suppress AtUnresolvedReference for file",
98+
AtFileType,
99+
"""
100+
# This is a header comment
101+
public <caret>Unresolved
102+
""".trimIndent(),
103+
"""
104+
# This is a header comment
105+
# Suppress:AtUnresolvedReference
106+
public Unresolved
107+
""".trimIndent()
108+
)
109+
}
110+
111+
@Test
112+
@DisplayName("File-Level Suppress Fix With Existing Suppress")
113+
fun fileLevelSuppressFixWithExistingSuppress() {
114+
fixture.enableInspections(AtUnresolvedReferenceInspection::class.java)
115+
testInspectionFix(
116+
fixture,
117+
"Suppress AtUnresolvedReference for file",
118+
AtFileType,
119+
"""
120+
# This is a header comment
121+
# Suppress:AtUsage
122+
public <caret>Unresolved
123+
""".trimIndent(),
124+
"""
125+
# This is a header comment
126+
# Suppress:AtUsage,AtUnresolvedReference
127+
public Unresolved
128+
""".trimIndent()
129+
)
130+
}
131+
}

0 commit comments

Comments
 (0)