IDEA-251753 [groovy] support file level suppression

GitOrigin-RevId: 4b214fbddcbe3402af0bddef42f818fd0c269a55
This commit is contained in:
Daniil Ovchinnikov
2020-10-20 17:03:57 +00:00
committed by intellij-monorepo-bot
parent 9e55da7eb9
commit 7cb573430a
6 changed files with 157 additions and 2 deletions
@@ -129,6 +129,7 @@ display.name.directory.0=Directory ''{0}''
shared.scopes.node.text=Shared Scopes
suppress.inspection.family=Suppress inspection
suppress.inspection.statement=Suppress for statement
suppress.inspection.file=Suppress for file
suspicious.name.combination.add.title=Add Group of Names
suspicious.name.combination.display.name=Suspicious variable/parameter name combination
suspicious.name.combination.edit.title=Edit Group of Names
@@ -26,6 +26,9 @@ import java.util.regex.Pattern;
* @author yole
*/
public final class SuppressionUtil extends SuppressionUtilCore {
public static final @NonNls String FILE_PREFIX = "file:";
/**
* Common part of regexp for suppressing in line comments for different languages.
* Comment start prefix isn't included, e.g. add '//' for Java/C/JS or '#' for Ruby
@@ -37,6 +40,7 @@ public final class SuppressionUtil extends SuppressionUtilCore {
@NonNls
public static final Pattern SUPPRESS_IN_LINE_COMMENT_PATTERN = Pattern.compile("//" + COMMON_SUPPRESS_REGEXP + ".*"); // for Java, C, JS line comments
public static final Pattern SUPPRESS_IN_FILE_LINE_COMMENT_PATTERN = Pattern.compile("//" + FILE_PREFIX + COMMON_SUPPRESS_REGEXP + ".*");
@NonNls
public static final String ALL = "ALL";
@@ -1,4 +1,4 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.plugins.groovy.codeInspection;
@@ -42,6 +42,7 @@ public abstract class GroovySuppressableInspectionTool extends LocalInspectionTo
}
return new SuppressQuickFix[]{
new SuppressByGroovyCommentFix(toolId),
new SuppressByGroovyFileCommentFix(toolId),
new SuppressForMemberFix(toolId, false),
new SuppressForMemberFix(toolId, true),
};
@@ -80,6 +81,11 @@ public abstract class GroovySuppressableInspectionTool extends LocalInspectionTo
}
}
PsiElement fileLevelSuppression = SuppressByGroovyFileCommentFixKt.fileLevelSuppression(place, toolId);
if (fileLevelSuppression != null) {
return fileLevelSuppression;
}
GrMember member = null;
GrDocComment docComment = PsiTreeUtil.getParentOfType(place, GrDocComment.class);
if (docComment != null) {
@@ -0,0 +1,68 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.plugins.groovy.codeInspection
import com.intellij.analysis.AnalysisBundle
import com.intellij.codeInsight.daemon.impl.actions.SuppressByCommentFix
import com.intellij.codeInspection.SuppressionUtil
import com.intellij.codeInspection.SuppressionUtilCore
import com.intellij.codeInspection.util.IntentionName
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.TokenType
import org.jetbrains.plugins.groovy.lang.psi.GroovyElementTypes
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile
internal class SuppressByGroovyFileCommentFix(toolId: String) : SuppressByCommentFix(toolId, GroovyFile::class.java) {
override fun getText(): @IntentionName String = AnalysisBundle.message("suppress.inspection.file")
override fun getContainer(context: PsiElement?): PsiElement? = context?.containingFile
override fun createSuppression(project: Project, element: PsiElement, container: PsiElement) {
val file = container as GroovyFile
val document = PsiDocumentManager.getInstance(project).getDocument(file) ?: return
val commentText = "//" + SuppressionUtil.FILE_PREFIX + SuppressionUtilCore.SUPPRESS_INSPECTIONS_TAG_NAME + " " + myID
val anchor = fileComments(file).lastOrNull()
if (anchor == null) {
document.insertString(0, "$commentText\n")
}
else {
document.insertString(anchor.textRange.endOffset, "\n$commentText")
}
}
}
/**
* Skips comments and whitespaces at the start of the file, and returns last found comment.
* This includes skipping hash bang, copyright notice, and/or other suppressions.
*/
private fun fileComments(file: GroovyFile): Sequence<PsiComment> = sequence {
var child = file.firstChild
while (child != null) {
val elementType = child.node.elementType
if (elementType === GroovyElementTypes.NL || elementType === TokenType.WHITE_SPACE) {
child = child.nextSibling
}
else if (child is PsiComment) {
yield(child)
child = child.getNextSibling()
}
else {
break
}
}
}
internal fun fileLevelSuppression(place: PsiElement, toolId: String): PsiElement? {
val containingFile = place.containingFile as? GroovyFile ?: return null
for (comment in fileComments(containingFile)) {
val text = comment.text
val matcher = SuppressionUtil.SUPPRESS_IN_FILE_LINE_COMMENT_PATTERN.matcher(text)
if (matcher.matches() && SuppressionUtil.isInspectionToolIdMentioned(matcher.group(1), toolId)) {
return comment
}
}
return null
}
@@ -0,0 +1,76 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.plugins.groovy.lang.highlighting
import com.intellij.analysis.AnalysisBundle
import com.intellij.spellchecker.inspections.SpellCheckingInspection
import groovy.transform.CompileStatic
import org.jetbrains.plugins.groovy.util.GroovyLatestTest
import org.jetbrains.plugins.groovy.util.HighlightingTest
import org.junit.Test
@SuppressWarnings('SpellCheckingInspection')
@CompileStatic
class GrSuppressionTest extends GroovyLatestTest implements HighlightingTest {
private void doTest(String before, String after) {
fixture.enableInspections SpellCheckingInspection
configureByText before
fixture.checkHighlighting()
fixture.launchAction fixture.findSingleIntention(AnalysisBundle.message("suppress.inspection.file"))
fixture.checkResult after
}
@Test
void 'suppress by file level comment'() {
doTest '''\
println("<TYPO><caret>abcdef</TYPO>")
''', '''\
//file:noinspection SpellCheckingInspection
println("abcdef")
'''
}
@Test
void 'suppress by file level comment after another comment'() {
doTest '''\
/* some other comment */
println("<TYPO><caret>abcdef</TYPO>")
''', '''\
/* some other comment */
//file:noinspection SpellCheckingInspection
println("abcdef")
'''
}
@Test
void 'suppress by file level comment after hash bang'() {
doTest '''\
#!/usr/bin/env groovy
// some other comment
println("<TYPO><caret>abcdef</TYPO>")
''', '''\
#!/usr/bin/env groovy
// some other comment
//file:noinspection SpellCheckingInspection
println("abcdef")
'''
}
}
@@ -149,7 +149,7 @@ class Aaaa {}
testHighlighting '''\
class <caret><warning descr="Class Aaaa is unused">Aaaa</warning> {}
'''
def action = fixture.findSingleIntention 'Suppress'
def action = fixture.findSingleIntention 'Suppress for class'
fixture.launchAction(action)
fixture.checkResult '''\