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;
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);
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
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();
}
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) {
return indexedFile.uri();
}
+ public void noSonarAt(Set<Integer> noSonarLines) {
+ this.noSonarLines.addAll(noSonarLines);
+ }
+
+ public boolean hasNoSonarAt(int line) {
+ return this.noSonarLines.contains(line);
+ }
+
}
*/
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;
- }
}
*/
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();
}
}
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;
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
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());
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;
// issues
ModuleIssues.class,
- NoSonarFilter.class,
// issue exclusions
IssueInclusionPatternInitializer.class,
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;
IssueCache.class,
DefaultProjectIssues.class,
IssueTransition.class,
+ NoSonarFilter.class,
// metrics
DefaultMetricFinder.class,
*/
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;
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;
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
*/