Skip to content

Commit 91731ea

Browse files
SONARPY-886 Rule S5855 Regex alternatives should not be redundant (#962)
* SONARPY-886 Rule S5855 Regex alternatives should not be redundant * Add code coverage * Remove POSIX class test cases * Use new analyzer-commons version to do not support POSIX for python
1 parent 73fc960 commit 91731ea

File tree

11 files changed

+155
-11
lines changed

11 files changed

+155
-11
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@
9191
<mockito.version>3.9.0</mockito.version>
9292
<sonar.version>8.9.0.43852</sonar.version>
9393
<sonar.orchestrator.version>3.35.1.2719</sonar.orchestrator.version>
94-
<sonar-analyzer-commons.version>1.21.0.804</sonar-analyzer-commons.version>
94+
<sonar-analyzer-commons.version>1.21.0.805</sonar-analyzer-commons.version>
9595
<sonarlint-core.version>6.0.0.32513</sonarlint-core.version>
9696
<sslr.version>1.23</sslr.version>
9797
<protobuf.version>3.17.3</protobuf.version>

python-checks/src/main/java/org/sonar/python/checks/CheckList.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import org.sonar.python.checks.hotspots.UnsafeHttpMethodsCheck;
5151
import org.sonar.python.checks.hotspots.UnverifiedHostnameCheck;
5252
import org.sonar.python.checks.regex.EmptyStringRepetitionCheck;
53+
import org.sonar.python.checks.regex.RedundantRegexAlternativesCheck;
5354
import org.sonar.python.checks.regex.StringReplaceCheck;
5455

5556
public final class CheckList {
@@ -184,6 +185,7 @@ public static Iterable<Class> getChecks() {
184185
PubliclyWritableDirectoriesCheck.class,
185186
RaiseOutsideExceptCheck.class,
186187
RedundantJumpCheck.class,
188+
RedundantRegexAlternativesCheck.class,
187189
RegexCheck.class,
188190
ReturnAndYieldInOneFunctionCheck.class,
189191
ReturnYieldOutsideFunctionCheck.class,

python-checks/src/main/java/org/sonar/python/checks/regex/AbstractRegexCheck.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.sonar.plugins.python.api.tree.RegularArgument;
3434
import org.sonar.plugins.python.api.tree.StringLiteral;
3535
import org.sonar.plugins.python.api.tree.Tree;
36+
import org.sonar.python.regex.PythonRegexIssueLocation;
3637
import org.sonar.python.regex.RegexContext;
3738
import org.sonar.python.tree.TreeUtils;
3839
import org.sonarsource.analyzer.commons.regex.RegexIssueLocation;
@@ -95,8 +96,8 @@ private static Optional<StringLiteral> patternArgStringLiteral(CallExpression re
9596

9697
public void addIssue(RegexSyntaxElement regexTree, String message, @Nullable Integer cost, List<RegexIssueLocation> secondaries) {
9798
if (reportedRegexTrees.add(regexTree)) {
98-
regexContext.addIssue(regexTree, message);
99-
// TODO: Add secondary location to the issue SONARPY-886
99+
PreciseIssue issue = regexContext.addIssue(regexTree, message);
100+
secondaries.stream().map(PythonRegexIssueLocation::preciseLocation).forEach(issue::secondary);
100101
// TODO: Add cost to the issue SONARPY-893
101102
}
102103
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2021 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 GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.python.checks.regex;
21+
22+
import org.sonar.check.Rule;
23+
import org.sonar.plugins.python.api.tree.CallExpression;
24+
import org.sonarsource.analyzer.commons.regex.RegexParseResult;
25+
import org.sonarsource.analyzer.commons.regex.finders.RedundantRegexAlternativesFinder;
26+
27+
@Rule(key = "S5855")
28+
public class RedundantRegexAlternativesCheck extends AbstractRegexCheck{
29+
30+
@Override
31+
public void checkRegex(RegexParseResult regexParseResult, CallExpression regexFunctionCall) {
32+
new RedundantRegexAlternativesFinder(this::addIssue).visit(regexParseResult);
33+
}
34+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<p>If an alternative in a regular expression only matches things that are already matched by another alternative, that alternative is redundant and
2+
serves no purpose.</p>
3+
<p>In the best case this means that the offending subpattern is merely redundant and should be removed. In the worst case it’s a sign that this regex
4+
does not match what it was intended to match and should be reworked.</p>
5+
<h2>Noncompliant Code Example</h2>
6+
<pre>
7+
r"[ab]|a" # The "|a" is redundant because "[ab]" already matches "a"
8+
r".*|a" # .* matches everything, so any other alternative is redundant
9+
</pre>
10+
<h2>Compliant Solution</h2>
11+
<pre>
12+
r"[ab]"
13+
r".*"
14+
</pre>
15+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"title": "Regex alternatives should not be redundant",
3+
"type": "BUG",
4+
"status": "ready",
5+
"remediation": {
6+
"func": "Constant\/Issue",
7+
"constantCost": "5min"
8+
},
9+
"tags": [
10+
"regex"
11+
],
12+
"defaultSeverity": "Major",
13+
"ruleSpecification": "RSPEC-5855",
14+
"sqKey": "S5855",
15+
"scope": "All",
16+
"quickfix": "unknown"
17+
}

python-checks/src/main/resources/org/sonar/l10n/py/rules/python/Sonar_way_profile.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@
134134
"S5807",
135135
"S5828",
136136
"S5842",
137+
"S5855",
137138
"S5864",
138139
"S5886",
139140
"S5890"
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2021 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 GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.python.checks.regex;
21+
22+
import org.junit.Test;
23+
import org.sonar.python.checks.utils.PythonCheckVerifier;
24+
25+
public class RedundantRegexAlternativesCheckTest {
26+
27+
@Test
28+
public void test() {
29+
PythonCheckVerifier.verify("src/test/resources/checks/regex/redundantRegexAlternativesCheckTest.py", new RedundantRegexAlternativesCheck());
30+
}
31+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import re
2+
3+
4+
def non_compliant(input):
5+
re.match(r"a|[ab]", input) # Noncompliant {{Remove or rework this redundant alternative.}}
6+
# ^ ^^^^< {{Alternative to keep}}
7+
re.match(r".|a", input) # Noncompliant
8+
re.match(r"a|.", input) # Noncompliant
9+
re.match(r"(.)|(a)", input) # Noncompliant
10+
re.match(r"a|b|bc?", input) # Noncompliant
11+
re.match(r"a|b|bc*", input) # Noncompliant
12+
re.match(r"a|b|bb*", input) # Noncompliant
13+
re.match(r"a|b|a|b|a|b|a|b", input) # Noncompliant 2
14+
re.match(r"[1-2]|[1-4]|[1-8]|[1-3]", input) # Noncompliant
15+
re.match(r"1|[1-2]", input) # Noncompliant
16+
17+
18+
def compliant(input):
19+
re.match(r"(a)|(.)", input) # Compliant
20+
re.match(r"a|(.)", input) # Compliant
21+
re.match(r"(a)|.", input) # Compliant
22+
re.match(r"a|b|bc+", input) # Compliant
23+
re.match(r"|a", input) # Compliant
24+
re.match(r"[ab]", input) # Compliant
25+
re.match(r".*", input) # Compliant

python-frontend/src/main/java/org/sonar/python/regex/PythonRegexIssueLocation.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.List;
2424
import java.util.Objects;
2525
import org.sonar.plugins.python.api.IssueLocation;
26+
import org.sonarsource.analyzer.commons.regex.RegexIssueLocation;
2627
import org.sonarsource.analyzer.commons.regex.ast.IndexRange;
2728
import org.sonarsource.analyzer.commons.regex.ast.RegexSyntaxElement;
2829

@@ -32,6 +33,10 @@ private PythonRegexIssueLocation() {
3233

3334
}
3435

36+
public static IssueLocation preciseLocation(RegexIssueLocation regexIssueLocation) {
37+
return preciseLocation(regexIssueLocation.syntaxElements(), regexIssueLocation.message());
38+
}
39+
3540
public static IssueLocation preciseLocation(RegexSyntaxElement syntaxElement, String message) {
3641
return preciseLocation(Collections.singletonList(syntaxElement), message);
3742
}

0 commit comments

Comments
 (0)