Skip to content

Commit 68454aa

Browse files
SONARPY-929 Match / case statement: support value patterns
1 parent f350f9b commit 68454aa

File tree

8 files changed

+129
-1
lines changed

8 files changed

+129
-1
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,4 +542,8 @@ public void visitKeywordPattern(KeywordPattern keywordPattern) {
542542
scan(keywordPattern.attributeName());
543543
scan(keywordPattern.pattern());
544544
}
545+
546+
public void visitValuePattern(ValuePattern valuePattern) {
547+
scan(valuePattern.qualifiedExpression());
548+
}
545549
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ enum Kind {
172172
RETURN_TYPE_ANNOTATION(TypeAnnotation.class),
173173

174174
PARAMETER_LIST(ParameterList.class),
175+
VALUE_PATTERN(ValuePattern.class),
175176

176177
WHILE_STMT(WhileStatement.class),
177178

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
@@ -198,4 +198,6 @@ public interface TreeVisitor {
198198
void visitClassPattern(ClassPattern classPattern);
199199

200200
void visitKeywordPattern(KeywordPattern keywordPattern);
201+
202+
void visitValuePattern(ValuePattern valuePattern);
201203
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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+
/**
23+
* Value Pattern
24+
*
25+
* <pre>
26+
* case a.b:
27+
* ...
28+
* </pre>
29+
*
30+
* See https://docs.python.org/3/reference/compound_stmts.html#value-patterns
31+
*/
32+
public interface ValuePattern extends Pattern {
33+
34+
QualifiedExpression qualifiedExpression();
35+
36+
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ public enum PythonGrammar implements GrammarRuleKey {
173173
PATTERN_ARG,
174174
KEYWORD_PATTERN,
175175
NAME_OR_ATTR,
176+
VALUE_PATTERN,
176177

177178
SIGNED_NUMBER,
178179
COMPLEX_NUMBER,
@@ -181,6 +182,7 @@ public enum PythonGrammar implements GrammarRuleKey {
181182
DECORATORS,
182183
DECORATOR,
183184
DOTTED_NAME,
185+
ATTR,
184186
FUNCNAME,
185187
FUN_RETURN_ANNOTATION,
186188

@@ -474,10 +476,11 @@ public static void compoundStatements(LexerfulGrammarBuilder b) {
474476

475477
b.rule(PATTERNS).is(b.firstOf(OPEN_SEQUENCE_PATTERN, PATTERN));
476478
b.rule(PATTERN).is(b.firstOf(AS_PATTERN, OR_PATTERN));
477-
b.rule(CLOSED_PATTERN).is(b.firstOf(CLASS_PATTERN, LITERAL_PATTERN, GROUP_PATTERN, WILDCARD_PATTERN, CAPTURE_PATTERN, SEQUENCE_PATTERN));
479+
b.rule(CLOSED_PATTERN).is(b.firstOf(CLASS_PATTERN, LITERAL_PATTERN, GROUP_PATTERN, WILDCARD_PATTERN, VALUE_PATTERN, CAPTURE_PATTERN, SEQUENCE_PATTERN));
478480
b.rule(AS_PATTERN).is(OR_PATTERN, "as", CAPTURE_PATTERN);
479481
b.rule(OR_PATTERN).is(CLOSED_PATTERN, b.zeroOrMore("|", CLOSED_PATTERN));
480482
b.rule(CAPTURE_PATTERN).is(NAME);
483+
b.rule(VALUE_PATTERN).is(ATTR);
481484

482485
b.rule(SEQUENCE_PATTERN).is(b.firstOf(
483486
b.sequence("[", b.optional(MAYBE_SEQUENCE_PATTERN) , "]"),
@@ -523,6 +526,7 @@ public static void compoundStatements(LexerfulGrammarBuilder b) {
523526
b.rule(DECORATORS).is(b.oneOrMore(DECORATOR));
524527
b.rule(DECORATOR).is("@", EXPR, NEWLINE);
525528
b.rule(DOTTED_NAME).is(NAME, b.zeroOrMore(".", NAME));
529+
b.rule(ATTR).is(NAME, b.oneOrMore(b.nextNot("(", "["), TRAILER));
526530

527531
b.rule(CLASSDEF).is(b.optional(DECORATORS), "class", CLASSNAME, b.optional("(", b.optional(ARGLIST), ")"), ":", SUITE);
528532
b.rule(CLASSNAME).is(NAME);

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
import org.sonar.plugins.python.api.tree.PassStatement;
8282
import org.sonar.plugins.python.api.tree.Pattern;
8383
import org.sonar.plugins.python.api.tree.PrintStatement;
84+
import org.sonar.plugins.python.api.tree.QualifiedExpression;
8485
import org.sonar.plugins.python.api.tree.RaiseStatement;
8586
import org.sonar.plugins.python.api.tree.RegularArgument;
8687
import org.sonar.plugins.python.api.tree.ReturnStatement;
@@ -872,6 +873,8 @@ public static Pattern closedPattern(AstNode closedPattern) {
872873
return wildcardPattern(astNode);
873874
} else if (astNode.is(PythonGrammar.CLASS_PATTERN)) {
874875
return classPattern(astNode);
876+
} else if (astNode.is(PythonGrammar.VALUE_PATTERN)) {
877+
return new ValuePatternImpl(nameAttributeAccess(astNode.getFirstChild()));
875878
}
876879
throw new IllegalStateException(String.format("Pattern %s not recognized.", astNode.getName()));
877880
}
@@ -1143,6 +1146,15 @@ private Expression starNamedExpressions(AstNode astNode) {
11431146
return expressions.get(0);
11441147
}
11451148

1149+
private static QualifiedExpression nameAttributeAccess(AstNode astNode) {
1150+
Expression expr = name(astNode.getFirstChild(PythonGrammar.NAME));
1151+
for (AstNode trailer : astNode.getChildren(PythonGrammar.TRAILER)) {
1152+
Name name = name(trailer.getFirstChild(PythonGrammar.NAME));
1153+
expr = new QualifiedExpressionImpl(name, expr, toPyToken(trailer.getFirstChild(PythonPunctuator.DOT).getToken()));
1154+
}
1155+
return (QualifiedExpression) expr;
1156+
}
1157+
11461158
private Expression assignmentExpression(AstNode astNode) {
11471159
AstNode nameNode = astNode.getFirstChild(PythonGrammar.TEST);
11481160
Expression nameExpression = expression(nameNode);
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.QualifiedExpression;
25+
import org.sonar.plugins.python.api.tree.Tree;
26+
import org.sonar.plugins.python.api.tree.TreeVisitor;
27+
import org.sonar.plugins.python.api.tree.ValuePattern;
28+
29+
public class ValuePatternImpl extends PyTree implements ValuePattern {
30+
31+
private final QualifiedExpression qualifiedExpression;
32+
33+
public ValuePatternImpl(QualifiedExpression qualifiedExpression) {
34+
this.qualifiedExpression = qualifiedExpression;
35+
}
36+
37+
@Override
38+
public QualifiedExpression qualifiedExpression() {
39+
return qualifiedExpression;
40+
}
41+
42+
@Override
43+
public void accept(TreeVisitor visitor) {
44+
visitor.visitValuePattern(this);
45+
}
46+
47+
@Override
48+
public Kind getKind() {
49+
return Kind.VALUE_PATTERN;
50+
}
51+
52+
@Override
53+
List<Tree> computeChildren() {
54+
return Collections.singletonList(qualifiedExpression);
55+
}
56+
}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.sonar.plugins.python.api.tree.Tree.Kind;
4444
import org.sonar.plugins.python.api.tree.Tuple;
4545
import org.sonar.plugins.python.api.tree.WildcardPattern;
46+
import org.sonar.plugins.python.api.tree.ValuePattern;
4647
import org.sonar.python.api.PythonGrammar;
4748
import org.sonar.python.parser.RuleTest;
4849

@@ -195,6 +196,18 @@ public void capture_pattern() {
195196
assertThat(capturePattern.children()).containsExactly(capturePattern.name());
196197
}
197198

199+
@Test
200+
public void value_pattern() {
201+
ValuePattern valuePattern = pattern("case a.b: ...");
202+
QualifiedExpression qualifiedExpression = valuePattern.qualifiedExpression();
203+
assertThat(TreeUtils.nameFromQualifiedExpression(qualifiedExpression)).isEqualTo("a.b");
204+
assertThat(valuePattern.children()).containsExactly(valuePattern.qualifiedExpression());
205+
206+
valuePattern = pattern("case a.b.c: ...");
207+
qualifiedExpression = valuePattern.qualifiedExpression();
208+
assertThat(TreeUtils.nameFromQualifiedExpression(qualifiedExpression)).isEqualTo("a.b.c");
209+
}
210+
198211
@Test
199212
public void sequence_pattern() {
200213
assertSequenceElements(pattern("case [x, y]: ..."), "x", "y");

0 commit comments

Comments
 (0)