IDEA-337709: Reimplement SuppressionAnnotationInspection to UAST

GitOrigin-RevId: 5384975e4786fbc34114065e117f66d930435621
This commit is contained in:
Karol Lewandowski
2023-11-15 14:34:19 +01:00
committed by intellij-monorepo-bot
parent 7ba6375831
commit ad246cb491
16 changed files with 653 additions and 254 deletions

View File

@@ -246,8 +246,6 @@ cloneable.class.without.clone.todo.message=TODO: copy mutable state here, so the
use.obsolete.collection.type.display.name=Use of obsolete collection type
use.obsolete.collection.type.problem.descriptor=Obsolete collection type <code>#ref</code> used #loc
use.obsolete.collection.type.ignore.library.arguments.option=Ignore obsolete collection types where they are required
inspection.suppression.annotation.display.name=Inspection suppression annotation
inspection.suppression.annotation.problem.descriptor=Inspection suppression annotation <code>#ref</code> #loc
use.system.out.err.display.name=Use of 'System.out' or 'System.err'
use.system.out.err.problem.descriptor=Uses of <code>#ref</code> should probably be replaced with more robust logging #loc
dumpstack.call.display.name=Call to 'Thread.dumpStack()'
@@ -2468,10 +2466,6 @@ fix.data.provider.signature.family.name=Fix data provider method signature
fix.data.provider.signature.missing.method.problem=Parameterized test class <code>#ref</code> lacks data provider method annotated with '@Parameters'
fix.data.provider.signature.incorrect.problem=Data provider method <code>#ref()</code> has an incorrect signature
fix.data.provider.multiple.methods.problem=Multiple @Parameters data provider methods present in class <code>#ref</code>
allow.suppressions.fix.family.name=Allow suppressions
allow.suppressions.fix.text=Allow these suppressions
allow.suppressions.preview.text=Inspection id will be added to the list of allowed suppressions in settings
remove.suppress.comment.fix.family.name=Remove //{0}
throws.runtime.exception.fix.family.name=Remove from 'throws' clause
move.exception.to.javadoc.fix.family.name=Move to Javadoc '@throws'
create.package.info.java.family.name=Create 'package-info.java'

View File

@@ -1437,10 +1437,6 @@
implementationClass="com.siyeh.ig.logging.StringConcatenationArgumentToLogCallInspection" cleanupTool="true"/>
<!--group.names.code.maturity.issues-->
<localInspection groupPath="Java" language="JAVA" shortName="SuppressionAnnotation" bundle="messages.InspectionGadgetsBundle"
key="inspection.suppression.annotation.display.name" groupBundle="messages.InspectionsBundle"
groupKey="group.names.code.maturity.issues" enabledByDefault="false" level="WARNING"
implementationClass="com.siyeh.ig.maturity.SuppressionAnnotationInspection"/>
<localInspection groupPath="Java" language="JAVA" suppressId="UseOfSystemOutOrSystemErr" shortName="SystemOutErr" bundle="messages.InspectionGadgetsBundle"
key="use.system.out.err.display.name" groupBundle="messages.InspectionsBundle"
groupKey="group.names.code.maturity.issues" enabledByDefault="false" level="WARNING"

View File

@@ -1,178 +0,0 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.siyeh.ig.maturity;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.options.OptPane;
import com.intellij.java.JavaBundle;
import com.intellij.modcommand.ModCommand;
import com.intellij.modcommand.ModCommandQuickFix;
import com.intellij.modcommand.ModPsiUpdater;
import com.intellij.modcommand.PsiUpdateModCommandQuickFix;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.tree.IElementType;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.BaseInspection;
import com.siyeh.ig.BaseInspectionVisitor;
import com.siyeh.ig.InspectionGadgetsFix;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import static com.intellij.codeInspection.options.OptPane.pane;
public class SuppressionAnnotationInspection extends BaseInspection {
public List<String> myAllowedSuppressions = new ArrayList<>();
@Override
public @NotNull OptPane getOptionsPane() {
return pane(OptPane.stringList("myAllowedSuppressions", JavaBundle.message("ignored.suppressions")));
}
@Override
protected LocalQuickFix @NotNull [] buildFixes(Object... infos) {
final boolean suppressionIdPresent = ((Boolean)infos[1]).booleanValue();
if (infos[0] instanceof PsiAnnotation annotation) {
return suppressionIdPresent
? new LocalQuickFix[]{new RemoveAnnotationQuickFix(annotation, null), new AllowSuppressionsFix()}
: new LocalQuickFix[]{new RemoveAnnotationQuickFix(annotation, null),};
} else if (infos[0] instanceof PsiComment) {
return suppressionIdPresent
? new LocalQuickFix[]{new RemoveSuppressCommentFix(), new AllowSuppressionsFix()}
: new LocalQuickFix[]{new RemoveSuppressCommentFix()};
}
return InspectionGadgetsFix.EMPTY_ARRAY;
}
@Override
@NotNull
public String buildErrorString(Object... infos) {
return InspectionGadgetsBundle.message(
"inspection.suppression.annotation.problem.descriptor");
}
@Override
public boolean isSuppressedFor(@NotNull PsiElement element) {
return false;
}
@Override
public SuppressQuickFix @NotNull [] getBatchSuppressActions(@Nullable PsiElement element) {
return SuppressQuickFix.EMPTY_ARRAY;
}
@Override
public BaseInspectionVisitor buildVisitor() {
return new SuppressionAnnotationVisitor();
}
private static class RemoveSuppressCommentFix extends PsiUpdateModCommandQuickFix {
@Override
protected void applyFix(@NotNull Project project, @NotNull PsiElement startElement, @NotNull ModPsiUpdater updater) {
startElement.delete();
}
@NotNull
@Override
public String getFamilyName() {
return InspectionGadgetsBundle.message("remove.suppress.comment.fix.family.name", SuppressionUtilCore.SUPPRESS_INSPECTIONS_TAG_NAME);
}
}
private class AllowSuppressionsFix extends ModCommandQuickFix {
@Override
public @NotNull ModCommand perform(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
final PsiElement psiElement = descriptor.getPsiElement();
final Iterable<String> ids;
if (psiElement instanceof PsiAnnotation) {
ids = JavaSuppressionUtil.getInspectionIdsSuppressedInAnnotation((PsiModifierList)psiElement.getParent());
}
else {
final String suppressedIds = JavaSuppressionUtil.getSuppressedInspectionIdsIn(psiElement);
if (suppressedIds == null) {
return ModCommand.nop();
}
ids = StringUtil.tokenize(suppressedIds, ",");
}
return ModCommand.updateInspectionOption(psiElement, SuppressionAnnotationInspection.this, inspection -> {
for (String id : ids) {
if (!inspection.myAllowedSuppressions.contains(id)) {
inspection.myAllowedSuppressions.add(id);
}
}
});
}
@NotNull
@Override
public String getName() {
return InspectionGadgetsBundle.message("allow.suppressions.fix.text");
}
@NotNull
@Override
public String getFamilyName() {
return InspectionGadgetsBundle.message("allow.suppressions.fix.family.name");
}
}
private class SuppressionAnnotationVisitor extends BaseInspectionVisitor {
@Override
public void visitComment(@NotNull PsiComment comment) {
super.visitComment(comment);
final IElementType tokenType = comment.getTokenType();
if (!tokenType.equals(JavaTokenType.END_OF_LINE_COMMENT)
&& !tokenType.equals(JavaTokenType.C_STYLE_COMMENT)) {
return;
}
final String commentText = comment.getText();
if (commentText.length() <= 2) {
return;
}
@NonNls final String strippedComment = commentText.substring(2).trim();
if (!strippedComment.startsWith(SuppressionUtilCore.SUPPRESS_INSPECTIONS_TAG_NAME)) {
return;
}
final String suppressedIds = JavaSuppressionUtil.getSuppressedInspectionIdsIn(comment);
if (suppressedIds == null) {
registerError(comment, comment, Boolean.FALSE);
return;
}
final Iterable<String> ids = StringUtil.tokenize(suppressedIds, ",");
for (String id : ids) {
if (!myAllowedSuppressions.contains(id)) {
registerError(comment, comment, Boolean.TRUE);
break;
}
}
}
@Override
public void visitAnnotation(@NotNull PsiAnnotation annotation) {
super.visitAnnotation(annotation);
final PsiJavaCodeReferenceElement reference = annotation.getNameReferenceElement();
if (reference == null) {
return;
}
@NonNls final String text = reference.getText();
if ("SuppressWarnings".equals(text) ||
BatchSuppressManager.SUPPRESS_INSPECTIONS_ANNOTATION_NAME.equals(text)) {
final PsiElement annotationParent = annotation.getParent();
if (annotationParent instanceof PsiModifierList) {
final Collection<String> ids = JavaSuppressionUtil.getInspectionIdsSuppressedInAnnotation((PsiModifierList)annotationParent);
if (!myAllowedSuppressions.containsAll(ids)) {
registerError(annotation, annotation, Boolean.TRUE);
}
else if (ids.isEmpty()) {
registerError(annotation, annotation, Boolean.FALSE);
}
}
}
}
}
}

View File

@@ -1,22 +0,0 @@
<warning descr="Inspection suppression annotation '@SuppressWarnings({\\"ALL\\", \\"SuppressionAnnotation\\"})'">@SuppressWarnings({"ALL", "SuppressionAnnotation"})</warning>
public class SuppressionAnnotation {
<warning descr="Inspection suppression annotation '@SuppressWarnings(\\"PublicField\\")'">@SuppressWarnings("PublicField")</warning>
public String s;
<warning descr="Inspection suppression annotation '@SuppressWarnings({})'">@SuppressWarnings({})</warning>
public String t;
void foo() {
<warning descr="Inspection suppression annotation '//noinspection HardCodedStringLiteral'">//noinspection HardCodedStringLiteral</warning>
System.out.println("hello");
<warning descr="Inspection suppression annotation '//noinspection'">//noinspection</warning>
System.out.println();
}
@SuppressWarnings("FreeSpeech")
void bar() {
//noinspection FreeSpeech
System.out.println();
}
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.siyeh.ig.maturity;
import com.intellij.codeInspection.InspectionProfileEntry;
import com.siyeh.ig.LightJavaInspectionTestCase;
import org.jetbrains.annotations.Nullable;
/**
* @author Bas Leijdekkers
*/
public class SuppressionAnnotationInspectionTest extends LightJavaInspectionTestCase {
public void testSuppressionAnnotation() { doTest(); }
@Nullable
@Override
protected InspectionProfileEntry getInspection() {
final SuppressionAnnotationInspection inspection = new SuppressionAnnotationInspection();
inspection.myAllowedSuppressions.add("FreeSpeech");
return inspection;
}
}

View File

@@ -1679,7 +1679,6 @@ code.vision.overrides.hint={0, choice, 1#1 override|2#{0,number} overrides}
hint.text.tostring.method.could.not.be.created.from.template=''toString()'' method could not be created from template ''{0}''
hint.text.tostring.template.invalid=toString() template ''{0}'' is invalid
command.name.generate.tostring=Generate toString()
ignored.suppressions=Ignored suppressions:
hint.text.removed.imports=Removed {0} {1, choice, 0#import|1#imports}
hint.text.added.imports=, added {0} {1, choice, 0#import|1#imports}
hint.text.rearranged.imports=Rearranged imports

View File

@@ -0,0 +1,22 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection
import com.intellij.lang.LanguageExtension
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.psi.PsiElement
import org.jetbrains.uast.UAnnotation
import org.jetbrains.uast.UExpression
private val EP_NAME: ExtensionPointName<SuppressionAnnotationUtil> =
ExtensionPointName.create("com.intellij.codeInspection.suppressionAnnotationUtil")
interface SuppressionAnnotationUtil {
companion object {
@JvmField
val extension = LanguageExtension<SuppressionAnnotationUtil>(EP_NAME.name)
}
fun isSuppressionAnnotation(annotation: UAnnotation): Boolean
fun getSuppressionAnnotationAttributeExpressions(annotation: UAnnotation): List<UExpression>
fun getRemoveAnnotationQuickFix(annotation: PsiElement): LocalQuickFix?
}

View File

@@ -9,7 +9,11 @@
beanClass="com.intellij.lang.LanguageExtensionPoint">
<with attribute="implementationClass" implements="com.intellij.codeInspection.sourceToSink.SourceToSinkProvider"/>
</extensionPoint>
<extensionPoint qualifiedName="com.intellij.codeInspection.suppressionAnnotationUtil"
dynamic="true"
beanClass="com.intellij.lang.LanguageExtensionPoint">
<with attribute="implementationClass" implements="com.intellij.codeInspection.SuppressionAnnotationUtil"/>
</extensionPoint>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
<!--Test frameworks-->
@@ -203,6 +207,11 @@
groupBundle="messages.JvmAnalysisBundle" bundle="messages.JvmAnalysisBundle"
groupKey="jvm.inspections.group.name" key="jvm.inspections.system.get.property.display.name"
implementationClass="com.intellij.codeInspection.SystemGetPropertyInspection"/>
<localInspection language="UAST" enabledByDefault="false" level="WARNING"
shortName="SuppressionAnnotation"
groupBundle="messages.JvmAnalysisBundle" bundle="messages.JvmAnalysisBundle"
groupKey="jvm.inspections.group.name" key="inspection.suppression.annotation.display.name"
implementationClass="com.intellij.codeInspection.SuppressionAnnotationInspection"/>
<notificationGroup id="UAST" displayType="BALLOON" hideFromSettings="true"/>
<projectService serviceInterface="com.intellij.codeInsight.AnnotationCacheOwnerNormalizer"
serviceImplementation="com.intellij.psi.UastAnnotationCacheOwnerNormalizer"/>
@@ -214,6 +223,7 @@
</extensions>
<extensions defaultExtensionNs="com.intellij.codeInspection">
<sourceToSinkProvider language="JAVA" implementationClass="com.intellij.codeInspection.sourceToSink.JavaSourceToSinkProvider"/>
<suppressionAnnotationUtil language="JAVA" implementationClass="com.intellij.codeInspection.JavaSuppressionAnnotationUtil"/>
</extensions>
<actions>
<group id="UastInternal" text="UAST" internal="true" popup="true">

View File

@@ -3,12 +3,12 @@
Reports comments or annotations suppressing inspections.
<p>This inspection can be useful when leaving suppressions intentionally for further review.</p>
<p><b>Example:</b></p>
<pre><code>
@SuppressWarnings("unused")
static Stream&lt;String&gt; stringProvider() {
return Stream.of("foo", "bar");
}
<pre><code lang="Java">
@SuppressWarnings("unused")
static Stream&lt;String&gt; stringProvider() {
return Stream.of("foo", "bar");
}
</code></pre>
<!-- tooltip end -->
</body>
</html>
</html>

View File

@@ -300,3 +300,10 @@ title.uast=UAST
current.version=Current version
dialog.title.choose.annotation=Choose {0}
jvm.inspections.dependency.intention.description=Opens a dialog to configure dependency rules between scopes.
inspection.suppression.annotation.display.name=Inspection suppression annotation
inspection.suppression.annotation.problem.descriptor=Inspection suppression annotation <code>#ref</code> #loc
ignored.suppressions=Ignored suppressions:
remove.suppress.comment.fix.family.name=Remove //{0}
allow.suppressions.fix.family.name=Allow suppressions
allow.suppressions.fix.text=Allow these suppressions

View File

@@ -0,0 +1,226 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection
import com.intellij.analysis.JvmAnalysisBundle
import com.intellij.codeInspection.options.OptPane
import com.intellij.lang.jvm.JvmAnnotation
import com.intellij.modcommand.ModCommand
import com.intellij.modcommand.ModCommandQuickFix
import com.intellij.modcommand.ModPsiUpdater
import com.intellij.modcommand.PsiUpdateModCommandQuickFix
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiAnnotation
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import com.intellij.psi.util.PsiModificationTracker
import com.intellij.uast.UastHintedVisitorAdapter
import org.jetbrains.annotations.VisibleForTesting
import org.jetbrains.uast.*
import org.jetbrains.uast.visitor.AbstractUastNonRecursiveVisitor
import com.intellij.jvm.analysis.quickFix.RemoveAnnotationQuickFix as RemoveJvmAnnotationQuickFix
@VisibleForTesting
class SuppressionAnnotationInspection : AbstractBaseUastLocalInspectionTool() {
var myAllowedSuppressions: MutableList<String> = ArrayList()
override fun getOptionsPane(): OptPane {
return OptPane.pane(OptPane.stringList("myAllowedSuppressions", JvmAnalysisBundle.message("ignored.suppressions")))
}
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return UastHintedVisitorAdapter.create(
holder.file.language,
SuppressionAnnotationVisitor(holder),
arrayOf(UAnnotation::class.java, UComment::class.java),
true
)
}
override fun isSuppressedFor(element: PsiElement): Boolean {
return false
}
override fun getBatchSuppressActions(element: PsiElement?): Array<SuppressQuickFix> {
return SuppressQuickFix.EMPTY_ARRAY
}
private inner class SuppressionAnnotationVisitor(private val holder: ProblemsHolder) : AbstractUastNonRecursiveVisitor() {
override fun visitElement(node: UElement): Boolean {
if (node is UComment) {
return visitComment(node)
}
else {
return super.visitElement(node)
}
}
private fun visitComment(comment: UComment): Boolean {
if (isSuppressComment(comment)) {
val ids = getIdsFromComment(comment.text)
if (ids != null) {
for (id in ids) {
if (!myAllowedSuppressions.contains(id)) {
registerProblem(comment, true)
break
}
}
}
else {
registerProblem(comment, false)
}
}
return true
}
private fun isSuppressComment(comment: UComment): Boolean {
val text = comment.text
if (text.length <= 2 && !text.startsWith("//")) {
return false
}
val strippedComment = text.substring(2).trim()
return strippedComment.startsWith(SuppressionUtilCore.SUPPRESS_INSPECTIONS_TAG_NAME)
}
private fun registerProblem(comment: UComment, addAllowSuppressionsFix: Boolean) {
val fixes = if (addAllowSuppressionsFix) arrayOf(RemoveSuppressCommentFix(), AllowSuppressionsFix())
else arrayOf(RemoveSuppressCommentFix())
holder.registerProblem(comment.sourcePsi, JvmAnalysisBundle.message("inspection.suppression.annotation.problem.descriptor"), *fixes)
}
override fun visitAnnotation(node: UAnnotation): Boolean {
val suppressionAnnotationUtil = SuppressionAnnotationUtil.extension.forLanguage(node.lang) ?: return false
if (suppressionAnnotationUtil.isSuppressionAnnotation(node)) {
val ids = getInspectionIdsSuppressedInAnnotation(node, suppressionAnnotationUtil)
when {
ids.isNotEmpty() && !myAllowedSuppressions.containsAll(ids) -> registerProblem(node, true)
ids.isEmpty() -> registerProblem(node, false)
}
}
return true
}
private fun registerProblem(annotation: UAnnotation, addAllowSuppressionsFix: Boolean) {
val sourcePsi = annotation.sourcePsi ?: return
var fixes: Array<LocalQuickFix> = if (addAllowSuppressionsFix) arrayOf(AllowSuppressionsFix()) else arrayOf()
val removeAnnotationFix = getRemoveAnnotationFix(sourcePsi)
if (removeAnnotationFix != null) {
fixes += removeAnnotationFix
}
holder.registerProblem(sourcePsi, JvmAnalysisBundle.message("inspection.suppression.annotation.problem.descriptor"), *fixes)
}
private fun getRemoveAnnotationFix(annotationElement: PsiElement): LocalQuickFix? {
val suppressionAnnotationUtil = SuppressionAnnotationUtil.extension.forLanguage(annotationElement.language)
val quickFix = suppressionAnnotationUtil?.getRemoveAnnotationQuickFix(annotationElement)
if (quickFix != null) {
return quickFix
}
val jvmAnnotation = annotationElement as? JvmAnnotation ?: return null
return RemoveJvmAnnotationQuickFix(jvmAnnotation)
}
}
private class RemoveSuppressCommentFix : PsiUpdateModCommandQuickFix() {
override fun applyFix(project: Project, startElement: PsiElement, updater: ModPsiUpdater) {
startElement.delete()
}
override fun getFamilyName(): String {
return JvmAnalysisBundle.message("remove.suppress.comment.fix.family.name", SuppressionUtilCore.SUPPRESS_INSPECTIONS_TAG_NAME)
}
}
private inner class AllowSuppressionsFix : ModCommandQuickFix() {
override fun perform(project: Project, descriptor: ProblemDescriptor): ModCommand {
val psiElement = descriptor.psiElement
val ids = getIds(psiElement) ?: return ModCommand.nop()
return ModCommand.updateOption(psiElement, this@SuppressionAnnotationInspection) { inspection ->
for (id in ids) {
if (!inspection.myAllowedSuppressions.contains(id)) {
inspection.myAllowedSuppressions.add(id)
}
}
}
}
private fun getIds(psiElement: PsiElement): Collection<String>? {
val annotation = psiElement.toUElement()?.getParentOfType<UAnnotation>(strict = false)
if (annotation != null) {
val suppressionAnnotationUtil = SuppressionAnnotationUtil.extension.forLanguage(annotation.lang) ?: return null
return getInspectionIdsSuppressedInAnnotation(annotation, suppressionAnnotationUtil)
}
else {
val comment = psiElement.toUElement(UComment::class.java) ?: return null
return getIdsFromComment(comment.text)
}
}
override fun getName(): String {
return JvmAnalysisBundle.message("allow.suppressions.fix.text")
}
override fun getFamilyName(): String {
return JvmAnalysisBundle.message("allow.suppressions.fix.family.name")
}
}
}
private fun getInspectionIdsSuppressedInAnnotation(annotation: UAnnotation,
suppressionAnnotationUtil: SuppressionAnnotationUtil): List<String> {
val sourcePsi = annotation.sourcePsi ?: return emptyList()
return CachedValuesManager.getCachedValue(sourcePsi) {
CachedValueProvider.Result.create(
doGetInspectionIdsSuppressedInAnnotation(annotation, suppressionAnnotationUtil),
PsiModificationTracker.MODIFICATION_COUNT
)
}
}
// do not move it into visitor class, as it will cause CachedValue-related exceptions
private fun doGetInspectionIdsSuppressedInAnnotation(annotation: UAnnotation,
suppressionAnnotationUtil: SuppressionAnnotationUtil): List<String> {
val expressions = suppressionAnnotationUtil.getSuppressionAnnotationAttributeExpressions(annotation)
return expressions.flatMap { getInspectionIdsSuppressedInAnnotation(it) }
}
private fun getInspectionIdsSuppressedInAnnotation(expression: UExpression): List<String> {
return when (expression) {
is ULiteralExpression -> listOfNotNull(expression.value as? String)
is UReferenceExpression -> listOfNotNull(expression.evaluateString())
is UCallExpression -> expression.valueArguments.flatMap { getInspectionIdsSuppressedInAnnotation(it) }
else -> emptyList()
}
}
private fun getIdsFromComment(commentText: String): List<String>? {
val matcher = SuppressionUtil.SUPPRESS_IN_LINE_COMMENT_PATTERN.matcher(commentText)
if (matcher.matches()) {
val suppressedIds = matcher.group(1).trim()
return StringUtil.tokenize(suppressedIds, ",").toList().map { it.trim() }
}
else {
return null
}
}
internal class JavaSuppressionAnnotationUtil : SuppressionAnnotationUtil {
override fun isSuppressionAnnotation(annotation: UAnnotation): Boolean {
val psiAnnotation = annotation.sourcePsi as? PsiAnnotation ?: return false
val referenceText = psiAnnotation.nameReferenceElement?.text ?: return false
return "SuppressWarnings" == referenceText || BatchSuppressManager.SUPPRESS_INSPECTIONS_ANNOTATION_NAME == referenceText
}
override fun getSuppressionAnnotationAttributeExpressions(annotation: UAnnotation): List<UExpression> {
return annotation.attributeValues
.filter { it.name == null || it.name == "value" }
.map { it.expression }
}
override fun getRemoveAnnotationQuickFix(annotation: PsiElement): LocalQuickFix? {
return null // default will be used
}
}

View File

@@ -0,0 +1,42 @@
package com.intellij.jvm.analysis.internal.testFramework
import com.intellij.codeInspection.SuppressionAnnotationInspection
import com.intellij.codeInspection.ex.InspectionProfileImpl
import com.intellij.jvm.analysis.testFramework.JvmInspectionTestBase
import com.intellij.jvm.analysis.testFramework.JvmLanguage
import com.intellij.pom.java.LanguageLevel
import com.intellij.profile.codeInspection.InspectionProfileManager
import com.intellij.testFramework.LightProjectDescriptor
abstract class SuppressionAnnotationInspectionTestBase : JvmInspectionTestBase() {
override val inspection = SuppressionAnnotationInspection()
override fun getProjectDescriptor(): LightProjectDescriptor = object : ProjectDescriptor(LanguageLevel.HIGHEST, true) {}
protected fun testAllowSuppressionQuickFix(jvmLanguage: JvmLanguage, code: String, vararg ids: String) {
InspectionProfileImpl.INIT_INSPECTIONS = true
try {
assertEmpty(inspection.myAllowedSuppressions)
myFixture.configureByText("A.${jvmLanguage.ext}", code)
val intention = myFixture.findSingleIntention("Allow these suppressions")
val previewText = buildPreviewText(ids)
myFixture.checkIntentionPreviewHtml(
intention,
previewText
)
myFixture.launchAction(intention)
val inspection = InspectionProfileManager.getInstance(project).currentProfile
.getUnwrappedTool("SuppressionAnnotation", file) as SuppressionAnnotationInspection
assertContainsElements(inspection.myAllowedSuppressions, ids.toList())
}
finally {
InspectionProfileImpl.INIT_INSPECTIONS = false
}
}
private fun buildPreviewText(ids: Array<out String>): String {
val idsPart = ids.joinToString(separator = "") { "<option selected=\"selected\">$it</option>" }
return "Ignored suppressions:<br/><br/><select multiple=\"multiple\" size=\"${ids.size + 2}\">$idsPart</select>"
}
}

View File

@@ -0,0 +1,150 @@
package com.intellij.codeInspection.tests.java
import com.intellij.jvm.analysis.internal.testFramework.SuppressionAnnotationInspectionTestBase
import com.intellij.jvm.analysis.testFramework.JvmLanguage
class JavaSuppressionAnnotationInspectionTest : SuppressionAnnotationInspectionTestBase() {
fun `test highlighting`() {
inspection.myAllowedSuppressions.add("FreeSpeech")
myFixture.testHighlighting(
JvmLanguage.JAVA,
"""
<warning descr="Inspection suppression annotation '@SuppressWarnings({\"ALL\", \"SuppressionAnnotation\"})'">@SuppressWarnings({"ALL", "SuppressionAnnotation"})</warning>
public class A {
<warning descr="Inspection suppression annotation '@SuppressWarnings(\"PublicField\")'">@SuppressWarnings("PublicField")</warning>
public String s;
<warning descr="Inspection suppression annotation '@SuppressWarnings({})'">@SuppressWarnings({})</warning>
public String t;
void foo() {
<warning descr="Inspection suppression annotation '//noinspection HardCodedStringLiteral'">//noinspection HardCodedStringLiteral</warning>
System.out.println("hello");
<warning descr="Inspection suppression annotation '// noinspection'">// noinspection</warning>
System.out.println();
}
@SuppressWarnings("FreeSpeech")
void bar() {
//noinspection FreeSpeech
System.out.println();
}
}
""".trimIndent(),
fileName = "A"
)
}
fun `test quickfix - remove annotation`() {
myFixture.testQuickFix(JvmLanguage.JAVA, """
public class A {
@SuppressWarnings("PublicField", "Hard<caret>CodedStringLiteral")
public String s = "test";
}
""".trimIndent(), """
public class A {
public String s = "test";
}
""".trimIndent(), "Remove '@SuppressWarnings' annotation", testPreview = true)
}
fun `test quickfix - remove comment`() {
myFixture.testQuickFix(JvmLanguage.JAVA, """
public class A {
//noinspection PublicField, Hard<caret>CodedStringLiteral
public String s = "test";
}
""".trimIndent(), """
public class A {
public String s = "test";
}
""".trimIndent(), "Remove //noinspection", testPreview = true)
}
fun `test quickfix - allow a single suppression from annotation`() {
testAllowSuppressionQuickFix(JvmLanguage.JAVA, """
public class A {
@SuppressWarnings("Public<caret>Field")
public String s = "test";
}
""".trimIndent(), "PublicField")
}
fun `test quickfix - allow a single suppression from annotation when array form used`() {
testAllowSuppressionQuickFix(JvmLanguage.JAVA, """
public class A {
@SuppressWarnings({"Public<caret>Field"})
public String s = "test";
}
""".trimIndent(), "PublicField")
}
fun `test quickfix - allow a single suppression from annotation when explicit attribute name exists`() {
testAllowSuppressionQuickFix(JvmLanguage.JAVA, """
public class A {
@SuppressWarnings(value = "Public<caret>Field")
public String s = "test";
}
""".trimIndent(), "PublicField")
}
fun `test quickfix - allow multiple suppressions from annotation`() {
testAllowSuppressionQuickFix(JvmLanguage.JAVA, """
public class A {
@SuppressWarnings("Public<caret>Field", "HardCodedStringLiteral")
public String s = "test";
}
""".trimIndent(), "PublicField", "HardCodedStringLiteral")
}
fun `test quickfix - allow multiple suppressions from annotation when array form used`() {
testAllowSuppressionQuickFix(JvmLanguage.JAVA, """
public class A {
@SuppressWarnings({"Public<caret>Field", "HardCodedStringLiteral"})
public String s = "test";
}
""".trimIndent(), "PublicField", "HardCodedStringLiteral")
}
fun `test quickfix - allow multiple suppressions from annotation when explicit attribute name exists`() {
testAllowSuppressionQuickFix(JvmLanguage.JAVA, """
public class A {
@SuppressWarnings(value = {"Public<caret>Field", "HardCodedStringLiteral"})
public String s = "test";
}
""".trimIndent(), "PublicField", "HardCodedStringLiteral")
}
fun `test quickfix - allow multiple suppressions from annotation when constants used`() {
myFixture.addClass("""
public final class Constants {
public static final String PUBLIC_FIELD = "PublicField";
public static final String HARD_CODED_STRING_LITERAL = "HardCodedStringLiteral";
}
""".trimIndent())
testAllowSuppressionQuickFix(JvmLanguage.JAVA, """
public class A {
@Suppress<caret>Warnings({Constants.PUBLIC_FIELD, Constants.HARD_CODED_STRING_LITERAL})
public String s = "test";
}
""".trimIndent(), "PublicField", "HardCodedStringLiteral")
}
fun `test quickfix - allow a single suppression from comment`() {
testAllowSuppressionQuickFix(JvmLanguage.JAVA, """
public class A {
//noinspection Public<caret>Field
public String s = "test";
}
""".trimIndent(), "PublicField")
}
fun `test quickfix - allow multiple suppressions from comment`() {
testAllowSuppressionQuickFix(JvmLanguage.JAVA, """
public class A {
//noinspection Public<caret>Field, Hard<caret>CodedStringLiteral
public String s = "test";
}
""".trimIndent(), "PublicField", "HardCodedStringLiteral")
}
}

View File

@@ -0,0 +1,150 @@
package com.intellij.codeInspection.tests.kotlin
import com.intellij.jvm.analysis.internal.testFramework.SuppressionAnnotationInspectionTestBase
import com.intellij.jvm.analysis.testFramework.JvmLanguage
class KotlinSuppressionAnnotationInspectionTest : SuppressionAnnotationInspectionTestBase() {
fun `test highlighting`() {
inspection.myAllowedSuppressions.add("FreeSpeech")
myFixture.testHighlighting(
JvmLanguage.KOTLIN,
"""
<warning descr="Inspection suppression annotation '@Suppress(\"ALL\", \"SuppressionAnnotation\")'">@Suppress("ALL", "SuppressionAnnotation")</warning>
class A {
<warning descr="Inspection suppression annotation '@Suppress(\"PublicField\")'">@Suppress("PublicField")</warning>
var s: String? = null
<warning descr="Inspection suppression annotation '@Suppress'">@Suppress</warning>
var t: String? = null
fun foo() {
<warning descr="Inspection suppression annotation '//noinspection HardCodedStringLiteral'">//noinspection HardCodedStringLiteral</warning>
any("hello")
<warning descr="Inspection suppression annotation '// noinspection'">// noinspection</warning>
any()
}
@Suppress("FreeSpeech")
fun bar() {
// noinspection FreeSpeech
any()
}
}
private fun any(s: String? = null): String? = s
""".trimIndent()
)
}
fun `test quickfix - remove annotation`() {
myFixture.testQuickFix(JvmLanguage.KOTLIN, """
class A {
@Suppress("PublicField", "Hard<caret>CodedStringLiteral")
var s: String = "test"
}
""".trimIndent(), """
class A {
var s: String = "test"
}
""".trimIndent(), "Remove '@Suppress' annotation", testPreview = true)
}
fun `test quickfix - remove comment`() {
myFixture.testQuickFix(JvmLanguage.KOTLIN, """
class A {
//noinspection PublicField, Hard<caret>CodedStringLiteral
var s: String = "test"
}
""".trimIndent(), """
class A {
var s: String = "test"
}
""".trimIndent(), "Remove //noinspection", testPreview = true)
}
fun `test quickfix - allow a single suppression from annotation`() {
testAllowSuppressionQuickFix(JvmLanguage.KOTLIN, """
class A {
@Suppress("Public<caret>Field")
var s: String = "test"
}
""".trimIndent(), "PublicField")
}
fun `test quickfix - allow a single suppression from annotation when array form used`() {
testAllowSuppressionQuickFix(JvmLanguage.KOTLIN, """
class A {
@Suppress(["Public<caret>Field"])
var s: String = "test"
}
""".trimIndent(), "PublicField")
}
fun `test quickfix - allow a single suppression from annotation when explicit attribute name exists`() {
testAllowSuppressionQuickFix(JvmLanguage.KOTLIN, """
class A {
@Suppress(names = "Public<caret>Field")
var s: String = "test"
}
""".trimIndent(), "PublicField")
}
fun `test quickfix - allow multiple suppressions from annotation`() {
testAllowSuppressionQuickFix(JvmLanguage.KOTLIN, """
class A {
@Suppress("Public<caret>Field", "HardCodedStringLiteral")
var s: String = "test"
}
""".trimIndent(), "PublicField", "HardCodedStringLiteral")
}
fun `test quickfix - allow multiple suppressions from annotation when array form used`() {
testAllowSuppressionQuickFix(JvmLanguage.KOTLIN, """
class A {
@Suppress(["Public<caret>Field", "HardCodedStringLiteral"])
var s: String = "test"
}
""".trimIndent(), "PublicField", "HardCodedStringLiteral")
}
fun `test quickfix - allow multiple suppressions from annotation when explicit attribute name exists`() {
testAllowSuppressionQuickFix(JvmLanguage.KOTLIN, """
class A {
@Suppress(names = ["Public<caret>Field", "HardCodedStringLiteral"])
var s: String = "test"
}
""".trimIndent(), "PublicField", "HardCodedStringLiteral")
}
fun `test quickfix - allow multiple suppressions from annotation when constants used`() {
testAllowSuppressionQuickFix(JvmLanguage.KOTLIN, """
object Constants {
const val PUBLIC_FIELD = "PublicField"
const val HARD_CODED_STRING_LITERAL = "HardCodedStringLiteral"
}
class A {
@Suppress([Constants.PUBLIC_<caret>FIELD, Constants.HARD_CODED_STRING_LITERAL])
var s: String = "test"
}
""".trimIndent(), "PublicField", "HardCodedStringLiteral")
}
fun `test quickfix - allow a single suppression from comment`() {
testAllowSuppressionQuickFix(JvmLanguage.KOTLIN, """
class A {
//noinspection Public<caret>Field
var s: String = "test"
}
""".trimIndent(), "PublicField")
}
fun `test quickfix - allow multiple suppressions from comment`() {
testAllowSuppressionQuickFix(JvmLanguage.KOTLIN, """
class A {
//noinspection Public<caret>Field, Hard<caret>CodedStringLiteral
var s: String = "test"
}
""".trimIndent(), "PublicField", "HardCodedStringLiteral")
}
}

View File

@@ -0,0 +1,35 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.inspections
import com.intellij.codeInspection.CommonQuickFixBundle
import com.intellij.codeInspection.IntentionWrapper
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.SuppressionAnnotationUtil
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.uast.UAnnotation
import org.jetbrains.uast.UExpression
internal class KotlinSuppressionAnnotationUtil : SuppressionAnnotationUtil {
private val suppressionAnnotationClassQualifiedName = Suppress::class.java.name
override fun isSuppressionAnnotation(annotation: UAnnotation): Boolean {
return annotation.sourcePsi?.text?.contains("Suppress") == true && // avoid resolving
annotation.qualifiedName == suppressionAnnotationClassQualifiedName
}
override fun getSuppressionAnnotationAttributeExpressions(annotation: UAnnotation): List<UExpression> {
return annotation.attributeValues
.filter { it.name == null || it.name == "names" }
.map { it.expression }
}
override fun getRemoveAnnotationQuickFix(annotation: PsiElement): LocalQuickFix? {
if (annotation is KtAnnotationEntry) {
val fix = RemoveAnnotationFix(CommonQuickFixBundle.message("fix.remove.annotation.text", "Suppress"), annotation)
return IntentionWrapper.wrapToQuickFixes(arrayOf(fix), annotation.containingFile).takeIf { it.size == 1 }?.first()
}
return null
}
}

View File

@@ -118,6 +118,10 @@
<definitionsScopedSearch implementation="org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinDefinitionsSearcher"/>
<codeInspection.suppressionAnnotationUtil
language="kotlin"
implementationClass="org.jetbrains.kotlin.idea.inspections.KotlinSuppressionAnnotationUtil"/>
</extensions>