Skip to content

Commit 738f9b3

Browse files
maksim-grebeniuk-sonarsourcesonartech
authored andcommitted
SONARPY-3166 Support noqa as an alias to NOSONAR (#380)
GitOrigin-RevId: 18a8fbafd685ec49b2ef116858763c2d3e5aa183
1 parent 8fbddf3 commit 738f9b3

8 files changed

Lines changed: 262 additions & 79 deletions

File tree

its/plugin/it-python-plugin-test/src/test/java/com/sonar/python/it/plugin/NoSonarTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ void test_nosonar() {
5757
analyzeProject(createScanner(NO_SONAR_PROJECT_KEY, "projects/nosonar/nosonar-project"));
5858

5959
IssueListAssert.assertThat(issues(NO_SONAR_PROJECT_KEY))
60-
.hasSize(19)
60+
.hasSize(21)
6161
// basic no-sonar checks
6262
.containsIssue(1, "python:PrintStatementUsage")
6363
.containsIssue(2, "python:NoSonar").doesNotContainIssue(2, "python:PrintStatementUsage")

python-commons/src/main/java/org/sonar/plugins/python/nosonar/NoSonarIssueFilter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public boolean accept(FilterableIssue issue, IssueFilterChain chain) {
4747
var noSonarLineInfo = noSonarLineInfos.get(issueLine);
4848
var isNotFilteredOutByNoSonar = noSonarLineInfo == null || !noSonarLineInfo.suppressedRuleKeys().contains(issue.ruleKey().rule());
4949
if (!isNotFilteredOutByNoSonar) {
50-
LOG.debug("Filtering out issue in the componen with key: {} for rule: {} on line: {} based on the file NoSonar infos {}",
50+
LOG.debug("Filtering out issue in the component with key: {} for rule: {} on line: {} based on the file NoSonar infos {}",
5151
issueComponentKey,
5252
issue.ruleKey().rule(),
5353
issueLine,

python-commons/src/main/java/org/sonar/plugins/python/nosonar/NoSonarLineInfoCollector.java

Lines changed: 29 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,21 @@
1616
*/
1717
package org.sonar.plugins.python.nosonar;
1818

19-
import com.sonar.sslr.api.GenericTokenType;
2019
import java.util.ArrayDeque;
20+
import java.util.HashMap;
2121
import java.util.HashSet;
22-
import java.util.List;
2322
import java.util.Map;
2423
import java.util.Objects;
24+
import java.util.Optional;
2525
import java.util.Set;
2626
import java.util.concurrent.ConcurrentHashMap;
27-
import java.util.regex.Pattern;
2827
import java.util.stream.Collectors;
29-
import java.util.stream.Stream;
3028
import javax.annotation.Nullable;
3129
import org.slf4j.Logger;
3230
import org.slf4j.LoggerFactory;
3331
import org.sonar.api.scanner.ScannerSide;
32+
import org.sonar.plugins.python.api.nosonar.NoSonarInfoParser;
33+
import org.sonar.plugins.python.api.nosonar.NoSonarLineInfo;
3434
import org.sonar.plugins.python.api.tree.ExpressionStatement;
3535
import org.sonar.plugins.python.api.tree.FileInput;
3636
import org.sonar.plugins.python.api.tree.StringLiteral;
@@ -45,13 +45,11 @@ public class NoSonarLineInfoCollector {
4545

4646
private static final Logger LOG = LoggerFactory.getLogger(NoSonarLineInfoCollector.class);
4747

48-
public static final String NOSONAR_PATTERN_REGEX = "^#\\s*NOSONAR(?:\\(([^)]*)\\))?.*";
49-
50-
private final Pattern noSonarPattern;
48+
private final NoSonarInfoParser parser;
5149
private final Map<String, Map<Integer, NoSonarLineInfo>> componentKeyToNoSonarLineInfoMap;
5250

5351
public NoSonarLineInfoCollector() {
54-
this.noSonarPattern = Pattern.compile(NOSONAR_PATTERN_REGEX);
52+
parser = new NoSonarInfoParser();
5553
this.componentKeyToNoSonarLineInfoMap = new ConcurrentHashMap<>();
5654
}
5755

@@ -72,10 +70,10 @@ public Map<Integer, NoSonarLineInfo> get(String key) {
7270

7371
public Set<Integer> getLinesWithEmptyNoSonar(String key) {
7472
return get(key)
75-
.values()
73+
.entrySet()
7674
.stream()
77-
.filter(NoSonarLineInfo::isSuppressedRuleKeysEmpty)
78-
.map(NoSonarLineInfo::line)
75+
.filter(entry -> entry.getValue().isSuppressedRuleKeysEmpty())
76+
.map(Map.Entry::getKey)
7977
.collect(Collectors.toSet());
8078
}
8179

@@ -86,8 +84,8 @@ private Map<Integer, NoSonarLineInfo> scan(Tree element) {
8684
while (!stack.isEmpty()) {
8785
var currentElement = stack.pop();
8886
if (currentElement instanceof Token token) {
89-
visitToken(token)
90-
.forEach(info -> result.put(info.line(), info));
87+
var tokenResults = visitToken(token);
88+
result.putAll(tokenResults);
9189
}
9290

9391
currentElement.children()
@@ -98,66 +96,42 @@ private Map<Integer, NoSonarLineInfo> scan(Tree element) {
9896
return result;
9997
}
10098

101-
private List<NoSonarLineInfo> visitToken(Token token) {
102-
return token.trivia()
103-
.stream()
104-
.flatMap(trivia -> visitComment(trivia, token))
105-
.filter(Objects::nonNull)
106-
.toList();
99+
private Map<Integer, NoSonarLineInfo> visitToken(Token token) {
100+
var result = new HashMap<Integer, NoSonarLineInfo>();
101+
for (var trivia : token.trivia()) {
102+
parseComment(trivia)
103+
.ifPresent(info -> {
104+
var commentLine = trivia.token().line();
105+
calculateLines(commentLine, token).forEach(line -> result.put(line, info));
106+
});
107+
}
108+
109+
return result;
107110
}
108111

109-
private Stream<NoSonarLineInfo> visitComment(Trivia trivia, Token parentToken) {
110-
String commentLine = getContents(trivia.token().value());
111-
int line = trivia.token().line();
112-
if (containsNoSonarComment(commentLine)) {
113-
return parse(line, commentLine, parentToken);
114-
}
115-
return Stream.of();
112+
private Optional<NoSonarLineInfo> parseComment(Trivia trivia) {
113+
var commentString = getContents(trivia.token().value());
114+
return parser.parse(commentString);
116115
}
117116

118117
private static String getContents(String comment) {
119118
// Comment always starts with "#"
120119
return comment.substring(comment.indexOf('#'));
121120
}
122121

123-
private static boolean containsNoSonarComment(String commentLine) {
124-
return commentLine.trim().contains("NOSONAR");
125-
}
126-
127-
private Stream<NoSonarLineInfo> parse(int line, String noSonarCommentLine, Token parentToken) {
128-
var rules = parseNoSonarRules(noSonarCommentLine);
122+
private static Set<Integer> calculateLines(int commentLine, Token parentToken) {
129123
var lines = new HashSet<Integer>();
130-
lines.add(line);
124+
lines.add(commentLine);
131125

132126
if (parentToken.parent() instanceof ExpressionStatement expressionStatement
133127
&& !expressionStatement.expressions().isEmpty()
134128
&& expressionStatement.expressions().get(0) instanceof StringLiteral stringLiteral) {
135129
var firstLine = stringLiteral.firstToken().line();
136-
for (int i = firstLine; i < line + 1; i++) {
130+
for (int i = firstLine; i < commentLine + 1; i++) {
137131
lines.add(i);
138132
}
139133
}
140-
141-
return lines.stream().map(l -> new NoSonarLineInfo(l, rules));
142-
}
143-
144-
private Set<String> parseNoSonarRules(String noSonarCommentLine) {
145-
var rules = new HashSet<String>();
146-
var matcher = noSonarPattern.matcher(noSonarCommentLine);
147-
148-
if (matcher.matches()) {
149-
var contentInsideParentheses = matcher.group(1);
150-
if (contentInsideParentheses != null) {
151-
var ruleArray = contentInsideParentheses.split(",");
152-
for (var rule : ruleArray) {
153-
var trimmedRule = rule.trim();
154-
if (!trimmedRule.isEmpty()) {
155-
rules.add(trimmedRule);
156-
}
157-
}
158-
}
159-
}
160-
return rules;
134+
return lines;
161135
}
162136

163137
public String getSuppressedRuleIds(){

python-commons/src/test/java/org/sonar/plugins/python/nosonar/NoSonarIssueFilterTest.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.sonar.api.rule.RuleKey;
2929
import org.sonar.api.scan.issue.filter.FilterableIssue;
3030
import org.sonar.api.scan.issue.filter.IssueFilterChain;
31+
import org.sonar.plugins.python.api.nosonar.NoSonarLineInfo;
3132

3233
class NoSonarIssueFilterTest {
3334

@@ -57,12 +58,12 @@ void test(Map<Integer, NoSonarLineInfo> noSonarInfos, @Nullable Integer line, bo
5758

5859
private static Stream<Arguments> provideFilterParameters() {
5960
return Stream.of(
60-
Arguments.of(Map.of(1, new NoSonarLineInfo(1, Set.of("my_rule"))), 1, true, false),
61-
Arguments.of(Map.of(1, new NoSonarLineInfo(1, Set.of("other_rule"))), 1, true, true),
62-
Arguments.of(Map.of(1, new NoSonarLineInfo(1, Set.of())), 1, true, true),
63-
Arguments.of(Map.of(2, new NoSonarLineInfo(2, Set.of("my_rule"))), 1, true, true),
64-
Arguments.of(Map.of(1, new NoSonarLineInfo(1, Set.of("my_rule"))), null, true, true),
65-
Arguments.of(Map.of(1, new NoSonarLineInfo(1, Set.of("other_rule"))), 1, false, false),
61+
Arguments.of(Map.of(1, new NoSonarLineInfo(Set.of("my_rule"))), 1, true, false),
62+
Arguments.of(Map.of(1, new NoSonarLineInfo(Set.of("other_rule"))), 1, true, true),
63+
Arguments.of(Map.of(1, new NoSonarLineInfo(Set.of())), 1, true, true),
64+
Arguments.of(Map.of(2, new NoSonarLineInfo(Set.of("my_rule"))), 1, true, true),
65+
Arguments.of(Map.of(1, new NoSonarLineInfo(Set.of("my_rule"))), null, true, true),
66+
Arguments.of(Map.of(1, new NoSonarLineInfo(Set.of("other_rule"))), 1, false, false),
6667
Arguments.of(Map.of(), 1, false, false)
6768
);
6869
}

python-commons/src/test/java/org/sonar/plugins/python/nosonar/NoSonarLineInfoCollectorTest.java

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.junit.jupiter.params.ParameterizedTest;
2424
import org.junit.jupiter.params.provider.Arguments;
2525
import org.junit.jupiter.params.provider.MethodSource;
26+
import org.sonar.plugins.python.api.nosonar.NoSonarLineInfo;
2627
import org.sonar.python.parser.PythonParser;
2728
import org.sonar.python.tree.PythonTreeMaker;
2829

@@ -37,65 +38,87 @@ void collector_test(String code, Map<Integer, NoSonarLineInfo> expectedLineInfos
3738

3839
var collector = new NoSonarLineInfoCollector();
3940
collector.collect("foo.py", fileInput);
40-
4141
Assertions.assertThat(collector.get("foo.py")).isEqualTo(expectedLineInfos);
4242
Assertions.assertThat(collector.getLinesWithEmptyNoSonar("foo.py")).isEqualTo(expectedEmptyNoSonarLines);
43+
Assertions.assertThat(collector.getSuppressedRuleIds()).isEqualTo(expectedSuppressedRuleIds);
4344
}
4445

4546
private static Stream<Arguments> provideCollectorParameters() {
4647
return Stream.of(
4748
Arguments.of("""
4849
a = 1 # NOSONAR(something)
4950
""",
50-
Map.of(1, new NoSonarLineInfo(1, Set.of("something"))),
51+
Map.of(1, new NoSonarLineInfo(Set.of("something"))),
5152
Set.of(),
5253
"something"
5354
),
5455
Arguments.of("""
5556
a = 1 # NOSONAR(one, two) some text
5657
""",
57-
Map.of(1, new NoSonarLineInfo(1, Set.of("one", "two"))),
58+
Map.of(1, new NoSonarLineInfo(Set.of("one", "two"))),
5859
Set.of(),
5960
"one,two"
6061
),
6162
Arguments.of("""
6263
a = 1 # NOSONAR()
6364
""",
64-
Map.of(1, new NoSonarLineInfo(1, Set.of())),
65+
Map.of(1, new NoSonarLineInfo(Set.of())),
6566
Set.of(1),
6667
""
6768
),
6869
Arguments.of("""
6970
a = 1 # NOSONAR(something,)
7071
""",
71-
Map.of(1, new NoSonarLineInfo(1, Set.of("something"))),
72+
Map.of(1, new NoSonarLineInfo(Set.of("something"))),
73+
Set.of(),
74+
"something"
75+
),
76+
Arguments.of("""
77+
a = 1 # NOSONAR(something,) adsgvnbdfa;l
78+
""",
79+
Map.of(1, new NoSonarLineInfo(Set.of("something"))),
7280
Set.of(),
7381
"something"
7482
),
7583
Arguments.of("""
7684
a = 1 # NOSONAR
7785
""",
78-
Map.of(1, new NoSonarLineInfo(1, Set.of())),
86+
Map.of(1, new NoSonarLineInfo(Set.of())),
7987
Set.of(1),
8088
""
8189
),
8290
Arguments.of("""
8391
a = 1 # NOSONAR some text
8492
""",
85-
Map.of(1, new NoSonarLineInfo(1, Set.of())),
93+
Map.of(1, new NoSonarLineInfo(Set.of())),
8694
Set.of(1),
8795
""
8896
),
8997
Arguments.of("a = 1 # NOSONAR some text",
90-
Map.of(1, new NoSonarLineInfo(1, Set.of())),
98+
Map.of(1, new NoSonarLineInfo(Set.of())),
9199
Set.of(1),
92100
""
93101
),
94102
Arguments.of("# NOSONAR some text",
95-
Map.of(1, new NoSonarLineInfo(1, Set.of())),
103+
Map.of(1, new NoSonarLineInfo(Set.of())),
96104
Set.of(1),
97105
""
98106
),
107+
Arguments.of("# noqa: some text",
108+
Map.of(1, new NoSonarLineInfo(Set.of("some"))),
109+
Set.of(),
110+
"some"
111+
),
112+
Arguments.of("# noqa: a,b",
113+
Map.of(1, new NoSonarLineInfo(Set.of("a", "b"))),
114+
Set.of(),
115+
"a,b"
116+
),
117+
Arguments.of("# noqa: a, b",
118+
Map.of(1, new NoSonarLineInfo(Set.of("a"))),
119+
Set.of(),
120+
"a"
121+
),
99122
Arguments.of("""
100123
""\"
101124
1
@@ -104,11 +127,11 @@ private static Stream<Arguments> provideCollectorParameters() {
104127
""\" # NOSONAR
105128
""",
106129
Map.of(
107-
1, new NoSonarLineInfo(1, Set.of()),
108-
2, new NoSonarLineInfo(2, Set.of()),
109-
3, new NoSonarLineInfo(3, Set.of()),
110-
4, new NoSonarLineInfo(4, Set.of()),
111-
5, new NoSonarLineInfo(5, Set.of())
130+
1, new NoSonarLineInfo(Set.of()),
131+
2, new NoSonarLineInfo(Set.of()),
132+
3, new NoSonarLineInfo(Set.of()),
133+
4, new NoSonarLineInfo(Set.of()),
134+
5, new NoSonarLineInfo(Set.of())
112135
),
113136
Set.of(1, 2, 3, 4, 5),
114137
""

0 commit comments

Comments
 (0)