Skip to content

Commit e26eca1

Browse files
SONARPY-892 Rule S6002 Regex lookahead assertions should not be contradictory (#965)
1 parent e1ef98e commit e26eca1

File tree

7 files changed

+122
-0
lines changed

7 files changed

+122
-0
lines changed

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
@@ -51,6 +51,7 @@
5151
import org.sonar.python.checks.hotspots.UnverifiedHostnameCheck;
5252
import org.sonar.python.checks.regex.EmptyStringRepetitionCheck;
5353
import org.sonar.python.checks.regex.RedundantRegexAlternativesCheck;
54+
import org.sonar.python.checks.regex.RegexLookaheadCheck;
5455
import org.sonar.python.checks.regex.ReluctantQuantifierWithEmptyContinuationCheck;
5556
import org.sonar.python.checks.regex.StringReplaceCheck;
5657

@@ -215,6 +216,7 @@ public static Iterable<Class> getChecks() {
215216
TrailingCommentCheck.class,
216217
TrailingWhitespaceCheck.class,
217218
ReferencedBeforeAssignmentCheck.class,
219+
RegexLookaheadCheck.class,
218220
UndefinedNameAllPropertyCheck.class,
219221
UnreachableExceptCheck.class,
220222
UnreadPrivateAttributesCheck.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-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.FailingLookaheadFinder;
26+
27+
@Rule(key = "S6002")
28+
public class RegexLookaheadCheck extends AbstractRegexCheck{
29+
30+
@Override
31+
public void checkRegex(RegexParseResult regexParseResult, CallExpression regexFunctionCall) {
32+
new FailingLookaheadFinder(this::addIssue, regexParseResult.getFinalState()).visit(regexParseResult);
33+
}
34+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<p>Lookahead assertions are a regex feature that makes it possible to look ahead in the input without consuming it. It is often used at the end of
2+
regular expressions to make sure that substrings only match when they are followed by a specific pattern.</p>
3+
<p>However, they can also be used in the middle (or at the beginning) of a regex. In that case there is the possibility that what comes after the
4+
lookahead does not match the pattern inside the lookahead. This makes the lookahead impossible to match and is a sign that there’s a mistake in the
5+
regular expression that should be fixed.</p>
6+
<h2>Noncompliant Code Example</h2>
7+
<pre>
8+
r"(?=a)b" # Noncompliant, the same character can't be equal to 'a' and 'b' at the same time
9+
</pre>
10+
<h2>Compliant Solution</h2>
11+
<pre>
12+
r"(?&lt;=a)b"
13+
r"a(?=b)"
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 lookahead assertions should not be contradictory",
3+
"type": "BUG",
4+
"status": "ready",
5+
"remediation": {
6+
"func": "Constant\/Issue",
7+
"constantCost": "20min"
8+
},
9+
"tags": [
10+
"regex"
11+
],
12+
"defaultSeverity": "Critical",
13+
"ruleSpecification": "RSPEC-6002",
14+
"sqKey": "S6002",
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
@@ -138,6 +138,7 @@
138138
"S5864",
139139
"S5886",
140140
"S5890",
141+
"S6002"
141142
"S6019"
142143
]
143144
}
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 RegexLookaheadCheckTest {
26+
27+
@Test
28+
public void test() {
29+
PythonCheckVerifier.verify("src/test/resources/checks/regex/regexLookaheadCheck.py", new RegexLookaheadCheck());
30+
}
31+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import re
2+
3+
4+
def non_compliant(input):
5+
re.match(r"(?=a)b", input) # Noncompliant {{Remove or fix this lookahead assertion that can never be true.}}
6+
# ^^^^^
7+
re.match(r"(?=ac)ab", input) # Noncompliant
8+
re.match(r"(?=a)bc", input) # Noncompliant
9+
re.match(r"(?!a)a", input) # Noncompliant
10+
re.match(r"(?!ab)ab", input) # Noncompliant
11+
re.match(r"(?=a)[^ba]", input) # Noncompliant
12+
re.match(r"(?!.)ab", input) # Noncompliant
13+
14+
15+
def compliant(input):
16+
re.match(r"(?=a)a", input)
17+
re.match(r"(?=a)..", input)
18+
re.match(r"(?=a)ab", input)
19+
re.match(r"(?!ab)..", input)
20+
re.match(r"(?<=a)b", input)
21+
re.match(r"a(?=b)", input)
22+
re.match(r"(?=abc)ab", input)

0 commit comments

Comments
 (0)