Skip to content

Commit ab986be

Browse files
SONARPY-979 Rule S6326: Regular expressions should not contain multiple spaces (#1106)
1 parent c457676 commit ab986be

File tree

8 files changed

+167
-0
lines changed

8 files changed

+167
-0
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
'project:biopython/Bio/PDB/parse_pdb_header.py':[
3+
38,
4+
39,
5+
295,
6+
296,
7+
],
8+
'project:biopython/Bio/PopGen/GenePop/Controller.py':[
9+
922,
10+
922,
11+
],
12+
'project:buildbot-0.8.6p1/buildbot/changes/mail.py':[
13+
478,
14+
],
15+
'project:numpy-1.16.4/numpy/distutils/from_template.py':[
16+
56,
17+
58,
18+
],
19+
'project:tensorflow/python/autograph/pyct/error_utils_test.py':[
20+
101,
21+
120,
22+
],
23+
'project:tensorflow/python/ops/map_fn.py':[
24+
648,
25+
],
26+
'project:tensorflow/tools/docs/parser.py':[
27+
590,
28+
],
29+
'project:tornado-2.3/demos/appengine/markdown.py':[
30+
1347,
31+
],
32+
'project:tornado-2.3/demos/blog/markdown.py':[
33+
1347,
34+
],
35+
}

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
@@ -57,6 +57,7 @@
5757
import org.sonar.python.checks.regex.GraphemeClustersInClassesCheck;
5858
import org.sonar.python.checks.regex.ImpossibleBoundariesCheck;
5959
import org.sonar.python.checks.regex.InvalidRegexCheck;
60+
import org.sonar.python.checks.regex.MultipleWhitespaceCheck;
6061
import org.sonar.python.checks.regex.RedundantRegexAlternativesCheck;
6162
import org.sonar.python.checks.regex.RegexComplexityCheck;
6263
import org.sonar.python.checks.regex.RegexLookaheadCheck;
@@ -189,6 +190,7 @@ public static Iterable<Class> getChecks() {
189190
MissingNewlineAtEndOfFileCheck.class,
190191
ModifiedParameterValueCheck.class,
191192
ModuleNameCheck.class,
193+
MultipleWhitespaceCheck.class,
192194
NeedlessPassCheck.class,
193195
NestedConditionalExpressionCheck.class,
194196
NestedControlFlowDepthCheck.class,
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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 java.util.Optional;
23+
import java.util.regex.Pattern;
24+
import org.sonar.check.Rule;
25+
import org.sonar.plugins.python.api.tree.CallExpression;
26+
import org.sonarsource.analyzer.commons.regex.RegexParseResult;
27+
import org.sonarsource.analyzer.commons.regex.finders.MultipleWhitespaceFinder;
28+
29+
@Rule(key = "S6326")
30+
public class MultipleWhitespaceCheck extends AbstractRegexCheck {
31+
32+
@Override
33+
public void checkRegex(RegexParseResult regexParseResult, CallExpression regexFunctionCall) {
34+
Optional.ofNullable(regexFunctionCall.calleeSymbol())
35+
.flatMap(symbol -> Optional.ofNullable(symbol.fullyQualifiedName()))
36+
.filter(fqn -> lookedUpFunctions().containsKey(fqn))
37+
.filter(fqn -> !regexParseResult.getResult().activeFlags().contains(Pattern.COMMENTS))
38+
.ifPresent(fqn -> new MultipleWhitespaceFinder(this::addIssue).visit(regexParseResult));
39+
}
40+
}
41+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<p>Multiple spaces in a regular expression can make it hard to tell how many spaces should be matched. It’s more readable to use only one space and
2+
then indicate with a quantifier how many spaces are expected.</p>
3+
<h2>Noncompliant Code Example</h2>
4+
<pre>
5+
r"Hello, world!"
6+
</pre>
7+
<h2>Compliant Solution</h2>
8+
<pre>
9+
r"Hello, {3}world!"
10+
</pre>
11+
<h2>Exceptions</h2>
12+
<p>If the extended modifier <code>re.X</code> or <code>re.VERBOSE</code> is set, whitespaces are ignored. In this case no issue should be triggered,
13+
because the whitespaces may be intended to improve readability.</p>
14+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"title": "Regular expressions should not contain multiple spaces",
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-6326",
14+
"sqKey": "S6326",
15+
"scope": "Main",
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
@@ -152,6 +152,7 @@
152152
"S6002",
153153
"S6019",
154154
"S6323",
155+
"S6326",
155156
"S6331",
156157
"S6035",
157158
"S6395",
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
26+
public class MultipleWhitespaceCheckTest {
27+
28+
@Test
29+
public void test() {
30+
PythonCheckVerifier.verify("src/test/resources/checks/regex/multipleWhitespaceCheck.py", new MultipleWhitespaceCheck());
31+
}
32+
33+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import re
2+
3+
4+
def non_compliant():
5+
input = "Hello world! Bob is a Bird... Bob is a Plane... Bob is Superman!"
6+
changed = re.match(r"Hello, world!", input) # Noncompliant {{Replace spaces with quantifier `{3}`.}}
7+
# ^^
8+
changed = re.match(r"Hello, world!", input) # Noncompliant {{Replace spaces with quantifier `{2}`.}}
9+
# ^
10+
changed = re.match(r"Hello, world! ", input) # Noncompliant {{Replace spaces with quantifier `{5}`.}}
11+
# ^^^^
12+
changed = re.match(r"Hello, world! ", input) # Noncompliant 2
13+
14+
15+
def compliant():
16+
input = "Hello world! Bob is a Bird... Bob is a Plane... Bob is Superman!"
17+
changed = re.match(r"\r\n|\r", input, re.MS)
18+
changed = re.match(r"[ ]", input)
19+
changed = re.compile(r"[ ]", re.X)
20+
changed = re.match(r"Hello, world!\t\t\t\t", input)
21+
changed = re.match(r"Hello , world!", input)
22+
changed = re.match(r"'Hello, {3}world!'", input)
23+
changed = re.match(r'Hello, world!', flags=re.VERBOSE)
24+
changed = re.compile(r'Hello, world!', flags=re.X)

0 commit comments

Comments
 (0)