diff options
7 files changed, 89 insertions, 100 deletions
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputFile.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputFile.java index 278d288d185..f7d640a4dbb 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputFile.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputFile.java @@ -30,6 +30,8 @@ import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import java.util.function.Consumer; import javax.annotation.CheckForNull; import javax.annotation.Nullable; @@ -56,6 +58,7 @@ public class DefaultInputFile extends DefaultInputComponent implements InputFile private Metadata metadata; private boolean published; private boolean excludedForCoverage; + private final Set<Integer> noSonarLines = new HashSet<>(); public DefaultInputFile(DefaultIndexedFile indexedFile, Consumer<DefaultInputFile> metadataGenerator) { this(indexedFile, metadataGenerator, null); @@ -82,7 +85,7 @@ public class DefaultInputFile extends DefaultInputComponent implements InputFile public InputStream inputStream() throws IOException { return contents != null ? new ByteArrayInputStream(contents.getBytes(charset())) : new BOMInputStream(Files.newInputStream(path()), - ByteOrderMark.UTF_8, ByteOrderMark.UTF_16LE, ByteOrderMark.UTF_16BE, ByteOrderMark.UTF_32LE, ByteOrderMark.UTF_32BE); + ByteOrderMark.UTF_8, ByteOrderMark.UTF_16LE, ByteOrderMark.UTF_16BE, ByteOrderMark.UTF_32LE, ByteOrderMark.UTF_32BE); } @Override @@ -242,7 +245,7 @@ public class DefaultInputFile extends DefaultInputComponent implements InputFile checkMetadata(); Preconditions.checkState(metadata.originalLineEndOffsets() != null, "InputFile is not properly initialized."); Preconditions.checkState(metadata.originalLineEndOffsets().length == metadata.lines(), - "InputFile is not properly initialized. 'originalLineEndOffsets' property length should be equal to 'lines'"); + "InputFile is not properly initialized. 'originalLineEndOffsets' property length should be equal to 'lines'"); return metadata.originalLineEndOffsets(); } @@ -299,7 +302,7 @@ public class DefaultInputFile extends DefaultInputComponent implements InputFile int line = findLine(globalOffset); int startLineOffset = originalLineStartOffsets()[line - 1]; // In case the global offset is between \r and \n, move the pointer to a valid location - return new DefaultTextPointer(line, Math.min(globalOffset, originalLineEndOffsets()[line -1]) - startLineOffset); + return new DefaultTextPointer(line, Math.min(globalOffset, originalLineEndOffsets()[line - 1]) - startLineOffset); } public DefaultInputFile setStatus(Status status) { @@ -369,4 +372,12 @@ public class DefaultInputFile extends DefaultInputComponent implements InputFile return indexedFile.uri(); } + public void noSonarAt(Set<Integer> noSonarLines) { + this.noSonarLines.addAll(noSonarLines); + } + + public boolean hasNoSonarAt(int line) { + return this.noSonarLines.contains(line); + } + } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/issue/NoSonarFilter.java b/sonar-plugin-api/src/main/java/org/sonar/api/issue/NoSonarFilter.java index 947b6d28c18..a58e5f02e4f 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/issue/NoSonarFilter.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/issue/NoSonarFilter.java @@ -19,63 +19,34 @@ */ package org.sonar.api.issue; -import java.util.HashMap; -import java.util.Map; import java.util.Set; -import org.apache.commons.lang.StringUtils; -import org.sonar.api.batch.ScannerSide; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.internal.DefaultInputFile; -import org.sonar.api.scan.issue.filter.FilterableIssue; -import org.sonar.api.scan.issue.filter.IssueFilter; -import org.sonar.api.scan.issue.filter.IssueFilterChain; +import org.sonar.api.scanner.ScannerSide; /** * Issue filter used to ignore issues created on lines commented with the tag "NOSONAR". * <br> - * Plugins, via {@link ScannerSide}s, must feed this filter by registering the + * Plugins, via {@link org.sonar.api.batch.sensor.Sensor}s, must feed this filter by registering the * lines that contain "NOSONAR". Note that filters are disabled for the issues reported by * end-users from UI or web services. * * @since 3.6 */ -public class NoSonarFilter implements IssueFilter { - - private final Map<String, Set<Integer>> noSonarLinesByResource = new HashMap<>(); - - /** - * @deprecated since 5.0 use {@link #noSonarInFile(InputFile, Set)} - */ - @Deprecated - public NoSonarFilter addComponent(String componentKey, Set<Integer> noSonarLines) { - noSonarLinesByResource.put(componentKey, noSonarLines); - return this; - } +@ScannerSide +public class NoSonarFilter { /** * Register lines in a file that contains the NOSONAR flag. + * * @param inputFile * @param noSonarLines Line number starts at 1 in a file * @since 5.0 + * @since 7.6 the method can be called multiple times by different sensors, and NOSONAR lines are merged */ public NoSonarFilter noSonarInFile(InputFile inputFile, Set<Integer> noSonarLines) { - noSonarLinesByResource.put(((DefaultInputFile) inputFile).key(), noSonarLines); + ((DefaultInputFile) inputFile).noSonarAt(noSonarLines); return this; } - @Override - public boolean accept(FilterableIssue issue, IssueFilterChain chain) { - boolean accepted = true; - if (issue.line() != null) { - Set<Integer> noSonarLines = noSonarLinesByResource.get(issue.componentKey()); - accepted = noSonarLines == null || !noSonarLines.contains(issue.line()); - if (!accepted && StringUtils.containsIgnoreCase(issue.ruleKey().rule(), "nosonar")) { - accepted = true; - } - } - if (accepted) { - accepted = chain.accept(issue); - } - return accepted; - } } diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/issue/NoSonarFilterTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/issue/NoSonarFilterTest.java index 3e4435f489b..0665df11878 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/issue/NoSonarFilterTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/issue/NoSonarFilterTest.java @@ -19,72 +19,23 @@ */ package org.sonar.api.issue; -import org.sonar.api.scan.issue.filter.FilterableIssue; - -import com.google.common.collect.ImmutableSet; -import org.junit.Before; +import java.util.Arrays; +import java.util.HashSet; import org.junit.Test; -import org.sonar.api.scan.issue.filter.IssueFilterChain; -import org.sonar.api.rule.RuleKey; - -import java.util.Set; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.fs.internal.TestInputFileBuilder; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.isA; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; public class NoSonarFilterTest { - NoSonarFilter filter = new NoSonarFilter(); - IssueFilterChain chain = mock(IssueFilterChain.class); - - @Before - public void setupChain() { - when(chain.accept(isA(FilterableIssue.class))).thenReturn(true); - } - - @Test - public void should_ignore_lines_commented_with_nosonar() { - FilterableIssue issue = mock(FilterableIssue.class); - when(issue.componentKey()).thenReturn("struts:org.apache.Action"); - when(issue.ruleKey()).thenReturn(RuleKey.of("squid", "AvoidCycles")); - - Set<Integer> noSonarLines = ImmutableSet.of(31, 55); - filter.addComponent("struts:org.apache.Action", noSonarLines); - - // issue on file - when(issue.line()).thenReturn(null); - assertThat(filter.accept(issue, chain)).isTrue(); - - // issue on lines - when(issue.line()).thenReturn(31); - assertThat(filter.accept(issue, chain)).isFalse(); - - when(issue.line()).thenReturn(222); - assertThat(filter.accept(issue, chain)).isTrue(); - - verify(chain, times(2)).accept(issue); - } - @Test - public void should_accept_issues_on_no_sonar_rules() { - // The "No Sonar" rule logs violations on the lines that are flagged with "NOSONAR" !! - FilterableIssue issue = mock(FilterableIssue.class); - when(issue.componentKey()).thenReturn("struts:org.apache.Action"); - when(issue.ruleKey()).thenReturn(RuleKey.of("squid", "NoSonarCheck")); - - Set<Integer> noSonarLines = ImmutableSet.of(31, 55); - filter.addComponent("struts:org.apache.Action", noSonarLines); - - when(issue.line()).thenReturn(31); - assertThat(filter.accept(issue, chain)).isTrue(); - - when(issue.line()).thenReturn(222); - assertThat(filter.accept(issue, chain)).isTrue(); + public void should_store_nosonar_lines_on_inputfile() { + DefaultInputFile f = TestInputFileBuilder.create("module1", "myfile.java").build(); + new NoSonarFilter().noSonarInFile(f, new HashSet<>(Arrays.asList(1,4))); - verify(chain, times(2)).accept(issue); + assertThat(f.hasNoSonarAt(1)).isTrue(); + assertThat(f.hasNoSonarAt(2)).isFalse(); + assertThat(f.hasNoSonarAt(4)).isTrue(); } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/ModuleIssues.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/ModuleIssues.java index edf5417d4f6..ce07d5a59db 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/ModuleIssues.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/ModuleIssues.java @@ -22,8 +22,10 @@ package org.sonar.scanner.issue; import java.util.Collection; import java.util.function.Consumer; import javax.annotation.concurrent.ThreadSafe; +import org.apache.commons.lang.StringUtils; import org.sonar.api.batch.fs.TextRange; import org.sonar.api.batch.fs.internal.DefaultInputComponent; +import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.rule.ActiveRule; import org.sonar.api.batch.rule.ActiveRules; import org.sonar.api.batch.sensor.issue.ExternalIssue; @@ -56,6 +58,10 @@ public class ModuleIssues { public boolean initAndAddIssue(Issue issue) { DefaultInputComponent inputComponent = (DefaultInputComponent) issue.primaryLocation().inputComponent(); + if (noSonar(inputComponent, issue)) { + return false; + } + ActiveRule activeRule = activeRules.find(issue.ruleKey()); if (activeRule == null) { // rule does not exist or is not enabled -> ignore the issue @@ -71,6 +77,14 @@ public class ModuleIssues { return false; } + private static boolean noSonar(DefaultInputComponent inputComponent, Issue issue) { + TextRange textRange = issue.primaryLocation().textRange(); + return inputComponent.isFile() + && textRange != null + && ((DefaultInputFile) inputComponent).hasNoSonarAt(textRange.start().line()) + && !StringUtils.containsIgnoreCase(issue.ruleKey().rule(), "nosonar"); + } + public void initAndAddExternalIssue(ExternalIssue issue) { DefaultInputComponent inputComponent = (DefaultInputComponent) issue.primaryLocation().inputComponent(); ScannerReport.ExternalIssue rawExternalIssue = createReportExternalIssue(issue, inputComponent.batchId()); diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java index a8cf432e5f6..68bd4a0ffdf 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java @@ -23,7 +23,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.batch.fs.internal.DefaultInputModule; import org.sonar.api.batch.fs.internal.FileMetadata; -import org.sonar.api.issue.NoSonarFilter; import org.sonar.api.scan.filesystem.FileExclusions; import org.sonar.core.extension.CoreExtensionsInstaller; import org.sonar.core.platform.ComponentContainer; @@ -130,7 +129,6 @@ public class ModuleScanContainer extends ComponentContainer { // issues ModuleIssues.class, - NoSonarFilter.class, // issue exclusions IssueInclusionPatternInitializer.class, diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java index e9c8b229b26..0171e117e5f 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java @@ -25,6 +25,7 @@ import org.sonar.api.batch.fs.internal.DefaultInputModule; import org.sonar.api.batch.fs.internal.InputModuleHierarchy; import org.sonar.api.batch.fs.internal.SensorStrategy; import org.sonar.api.batch.rule.CheckFactory; +import org.sonar.api.issue.NoSonarFilter; import org.sonar.api.resources.Languages; import org.sonar.api.resources.ResourceTypes; import org.sonar.api.scan.filesystem.PathResolver; @@ -175,6 +176,7 @@ public class ProjectScanContainer extends ComponentContainer { IssueCache.class, DefaultProjectIssues.class, IssueTransition.class, + NoSonarFilter.class, // metrics DefaultMetricFinder.class, diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/ModuleIssuesTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/ModuleIssuesTest.java index ed972c1dbc9..ab8dd62ce52 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/ModuleIssuesTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/ModuleIssuesTest.java @@ -19,6 +19,8 @@ */ package org.sonar.scanner.issue; +import java.util.Collections; +import java.util.HashSet; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -53,6 +55,7 @@ public class ModuleIssuesTest { static final RuleKey SQUID_RULE_KEY = RuleKey.of("squid", "AvoidCycle"); static final String SQUID_RULE_NAME = "Avoid Cycle"; + private static final RuleKey NOSONAR_RULE_KEY = RuleKey.of("squid", "NoSonarCheck"); @Mock ModuleIssueFilters filters; @@ -179,6 +182,45 @@ public class ModuleIssuesTest { verifyZeroInteractions(reportPublisher); } + @Test + public void should_ignore_lines_commented_with_nosonar() { + ruleBuilder.add(SQUID_RULE_KEY).setName(SQUID_RULE_NAME); + activeRulesBuilder.create(SQUID_RULE_KEY).setSeverity(Severity.INFO).activate(); + initModuleIssues(); + + DefaultIssue issue = new DefaultIssue() + .at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message("")) + .forRule(SQUID_RULE_KEY); + + file.noSonarAt(new HashSet<>(Collections.singletonList(3))); + + boolean added = moduleIssues.initAndAddIssue(issue); + + assertThat(added).isFalse(); + verifyZeroInteractions(reportPublisher); + } + + @Test + public void should_accept_issues_on_no_sonar_rules() { + // The "No Sonar" rule logs violations on the lines that are flagged with "NOSONAR" !! + ruleBuilder.add(NOSONAR_RULE_KEY).setName("No Sonar"); + activeRulesBuilder.create(NOSONAR_RULE_KEY).setSeverity(Severity.INFO).activate(); + initModuleIssues(); + + file.noSonarAt(new HashSet<>(Collections.singletonList(3))); + + DefaultIssue issue = new DefaultIssue() + .at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message("")) + .forRule(NOSONAR_RULE_KEY); + + when(filters.accept(anyString(), any(ScannerReport.Issue.class))).thenReturn(true); + + boolean added = moduleIssues.initAndAddIssue(issue); + + assertThat(added).isTrue(); + verify(reportPublisher.getWriter()).appendComponentIssue(eq(file.batchId()), any()); + } + /** * Every rules and active rules has to be added in builders before creating ModuleIssues */ |