Skip to content

Commit 0373147

Browse files
maksim-grebeniuk-sonarsourcesonartech
authored andcommitted
SONARPY-3084 Filter issues after the analysis if there are on a line with # NOSONAR(SXXXX) (#372)
GitOrigin-RevId: 7172c20adcbc6732f6c8f9aaeeec4e1bbab32954
1 parent 96a3fe5 commit 0373147

4 files changed

Lines changed: 127 additions & 2 deletions

File tree

python-commons/src/main/java/org/sonar/plugins/python/PythonExtensions.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.sonar.plugins.python.indexer.SonarLintPythonIndexer;
4040
import org.sonar.plugins.python.mypy.MypyRulesDefinition;
4141
import org.sonar.plugins.python.mypy.MypySensor;
42+
import org.sonar.plugins.python.nosonar.NoSonarIssueFilter;
4243
import org.sonar.plugins.python.nosonar.NoSonarLineInfoCollector;
4344
import org.sonar.plugins.python.pylint.PylintRulesDefinition;
4445
import org.sonar.plugins.python.pylint.PylintSensor;
@@ -69,6 +70,7 @@ private PythonExtensions() {
6970
public static void addCommonExtensions(Plugin.Context context) {
7071
context.addExtensions(
7172
NoSonarLineInfoCollector.class,
73+
NoSonarIssueFilter.class,
7274
buildPythonSuffix(),
7375
buildIpynbPythonSuffix(),
7476
buildPythonVersion(),
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2025 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonar.plugins.python.nosonar;
18+
19+
import org.slf4j.Logger;
20+
import org.slf4j.LoggerFactory;
21+
import org.sonar.api.scan.issue.filter.FilterableIssue;
22+
import org.sonar.api.scan.issue.filter.IssueFilter;
23+
import org.sonar.api.scan.issue.filter.IssueFilterChain;
24+
import org.sonar.api.scanner.ScannerSide;
25+
import org.sonarsource.api.sonarlint.SonarLintSide;
26+
27+
@SonarLintSide
28+
@ScannerSide
29+
public class NoSonarIssueFilter implements IssueFilter {
30+
private static final Logger LOG = LoggerFactory.getLogger(NoSonarIssueFilter.class);
31+
32+
private final NoSonarLineInfoCollector noSonarLineInfoCollector;
33+
34+
public NoSonarIssueFilter(NoSonarLineInfoCollector noSonarLineInfoCollector) {
35+
this.noSonarLineInfoCollector = noSonarLineInfoCollector;
36+
}
37+
38+
@Override
39+
public boolean accept(FilterableIssue issue, IssueFilterChain chain) {
40+
var issueLine = issue.line();
41+
if (issueLine == null) {
42+
return chain.accept(issue);
43+
}
44+
var issueComponentKey = issue.componentKey();
45+
46+
var noSonarLineInfos = noSonarLineInfoCollector.get(issueComponentKey);
47+
var noSonarLineInfo = noSonarLineInfos.get(issueLine);
48+
var isNotFilteredOutByNoSonar = noSonarLineInfo == null || !noSonarLineInfo.suppressedRuleKeys().contains(issue.ruleKey().rule());
49+
if (!isNotFilteredOutByNoSonar) {
50+
LOG.debug("Filtering out issue in the componen with key: {} for rule: {} on line: {} based on the file NoSonar infos {}",
51+
issueComponentKey,
52+
issue.ruleKey().rule(),
53+
issueLine,
54+
noSonarLineInfos.values());
55+
}
56+
return isNotFilteredOutByNoSonar && chain.accept(issue);
57+
}
58+
}

python-commons/src/test/java/org/sonar/plugins/python/PythonExtensionsTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@ class PythonExtensionsTest {
4343
void testGetExtensions() {
4444
Version v79 = Version.create(7, 9);
4545
SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(v79, SonarQubeSide.SERVER, SonarEdition.DEVELOPER);
46-
assertThat(extensions(runtime)).hasSize(41);
46+
assertThat(extensions(runtime)).hasSize(42);
4747
assertThat(extensions(runtime)).contains(AnalysisWarningsWrapper.class);
4848
assertThat(extensions(SonarRuntimeImpl.forSonarLint(v79)))
49-
.hasSize(21)
49+
.hasSize(22)
5050
.contains(SonarLintCache.class);
5151
}
5252

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2025 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonar.plugins.python.nosonar;
18+
19+
import java.util.Map;
20+
import java.util.Set;
21+
import java.util.stream.Stream;
22+
import javax.annotation.Nullable;
23+
import org.assertj.core.api.Assertions;
24+
import org.junit.jupiter.params.ParameterizedTest;
25+
import org.junit.jupiter.params.provider.Arguments;
26+
import org.junit.jupiter.params.provider.MethodSource;
27+
import org.mockito.Mockito;
28+
import org.sonar.api.rule.RuleKey;
29+
import org.sonar.api.scan.issue.filter.FilterableIssue;
30+
import org.sonar.api.scan.issue.filter.IssueFilterChain;
31+
32+
class NoSonarIssueFilterTest {
33+
34+
@ParameterizedTest
35+
@MethodSource("provideFilterParameters")
36+
void test(
37+
String componentKey, Map<Integer, NoSonarLineInfo> noSonarInfos, @Nullable Integer line, String ruleRepo, String ruleKey, boolean filterChainAcceptResult, boolean expectedResult) {
38+
39+
var collector = Mockito.mock(NoSonarLineInfoCollector.class);
40+
Mockito.when(collector.get(componentKey))
41+
.thenReturn(noSonarInfos);
42+
var filter = new NoSonarIssueFilter(collector);
43+
44+
var issue = Mockito.mock(FilterableIssue.class);
45+
Mockito.when(issue.componentKey()).thenReturn(componentKey);
46+
Mockito.when(issue.line()).thenReturn(line);
47+
Mockito.when(issue.ruleKey()).thenReturn(RuleKey.of(ruleRepo, ruleKey));
48+
49+
var filterChain = Mockito.mock(IssueFilterChain.class);
50+
Mockito.when(filterChain.accept(issue)).thenReturn(filterChainAcceptResult);
51+
52+
var result = filter.accept(issue, filterChain);
53+
Assertions.assertThat(result).isEqualTo(expectedResult);
54+
}
55+
56+
private static Stream<Arguments> provideFilterParameters() {
57+
return Stream.of(
58+
Arguments.of("foo.py", Map.of(1, new NoSonarLineInfo(1, Set.of("my_rule"))), 1, "my_repo", "my_rule", true, false),
59+
Arguments.of("foo.py", Map.of(1, new NoSonarLineInfo(1, Set.of("other_rule"))), 1, "my_repo", "my_rule", true, true),
60+
Arguments.of("foo.py", Map.of(2, new NoSonarLineInfo(2, Set.of("my_rule"))), 1, "my_repo", "my_rule", true, true),
61+
Arguments.of("foo.py", Map.of(1, new NoSonarLineInfo(1, Set.of("my_rule"))), null, "my_repo", "my_rule", true, true),
62+
Arguments.of("foo.py", Map.of(1, new NoSonarLineInfo(1, Set.of("other_rule"))), 1, "my_repo", "my_rule", false, false)
63+
);
64+
}
65+
}

0 commit comments

Comments
 (0)