IJPL-19723 [inspections]: fix Redundant Suppression false positive for SSR inspection

GitOrigin-RevId: 068c01dc48efeb976e1fe9aba1933fbfd74450aa
This commit is contained in:
Bas Leijdekkers
2026-02-08 13:46:05 +00:00
committed by intellij-monorepo-bot
parent 3b4a6dc223
commit 1ea9a32a5b
7 changed files with 107 additions and 15 deletions
@@ -1,26 +1,43 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.structuralsearch.inspection;
import com.intellij.analysis.AnalysisScope;
import com.intellij.codeHighlighting.HighlightDisplayLevel;
import com.intellij.codeInsight.daemon.impl.DaemonProgressIndicator;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInspection.InspectionEngine;
import com.intellij.codeInspection.RedundantSuppressInspection;
import com.intellij.codeInspection.ex.GlobalInspectionToolWrapper;
import com.intellij.codeInspection.ex.InspectionProfileImpl;
import com.intellij.codeInspection.ex.InspectionToolWrapper;
import com.intellij.codeInspection.ex.LocalInspectionToolWrapper;
import com.intellij.codeInspection.ex.ToolsImpl;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.profile.codeInspection.InspectionProfileManager;
import com.intellij.psi.PsiFile;
import com.intellij.structuralsearch.MatchOptions;
import com.intellij.structuralsearch.inspection.SSBasedInspection;
import com.intellij.structuralsearch.inspection.StructuralSearchProfileActionProvider;
import com.intellij.structuralsearch.plugin.replace.ui.ReplaceConfiguration;
import com.intellij.structuralsearch.plugin.ui.Configuration;
import com.intellij.structuralsearch.plugin.ui.SearchConfiguration;
import com.intellij.testFramework.InspectionTestUtil;
import com.intellij.testFramework.InspectionsKt;
import com.intellij.testFramework.PerformanceUnitTest;
import com.intellij.testFramework.PlatformTestUtil;
import com.intellij.testFramework.fixtures.impl.GlobalInspectionContextForTests;
import com.intellij.tools.ide.metrics.benchmark.Benchmark;
import com.intellij.util.PairProcessor;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.junit.Assert.assertNotNull;
public class SSBasedInspectionTest extends SSBasedInspectionTestCase {
@@ -42,6 +59,41 @@ public class SSBasedInspectionTest extends SSBasedInspectionTestCase {
inspectionTest(configuration, HighlightDisplayLevel.ERROR);
quickFixTest("Suppress for statement");
}
public void testRedundantSuppress() {
final Configuration configuration = new ReplaceConfiguration();
configuration.setName("println");
configuration.setSuppressId("println");
final MatchOptions matchOptions = configuration.getMatchOptions();
matchOptions.setFileType(JavaFileType.INSTANCE);
matchOptions.fillSearchCriteria("System.out.println();");
configuration.getReplaceOptions().setReplacement("System.out.print('\\n');");
Project project = myFixture.getProject();
InspectionProfileImpl profile = InspectionProfileManager.getInstance(project).getCurrentProfile();
StructuralSearchProfileActionProvider.createNewInspection(configuration, project, profile);
@NotNull InspectionToolWrapper<?, ?> redundantSuppressInspection = new GlobalInspectionToolWrapper(new RedundantSuppressInspection());
VirtualFile sourceDir = myFixture.copyDirectoryToProject(new File("RedundantSuppress/", "src").getPath(), "");
AnalysisScope scope = new AnalysisScope(myFixture.getPsiManager().findDirectory(sourceDir));
SSBasedInspection inspection = SSBasedInspection.getStructuralSearchInspection(profile);
List<InspectionToolWrapper<?, ?>> wrappers = new ArrayList<>();
wrappers.add(redundantSuppressInspection);
wrappers.add(new LocalInspectionToolWrapper(inspection));
wrappers.addAll(inspection.getChildren());
GlobalInspectionContextForTests globalContext = InspectionsKt.createGlobalContextForTool(scope, project, wrappers);
InspectionTestUtil.runTool(redundantSuppressInspection, scope, globalContext);
InspectionTestUtil.compareToolResults(globalContext, redundantSuppressInspection, false, getTestDataPath() + "/RedundantSuppress/");
myFixture.testHighlighting(true, false, false, "FixAll.java");
final IntentionAction intention = myFixture.getAvailableIntention("Fix all 'println' problems in file");
assertNotNull("Intention not found. Did you forget a <caret>?", intention);
myFixture.checkPreviewAndLaunchAction(intention);
myFixture.checkResultByFile("FixAll.after.java");
}
public void testBrokenPattern() {
// check broken pattern does not throw exceptions
@@ -376,8 +376,8 @@ public final class InspectionEngine {
if (holder.hasResults()) {
for (ProblemDescriptor descriptor : holder.getResults()) {
PsiElement element = descriptor.getPsiElement();
if (element == null || !ignoreSuppressedElements || !SuppressionUtil.inspectionResultSuppressed(element, tool)) {
LocalInspectionToolWrapper wrapper = getRedirectedToolWrapper(toolWrapper, descriptor);
LocalInspectionToolWrapper wrapper = getRedirectedToolWrapper(toolWrapper, descriptor);
if (element == null || !ignoreSuppressedElements || !SuppressionUtil.inspectionResultSuppressed(element, wrapper.getTool())) {
resultDescriptors.computeIfAbsent(wrapper, x -> new ArrayList<>()).add(descriptor);
}
}
@@ -391,8 +391,7 @@ public final class InspectionEngine {
return resultDescriptors;
}
private static @Nullable LocalInspectionToolWrapper getRedirectedToolWrapper(LocalInspectionToolWrapper toolWrapper,
ProblemDescriptor descriptor) {
private static LocalInspectionToolWrapper getRedirectedToolWrapper(LocalInspectionToolWrapper toolWrapper, ProblemDescriptor descriptor) {
if (descriptor instanceof ProblemDescriptorWithReporterName name && toolWrapper.getTool() instanceof DynamicGroupTool groupTool) {
String reportingToolName = name.getReportingToolShortName();
for (LocalInspectionToolWrapper child : groupTool.getChildren()) {
@@ -416,8 +415,7 @@ public final class InspectionEngine {
if (toolWrapper instanceof LocalInspectionToolWrapper local) {
Map<LocalInspectionToolWrapper, List<ProblemDescriptor>> problemDescriptors =
inspectEx(Collections.singletonList(local), psiFile, psiFile.getTextRange(), psiFile.getTextRange(),
false,
false, true, new EmptyProgressIndicator(), PairProcessor.alwaysTrue());
false, false, true, new EmptyProgressIndicator(), PairProcessor.alwaysTrue());
for (List<ProblemDescriptor> group : problemDescriptors.values()) {
result.addAll(group);
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2026 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.AnalysisScope;
@@ -138,18 +138,24 @@ public abstract class RedundantSuppressInspectionBase extends GlobalSimpleInspec
InspectionToolWrapper<?, ?> toolWrapper = entry.getKey();
String toolId = entry.getValue();
toolWrapper.initialize(globalContext);
Collection<CommonProblemDescriptor> descriptors;
List<ProblemDescriptor> descriptors;
if (toolWrapper instanceof LocalInspectionToolWrapper local) {
if (local.isUnfair()) {
continue; // can't work with passes other than LocalInspectionPass
}
LocalInspectionTool tool = local.getTool();
String id = toolWrapper.getMainToolId();
List<LocalInspectionToolWrapper> wrappers;
if (id != null) {
LocalInspectionToolWrapper mainTool = (LocalInspectionToolWrapper)((InspectionProfileImpl)profile).getToolById(id, psiFile);
wrappers = mainTool != null ? List.of(local, mainTool) : List.of(local);
}
else {
wrappers = List.of(local);
}
List<ProblemDescriptor> found = Collections.synchronizedList(new ArrayList<>());
// shouldn't use standard ProblemsHolder because it filters out suppressed elements by default
InspectionEngine.inspectEx(Collections.singletonList(new LocalInspectionToolWrapper(tool)), psiFile, psiFile.getTextRange(),
psiFile.getTextRange(), false,
true, false, ProgressIndicatorProvider.getGlobalProgressIndicator(),
(wrapper, descriptor) -> found.add(descriptor));
InspectionEngine.inspectEx(wrappers, psiFile, psiFile.getTextRange(), psiFile.getTextRange(), false, true, false,
ProgressIndicatorProvider.getGlobalProgressIndicator(), (__, descriptor) -> found.add(descriptor));
descriptors = new ArrayList<>(found);
}
else if (toolWrapper instanceof GlobalInspectionToolWrapper global) {
@@ -166,7 +172,7 @@ public abstract class RedundantSuppressInspectionBase extends GlobalSimpleInspec
Collection<String> suppressedIds = e.getValue();
PsiElement suppressedScope = e.getKey();
if (!suppressedIds.contains(toolId)) continue;
for (CommonProblemDescriptor descriptor : descriptors) {
for (ProblemDescriptor descriptor : descriptors) {
if (!(descriptor instanceof ProblemDescriptor problemDescriptor)) continue;
PsiElement element = problemDescriptor.getPsiElement();
if (element == null) continue;
@@ -0,0 +1,11 @@
class Y {
void x() {
<caret>System.out.print('\n');
System.out.print('\n');
System.out.print('\n');
// noinspection println
System.out.println();
}
}
@@ -0,0 +1,11 @@
class Y {
void x() {
<warning descr="println"><caret>System.out.println();</warning>
<warning descr="println">System.out.println();</warning>
<warning descr="println">System.out.println();</warning>
// noinspection println
System.out.println();
}
}
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<problems>
</problems>
@@ -0,0 +1,11 @@
class X {
void x() {
System.out.println();
System.out.println();
System.out.println();
// noinspection println
System.out.println();
}
}