Skip to content

Commit 2b591e3

Browse files
SONARPY-983 Rule S6395: Non-capturing groups without quantifier should not be used (#1109)
1 parent e935629 commit 2b591e3

File tree

8 files changed

+169
-1
lines changed

8 files changed

+169
-1
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
'project:biopython/Bio/SearchIO/ExonerateIO/exonerate_text.py':[
3+
32,
4+
32,
5+
36,
6+
36,
7+
],
8+
'project:buildbot-0.8.6p1/buildbot/changes/mail.py':[
9+
212,
10+
212,
11+
214,
12+
214,
13+
],
14+
'project:django-2.2.3/django/db/backends/sqlite3/introspection.py':[
15+
204,
16+
204,
17+
],
18+
'project:django-2.2.3/django/utils/text.py':[
19+
308,
20+
],
21+
'project:django-2.2.3/django/utils/translation/template.py':[
22+
20,
23+
20,
24+
29,
25+
29,
26+
32,
27+
32,
28+
],
29+
'project:mypy-0.782/test-data/stdlib-samples/3.2/textwrap.py':[
30+
338,
31+
],
32+
'project:tornado-2.3/demos/appengine/markdown.py':[
33+
721,
34+
],
35+
'project:tornado-2.3/demos/blog/markdown.py':[
36+
721,
37+
],
38+
'project:tornado-2.3/tornado/escape.py':[
39+
235,
40+
235,
41+
235,
42+
],
43+
}

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
@@ -64,6 +64,7 @@
6464
import org.sonar.python.checks.regex.ReluctantQuantifierWithEmptyContinuationCheck;
6565
import org.sonar.python.checks.regex.SingleCharacterAlternationCheck;
6666
import org.sonar.python.checks.regex.StringReplaceCheck;
67+
import org.sonar.python.checks.regex.UnquantifiedNonCapturingGroupCheck;
6768

6869
public final class CheckList {
6970

@@ -245,6 +246,7 @@ public static Iterable<Class> getChecks() {
245246
RegexComplexityCheck.class,
246247
RegexLookaheadCheck.class,
247248
UndefinedNameAllPropertyCheck.class,
249+
UnquantifiedNonCapturingGroupCheck.class,
248250
UnreachableExceptCheck.class,
249251
UnreadPrivateAttributesCheck.class,
250252
UnreadPrivateInnerClassesCheck.class,
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-2022 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.UnquantifiedNonCapturingGroupFinder;
26+
27+
@Rule(key = "S6395")
28+
public class UnquantifiedNonCapturingGroupCheck extends AbstractRegexCheck {
29+
30+
@Override
31+
public void checkRegex(RegexParseResult regexParseResult, CallExpression regexFunctionCall) {
32+
new UnquantifiedNonCapturingGroupFinder(this::addIssue).visit(regexParseResult);
33+
}
34+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<p>Sub-patterns can be wrapped by parentheses to build a group. This enables to restrict alternations, back reference the group or apply a quantifier
2+
to the sub-pattern.</p>
3+
<p>If this group should not be part of the match result or if no reference to this group is required, a non-capturing group can be created by adding
4+
<code>:?</code> behind the opening parenthesis.</p>
5+
<p>However, if this non-capturing group does not have a quantifier, or does not wrap an alternation, then imaging this group is redundant.</p>
6+
<h2>Noncompliant Code Example</h2>
7+
<pre>
8+
r"(?:number)\d{2}"
9+
</pre>
10+
<h2>Compliant Solution</h2>
11+
<pre>
12+
r"number\d{2}"
13+
r"(?:number)?\d{2}"
14+
</pre>
15+
<h2>Exceptions</h2>
16+
<p>This rule does not report an issue if the non-capturing group is an alternation.</p>
17+
<pre>
18+
r"(?:number|string)"
19+
</pre>
20+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"title": "Non-capturing groups without quantifier should not be used",
3+
"type": "CODE_SMELL",
4+
"status": "ready",
5+
"remediation": {
6+
"func": "Constant\/Issue",
7+
"constantCost": "5min"
8+
},
9+
"tags": [
10+
"regex"
11+
],
12+
"defaultSeverity": "Major",
13+
"ruleSpecification": "RSPEC-6395",
14+
"sqKey": "S6395",
15+
"scope": "All",
16+
"quickfix": "unknown"
17+
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@
153153
"S6019",
154154
"S6035",
155155
"S6323",
156-
"S6331"
156+
"S6331",
157+
"S6395"
157158
]
158159
}
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-2022 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 UnquantifiedNonCapturingGroupCheckTest {
26+
27+
@Test
28+
public void test() {
29+
PythonCheckVerifier.verify("src/test/resources/checks/regex/unquantifiedNonCapturingGroupCheck.py", new UnquantifiedNonCapturingGroupCheck());
30+
}
31+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import re
2+
3+
4+
def non_compliant(input):
5+
re.match(r"(?:number)", input) # Noncompliant {{Unwrap this unnecessarily grouped subpattern.}}
6+
# ^^^^^^^^^^
7+
re.match(r"(?:number)\d{2}", input) # Noncompliant
8+
re.match(r"(?:number(?:two){2})", input) # Noncompliant
9+
# ^^^^^^^^^^^^^^^^^^^^
10+
re.match(r"(?:number(?:two)){2}", input) # Noncompliant
11+
# ^^^^^^^
12+
re.match(r"foo(?:number)bar", input) # Noncompliant
13+
re.match(r"(?:)", input) # Noncompliant
14+
15+
16+
def compliant(input):
17+
re.match(r"(?:number)?+", input)
18+
re.match(r"number\d{2}", input)
19+
re.match(r"(?:number)?\d{2}", input)
20+
re.match(r"(?:number|string)", input)

0 commit comments

Comments
 (0)