Browse Source

SONAR-11465 NoSonarFilter moved to project level

tags/7.6
Julien HENRY 5 years ago
parent
commit
4a659a05cf

+ 14
- 3
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputFile.java View File

@@ -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);
}

}

+ 7
- 36
sonar-plugin-api/src/main/java/org/sonar/api/issue/NoSonarFilter.java View File

@@ -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;
}
}

+ 10
- 59
sonar-plugin-api/src/test/java/org/sonar/api/issue/NoSonarFilterTest.java View File

@@ -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();
}
}

+ 14
- 0
sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/ModuleIssues.java View File

@@ -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());

+ 0
- 2
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java View File

@@ -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,

+ 2
- 0
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java View File

@@ -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,

+ 42
- 0
sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/ModuleIssuesTest.java View File

@@ -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
*/

Loading…
Cancel
Save