Skip to content

Commit c2959db

Browse files
SONARPY-918 Add capture pattern
1 parent 9e0fd79 commit c2959db

File tree

10 files changed

+143
-7
lines changed

10 files changed

+143
-7
lines changed

python-frontend/src/main/java/org/sonar/plugins/python/api/tree/BaseTreeVisitor.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,4 +500,9 @@ public void visitAsPattern(AsPattern asPattern) {
500500
public void visitGuard(Guard guard) {
501501
scan(guard.condition());
502502
}
503+
504+
@Override
505+
public void visitCapturePattern(CapturePattern capturePattern) {
506+
scan(capturePattern.name());
507+
}
503508
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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.plugins.python.api.tree;
21+
22+
public interface CapturePattern extends Pattern {
23+
24+
Name name();
25+
}

python-frontend/src/main/java/org/sonar/plugins/python/api/tree/Tree.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ enum Kind {
5858

5959
CALL_EXPR(CallExpression.class),
6060

61+
CAPTURE_PATTERN(CapturePattern.class),
62+
6163
CASE_BLOCK(CaseBlock.class),
6264

6365
CLASSDEF(ClassDef.class),

python-frontend/src/main/java/org/sonar/plugins/python/api/tree/TreeVisitor.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,4 +182,6 @@ public interface TreeVisitor {
182182
void visitAsPattern(AsPattern asPattern);
183183

184184
void visitGuard(Guard guard);
185+
186+
void visitCapturePattern(CapturePattern capturePattern);
185187
}

python-frontend/src/main/java/org/sonar/python/api/PythonGrammar.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ public static void compoundStatements(LexerfulGrammarBuilder b) {
459459
b.rule(GUARD).is("if", NAMED_EXPR_TEST);
460460

461461
b.rule(PATTERN).is(b.firstOf(AS_PATTERN, CLOSED_PATTERN));
462-
b.rule(CLOSED_PATTERN).is(LITERAL_PATTERN);
462+
b.rule(CLOSED_PATTERN).is(b.firstOf(LITERAL_PATTERN, CAPTURE_PATTERN));
463463
b.rule(AS_PATTERN).is(CLOSED_PATTERN, "as", CAPTURE_PATTERN);
464464
b.rule(CAPTURE_PATTERN).is(NAME);
465465

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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.tree;
21+
22+
import java.util.Collections;
23+
import java.util.List;
24+
import org.sonar.plugins.python.api.tree.CapturePattern;
25+
import org.sonar.plugins.python.api.tree.Name;
26+
import org.sonar.plugins.python.api.tree.Tree;
27+
import org.sonar.plugins.python.api.tree.TreeVisitor;
28+
29+
public class CapturePatternImpl extends PyTree implements CapturePattern {
30+
31+
private final Name name;
32+
33+
public CapturePatternImpl(Name name) {
34+
this.name = name;
35+
}
36+
37+
@Override
38+
public Name name() {
39+
return name;
40+
}
41+
42+
@Override
43+
public void accept(TreeVisitor visitor) {
44+
visitor.visitCapturePattern(this);
45+
}
46+
47+
@Override
48+
public Kind getKind() {
49+
return Kind.CAPTURE_PATTERN;
50+
}
51+
52+
@Override
53+
List<Tree> computeChildren() {
54+
return Collections.singletonList(name);
55+
}
56+
}

python-frontend/src/main/java/org/sonar/python/tree/PythonTreeMaker.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -823,14 +823,11 @@ public Guard guard(AstNode guardNode) {
823823
return new GuardImpl(ifKeyword, condition);
824824
}
825825

826-
private static Pattern pattern(AstNode pattern) {
827-
// TODO: consider OR Patterns and other kind of patterns
826+
public static Pattern pattern(AstNode pattern) {
828827
if (pattern.is(PythonGrammar.CLOSED_PATTERN)) {
829-
return literalPattern(pattern.getFirstChild());
830-
} else if (pattern.is(PythonGrammar.AS_PATTERN)) {
831-
return asPattern(pattern);
828+
return closedPattern(pattern);
832829
}
833-
throw new IllegalStateException(String.format("Pattern %s not recognized.", pattern.getName()));
830+
return asPattern(pattern);
834831
}
835832

836833
private static AsPattern asPattern(AstNode asPattern) {
@@ -840,6 +837,16 @@ private static AsPattern asPattern(AstNode asPattern) {
840837
return new AsPatternImpl(pattern, asKeyword, alias);
841838
}
842839

840+
private static Pattern closedPattern(AstNode closedPattern) {
841+
AstNode astNode = closedPattern.getFirstChild();
842+
if (astNode.is(PythonGrammar.LITERAL_PATTERN)) {
843+
return literalPattern(astNode);
844+
} else if (astNode.is(PythonGrammar.CAPTURE_PATTERN)) {
845+
return new CapturePatternImpl(name(astNode.getFirstChild()));
846+
}
847+
throw new IllegalStateException(String.format("Pattern %s not recognized.", closedPattern.getName()));
848+
}
849+
843850
private static LiteralPattern literalPattern(AstNode literalPattern) {
844851
LiteralPattern.LiteralKind literalKind;
845852
if (literalPattern.hasDirectChildren(PythonGrammar.COMPLEX_NUMBER, PythonGrammar.SIGNED_NUMBER)) {

python-frontend/src/test/java/org/sonar/plugins/python/api/tree/BaseTreeVisitorTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,4 +400,25 @@ public void guard() {
400400

401401
verify(visitor).visitBinaryExpression(((BinaryExpression) guard.condition()));
402402
}
403+
404+
@Test
405+
public void as_pattern() {
406+
setRootRule(PythonGrammar.AS_PATTERN);
407+
AsPattern pattern = ((AsPattern) parse("'foo' as x", PythonTreeMaker::pattern));
408+
FirstLastTokenVerifierVisitor visitor = spy(FirstLastTokenVerifierVisitor.class);
409+
pattern.accept(visitor);
410+
411+
verify(visitor).visitLiteralPattern((LiteralPattern) pattern.pattern());
412+
verify(visitor).visitName(pattern.alias());
413+
}
414+
415+
@Test
416+
public void capture_pattern() {
417+
setRootRule(PythonGrammar.CLOSED_PATTERN);
418+
CapturePattern pattern = ((CapturePattern) parse("x", PythonTreeMaker::pattern));
419+
FirstLastTokenVerifierVisitor visitor = spy(FirstLastTokenVerifierVisitor.class);
420+
pattern.accept(visitor);
421+
422+
verify(visitor).visitName(pattern.name());
423+
}
403424
}

python-frontend/src/test/java/org/sonar/python/parser/compound/statements/CasePatternsTest.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public void none() {
4747

4848
@Test
4949
public void booleans() {
50+
setRootRule(PythonGrammar.LITERAL_PATTERN);
5051
assertThat(p)
5152
.matches("True")
5253
.matches("False")
@@ -71,6 +72,13 @@ public void complex_numbers() {
7172
public void as_patterns() {
7273
assertThat(p)
7374
.matches("'foo' as x")
75+
.matches("x as y")
7476
.notMatches("'foo' as x as y");
7577
}
78+
79+
@Test
80+
public void capture() {
81+
assertThat(p)
82+
.matches("x");
83+
}
7684
}

python-frontend/src/test/java/org/sonar/python/tree/PythonTreeMakerMatchStatementTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.junit.Test;
2323
import org.sonar.plugins.python.api.tree.AsPattern;
24+
import org.sonar.plugins.python.api.tree.CapturePattern;
2425
import org.sonar.plugins.python.api.tree.CaseBlock;
2526
import org.sonar.plugins.python.api.tree.Expression;
2627
import org.sonar.plugins.python.api.tree.Guard;
@@ -157,4 +158,13 @@ public void as_pattern() {
157158
assertThat(asPattern.alias().name()).isEqualTo("x");
158159
assertThat(asPattern.children()).extracting(Tree::getKind).containsExactly(Tree.Kind.LITERAL_PATTERN, Tree.Kind.TOKEN, Tree.Kind.NAME);
159160
}
161+
162+
@Test
163+
public void capture_pattern() {
164+
setRootRule(PythonGrammar.CASE_BLOCK);
165+
CaseBlock caseBlock = parse("case x: ...", treeMaker::caseBlock);
166+
CapturePattern capturePattern = (CapturePattern) caseBlock.pattern();
167+
assertThat(capturePattern.name().name()).isEqualTo("x");
168+
assertThat(capturePattern.children()).containsExactly(capturePattern.name());
169+
}
160170
}

0 commit comments

Comments
 (0)