diff --git a/platform/analysis-api/resources/messages/AnalysisBundle.properties b/platform/analysis-api/resources/messages/AnalysisBundle.properties index dd4294a4469f..4f8bdbe38b76 100644 --- a/platform/analysis-api/resources/messages/AnalysisBundle.properties +++ b/platform/analysis-api/resources/messages/AnalysisBundle.properties @@ -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 diff --git a/platform/analysis-impl/src/com/intellij/codeInspection/SuppressionUtil.java b/platform/analysis-impl/src/com/intellij/codeInspection/SuppressionUtil.java index 0fd0daec5e72..e613979ad558 100644 --- a/platform/analysis-impl/src/com/intellij/codeInspection/SuppressionUtil.java +++ b/platform/analysis-impl/src/com/intellij/codeInspection/SuppressionUtil.java @@ -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"; diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/GroovySuppressableInspectionTool.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/GroovySuppressableInspectionTool.java index e72bc53fae0c..5af205931428 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/GroovySuppressableInspectionTool.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/GroovySuppressableInspectionTool.java @@ -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) { diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/SuppressByGroovyFileCommentFix.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/SuppressByGroovyFileCommentFix.kt new file mode 100644 index 000000000000..9c3f2369abcf --- /dev/null +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/SuppressByGroovyFileCommentFix.kt @@ -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 = 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 +} diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrSuppressionTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrSuppressionTest.groovy new file mode 100644 index 000000000000..f7bc0a565af1 --- /dev/null +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrSuppressionTest.groovy @@ -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("abcdef") +''', '''\ +//file:noinspection SpellCheckingInspection + + + +println("abcdef") +''' + } + + @Test + void 'suppress by file level comment after another comment'() { + doTest '''\ +/* some other comment */ + + + +println("abcdef") +''', '''\ +/* 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("abcdef") +''', '''\ +#!/usr/bin/env groovy +// some other comment +//file:noinspection SpellCheckingInspection + + + +println("abcdef") +''' + } +} diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrUnusedDefTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrUnusedDefTest.groovy index 4f7bf2b92b55..b90f41e1f202 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrUnusedDefTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrUnusedDefTest.groovy @@ -149,7 +149,7 @@ class Aaaa {} testHighlighting '''\ class Aaaa {} ''' - def action = fixture.findSingleIntention 'Suppress' + def action = fixture.findSingleIntention 'Suppress for class' fixture.launchAction(action) fixture.checkResult '''\