Skip to content

Commit 0a64586

Browse files
joke1196sonartech
authored andcommitted
SONARPY-3407 Untangling PythonTreeMaker and ExceptGroupJumpInstructionsCheck (#535)
GitOrigin-RevId: c445837fcbbf056855af47b66ae5c5361129edec
1 parent 3b65f9a commit 0a64586

File tree

5 files changed

+76
-11
lines changed

5 files changed

+76
-11
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public void visitReturnStatement(ReturnStatement returnStatement) {
4848
private static void checkExceptGroupStatement(Tree statement, String message, Tree.Kind... possibleParents) {
4949
Tree parent = TreeUtils.firstAncestorOfKind(statement, possibleParents);
5050
if (parent != null && parent.is(Tree.Kind.EXCEPT_GROUP_CLAUSE)) {
51-
PythonTreeMaker.recognitionException(statement.firstToken().line(), message);
51+
PythonTreeMakerUtils.throwRecognitionException(statement.firstToken().line(), message);
5252
}
5353
}
5454
}

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

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import com.sonar.sslr.api.AstNode;
2020
import com.sonar.sslr.api.GenericTokenType;
21-
import com.sonar.sslr.api.RecognitionException;
2221
import java.util.ArrayList;
2322
import java.util.Collection;
2423
import java.util.Collections;
@@ -121,10 +120,6 @@ public FileInput fileInput(AstNode astNode) {
121120
return pyFileInputTree;
122121
}
123122

124-
public static void recognitionException(int line, String message) {
125-
throw new RecognitionException(line, "Parse error at line " + line + ": " + message + ".");
126-
}
127-
128123
protected Token toPyToken(@Nullable com.sonar.sslr.api.Token token) {
129124
if (token == null) {
130125
return null;
@@ -839,10 +834,10 @@ public void checkExceptClauses(List<ExceptClause> excepts) {
839834
Tree.Kind firstExceptKind = excepts.get(0).getKind();
840835
for (ExceptClause except : excepts) {
841836
if (firstExceptKind != except.getKind()) {
842-
recognitionException(except.exceptKeyword().line(), "Try statement cannot contain both except and except* clauses");
837+
PythonTreeMakerUtils.throwRecognitionException(except.exceptKeyword().line(), "Try statement cannot contain both except and except* clauses");
843838
}
844839
if (except.is(Tree.Kind.EXCEPT_GROUP_CLAUSE) && except.exception() == null) {
845-
recognitionException(except.exceptKeyword().line(), "except* clause must specify the type of the expected exception");
840+
PythonTreeMakerUtils.throwRecognitionException(except.exceptKeyword().line(), "except* clause must specify the type of the expected exception");
846841
}
847842
}
848843
}
@@ -1010,7 +1005,7 @@ private static void checkPositionalAndKeywordArgumentsConstraint(List<Pattern> p
10101005
positionalArgs = false;
10111006
} else if (!positionalArgs) {
10121007
int line = pattern.firstToken().line();
1013-
recognitionException(line, "Positional patterns follow keyword patterns");
1008+
PythonTreeMakerUtils.throwRecognitionException(line, "Positional patterns follow keyword patterns");
10141009
}
10151010
}
10161011
}
@@ -1295,7 +1290,7 @@ private Expression assignmentExpression(AstNode astNode) {
12951290
Expression nameExpression = expression(nameNode);
12961291
if (!nameExpression.is(Tree.Kind.NAME)) {
12971292
int line = nameNode.getTokenLine();
1298-
recognitionException(line, "The left-hand side of an assignment expression must be a name");
1293+
PythonTreeMakerUtils.throwRecognitionException(line, "The left-hand side of an assignment expression must be a name");
12991294
}
13001295
Name name = (Name) nameExpression;
13011296
AstNode operatorNode = astNode.getFirstChild(PythonPunctuator.WALRUS_OPERATOR);
@@ -1557,7 +1552,7 @@ private static void checkGeneratorExpressionInArgument(List<Argument> arguments)
15571552
.collect(Collectors.toList());
15581553
if (!nonParenthesizedGeneratorExpressions.isEmpty() && arguments.size() > 1) {
15591554
int line = nonParenthesizedGeneratorExpressions.get(0).firstToken().line();
1560-
recognitionException(line, "Generator expression must be parenthesized if not sole argument");
1555+
PythonTreeMakerUtils.throwRecognitionException(line, "Generator expression must be parenthesized if not sole argument");
15611556
}
15621557
}
15631558

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2025 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 Sonar Source-Available License Version 1, as published by SonarSource SA.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonar.python.tree;
18+
19+
import com.sonar.sslr.api.RecognitionException;
20+
21+
public class PythonTreeMakerUtils {
22+
23+
private PythonTreeMakerUtils(){}
24+
public static void throwRecognitionException(int line, String message) {
25+
throw new RecognitionException(line, "Parse error at line " + line + ": " + message + ".");
26+
}
27+
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
import static org.assertj.core.api.Assertions.assertThat;
121121
import static org.assertj.core.api.Assertions.assertThatThrownBy;
122122
import static org.assertj.core.api.Assertions.fail;
123+
import static org.junit.jupiter.api.Assertions.assertThrows;
123124
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
124125

125126
class PythonTreeMakerTest extends RuleTest {
@@ -1520,6 +1521,15 @@ void try_statement() {
15201521
assertThat(children.get(children.size() - 2)).isSameAs(tryStatement.elseClause());
15211522
assertThat(children.get(children.size() - 1)).isSameAs(tryStatement.finallyClause());
15221523

1524+
var ast = p.parse("try:\n pass\nexcept* :\n pass");
1525+
Exception exception = assertThrows(RecognitionException.class, () -> treeMaker.tryStatement(ast));
1526+
1527+
assertThat(exception.getMessage()).isEqualTo("Parse error at line 3: except* clause must specify the type of the expected exception.");
1528+
1529+
var astExcept = p.parse("try:\n pass\nexcept Error:\n pass\nexcept* Error:\n pass");
1530+
exception = assertThrows(RecognitionException.class, () -> treeMaker.tryStatement(astExcept));
1531+
1532+
assertThat(exception.getMessage()).isEqualTo("Parse error at line 5: Try statement cannot contain both except and except* clauses.");
15231533
}
15241534

15251535
@Test
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-2025 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 Sonar Source-Available License Version 1, as published by SonarSource SA.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonar.python.tree;
18+
19+
import com.sonar.sslr.api.RecognitionException;
20+
import org.junit.jupiter.api.Test;
21+
22+
import static org.junit.jupiter.api.Assertions.assertThrows;
23+
import static org.assertj.core.api.Assertions.assertThat;
24+
25+
class PythonTreeMakerUtilsTest {
26+
27+
@Test
28+
void shouldThrowTheExceptionWithTheProvidedMessage(){
29+
Exception exception = assertThrows(RecognitionException.class, () -> PythonTreeMakerUtils.throwRecognitionException(12, "TEST"));
30+
assertThat(exception.getMessage()).isEqualTo("Parse error at line 12: TEST.");
31+
}
32+
}
33+

0 commit comments

Comments
 (0)