Skip to content

Commit 8b2aa99

Browse files
committed
SONARPLUGINS-2012 Provide Python rule engine based on SSLR
* SONARPLUGINS-2014 Add generic Python rule to define some homemade checks with an XPath expression * SONARPLUGINS-2021 When there is a parsing error, this error should be added like a violation on that Python file * Add DevKit
1 parent ba0df94 commit 8b2aa99

27 files changed

Lines changed: 674 additions & 17 deletions

File tree

pom.xml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,7 @@
4343
<module>python-squid</module>
4444
<module>python-checks</module>
4545
<module>sonar-python-plugin</module>
46-
<!--
4746
<module>python-devkit</module>
48-
-->
4947
</modules>
5048

5149
<scm>
@@ -79,6 +77,11 @@
7977
<artifactId>sslr-core</artifactId>
8078
<version>1.13</version>
8179
</dependency>
80+
<dependency>
81+
<groupId>org.codehaus.sonar.sslr</groupId>
82+
<artifactId>sslr-devkit</artifactId>
83+
<version>1.13</version>
84+
</dependency>
8285
<dependency>
8386
<groupId>org.codehaus.sonar.sslr</groupId>
8487
<artifactId>sslr-testing-harness</artifactId>
@@ -89,11 +92,21 @@
8992
<artifactId>sslr-squid-bridge</artifactId>
9093
<version>2.2</version>
9194
</dependency>
95+
<dependency>
96+
<groupId>junit</groupId>
97+
<artifactId>junit</artifactId>
98+
<version>4.10</version>
99+
</dependency>
92100
<dependency>
93101
<groupId>org.easytesting</groupId>
94102
<artifactId>fest-assert</artifactId>
95103
<version>1.4</version>
96104
</dependency>
105+
<dependency>
106+
<groupId>ch.qos.logback</groupId>
107+
<artifactId>logback-classic</artifactId>
108+
<version>0.9.15</version>
109+
</dependency>
97110
</dependencies>
98111
</dependencyManagement>
99112

python-checks/pom.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,21 @@
1818
<artifactId>python-squid</artifactId>
1919
<version>${project.version}</version>
2020
</dependency>
21+
<dependency>
22+
<groupId>junit</groupId>
23+
<artifactId>junit</artifactId>
24+
<scope>test</scope>
25+
</dependency>
26+
<dependency>
27+
<groupId>org.easytesting</groupId>
28+
<artifactId>fest-assert</artifactId>
29+
<scope>test</scope>
30+
</dependency>
31+
<dependency>
32+
<groupId>ch.qos.logback</groupId>
33+
<artifactId>logback-classic</artifactId>
34+
<scope>test</scope>
35+
</dependency>
2136
</dependencies>
2237

2338
</project>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Sonar Python Plugin
3+
* Copyright (C) 2011 Waleri Enns
4+
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
17+
* License along with this program; if not, write to the Free Software
18+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
19+
*/
20+
package org.sonar.python.checks;
21+
22+
import com.google.common.collect.ImmutableList;
23+
24+
import java.util.List;
25+
26+
public final class CheckList {
27+
28+
public static final String REPOSITORY_KEY = "python";
29+
30+
public static final String SONAR_WAY_PROFILE = "Sonar way";
31+
32+
private CheckList() {
33+
}
34+
35+
public static List<Class> getChecks() {
36+
return ImmutableList.<Class> of(
37+
ParsingErrorCheck.class,
38+
XPathCheck.class);
39+
}
40+
41+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Sonar Python Plugin
3+
* Copyright (C) 2011 Waleri Enns
4+
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
17+
* License along with this program; if not, write to the Free Software
18+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
19+
*/
20+
package org.sonar.python.checks;
21+
22+
import com.sonar.sslr.api.AuditListener;
23+
import com.sonar.sslr.api.RecognitionException;
24+
import com.sonar.sslr.squid.checks.SquidCheck;
25+
import org.sonar.check.Priority;
26+
import org.sonar.check.Rule;
27+
import org.sonar.python.api.PythonGrammar;
28+
29+
import java.io.PrintWriter;
30+
import java.io.StringWriter;
31+
32+
@Rule(
33+
key = "ParsingError",
34+
priority = Priority.MAJOR)
35+
public class ParsingErrorCheck extends SquidCheck<PythonGrammar> implements AuditListener {
36+
37+
public void processException(Exception e) {
38+
StringWriter exception = new StringWriter();
39+
e.printStackTrace(new PrintWriter(exception));
40+
getContext().createFileViolation(this, exception.toString());
41+
}
42+
43+
public void processRecognitionException(RecognitionException e) {
44+
getContext().createLineViolation(this, e.getMessage(), e.getLine());
45+
}
46+
47+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Sonar Python Plugin
3+
* Copyright (C) 2011 Waleri Enns
4+
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
17+
* License along with this program; if not, write to the Free Software
18+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
19+
*/
20+
package org.sonar.python.checks;
21+
22+
import com.sonar.sslr.squid.checks.AbstractXPathCheck;
23+
import org.sonar.check.Cardinality;
24+
import org.sonar.check.Priority;
25+
import org.sonar.check.Rule;
26+
import org.sonar.check.RuleProperty;
27+
import org.sonar.python.api.PythonGrammar;
28+
29+
@Rule(
30+
key = "XPath",
31+
priority = Priority.MAJOR,
32+
cardinality = Cardinality.MULTIPLE)
33+
public class XPathCheck extends AbstractXPathCheck<PythonGrammar> {
34+
35+
private static final String DEFAULT_XPATH_QUERY = "";
36+
private static final String DEFAULT_MESSAGE = "The XPath expression matches this piece of code";
37+
38+
@RuleProperty(
39+
key = "xpathQuery",
40+
defaultValue = "" + DEFAULT_XPATH_QUERY)
41+
public String xpathQuery = DEFAULT_XPATH_QUERY;
42+
43+
@RuleProperty(
44+
key = "message",
45+
defaultValue = "" + DEFAULT_XPATH_QUERY)
46+
public String message = DEFAULT_MESSAGE;
47+
48+
@Override
49+
public String getXPathQuery() {
50+
return xpathQuery;
51+
}
52+
53+
@Override
54+
public String getMessage() {
55+
return message;
56+
}
57+
58+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
rule.python.ParsingError.name=Python parser failure
2+
rule.python.XPath.name=XPath rule
3+
rule.python.XPath.param.xpathQuery=The XPath query
4+
rule.python.XPath.param.message=The violation message
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<p>
2+
When the Python parser fails, it is possible to record the failure as a violation on the file.
3+
This way, not only it is possible to track the number of files that do not parse but also to easily find out why they do not parse.
4+
</p>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<p>
2+
This rule allows to define some homemade Python rules with help of an XPath expression.
3+
</p>
4+
5+
<p>
6+
Violations are created depending on the return value of the XPath expression. If the XPath expression returns:
7+
</p>
8+
<ul>
9+
<li>a single or list of AST nodes, then a line violation with the given message is created for each node</li>
10+
<li>a boolean, then a file violation with the given message is created only if the boolean is true</li>
11+
<li>anything else, no violation is created</li>
12+
</ul>
13+
14+
<p>
15+
Here is an example of an XPath expression to log a violation on each statement : //statement
16+
</p>
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Sonar Python Plugin
3+
* Copyright (C) 2011 Waleri Enns
4+
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
17+
* License along with this program; if not, write to the Free Software
18+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
19+
*/
20+
package org.sonar.python.checks;
21+
22+
import org.apache.commons.io.FileUtils;
23+
import org.junit.Test;
24+
import org.sonar.api.rules.AnnotationRuleParser;
25+
import org.sonar.api.rules.Rule;
26+
import org.sonar.api.rules.RuleParam;
27+
28+
import java.io.File;
29+
import java.util.List;
30+
import java.util.Locale;
31+
import java.util.ResourceBundle;
32+
33+
import static org.fest.assertions.Assertions.assertThat;
34+
35+
public class CheckListTest {
36+
37+
/**
38+
* Enforces that each check declared in list.
39+
*/
40+
@Test
41+
public void count() {
42+
int count = 0;
43+
List<File> files = (List<File>) FileUtils.listFiles(new File("src/main/java/org/sonar/python/checks/"), new String[] {"java"}, false);
44+
for (File file : files) {
45+
if (file.getName().endsWith("Check.java")) {
46+
count++;
47+
}
48+
}
49+
assertThat(CheckList.getChecks().size()).isEqualTo(count);
50+
}
51+
52+
/**
53+
* Enforces that each check has test, name and description.
54+
*/
55+
@Test
56+
public void test() {
57+
List<Class> checks = CheckList.getChecks();
58+
59+
for (Class cls : checks) {
60+
String testName = '/' + cls.getName().replace('.', '/') + "Test.class";
61+
assertThat(getClass().getResource(testName))
62+
.overridingErrorMessage("No test for " + cls.getSimpleName())
63+
.isNotNull();
64+
}
65+
66+
ResourceBundle resourceBundle = ResourceBundle.getBundle("org.sonar.l10n.python", Locale.ENGLISH);
67+
68+
List<Rule> rules = new AnnotationRuleParser().parse("repositoryKey", checks);
69+
for (Rule rule : rules) {
70+
resourceBundle.getString("rule." + CheckList.REPOSITORY_KEY + "." + rule.getKey() + ".name");
71+
assertThat(getClass().getResource("/org/sonar/l10n/python/" + rule.getKey() + ".html"))
72+
.overridingErrorMessage("No description for " + rule.getKey())
73+
.isNotNull();
74+
75+
assertThat(rule.getDescription())
76+
.overridingErrorMessage("Description of " + rule.getKey() + " should be in separate file")
77+
.isEmpty();
78+
79+
for (RuleParam param : rule.getParams()) {
80+
resourceBundle.getString("rule." + CheckList.REPOSITORY_KEY + "." + rule.getKey() + ".param." + param.getKey());
81+
82+
assertThat(param.getDescription())
83+
.overridingErrorMessage("Description for param " + param.getKey() + " of " + rule.getKey() + " should be in separate file")
84+
.isEmpty();
85+
}
86+
}
87+
}
88+
89+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Sonar Python Plugin
3+
* Copyright (C) 2011 Waleri Enns
4+
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
17+
* License along with this program; if not, write to the Free Software
18+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
19+
*/
20+
package org.sonar.python.checks;
21+
22+
import com.sonar.sslr.squid.checks.CheckMessagesVerifier;
23+
import org.junit.Test;
24+
import org.sonar.python.PythonAstScanner;
25+
import org.sonar.squid.api.SourceFile;
26+
27+
import java.io.File;
28+
29+
public class ParsingErrorCheckTest {
30+
31+
@Test
32+
public void test() {
33+
SourceFile file = PythonAstScanner.scanSingleFile(new File("src/test/resources/checks/parsingError.py"), new ParsingErrorCheck());
34+
CheckMessagesVerifier.verify(file.getCheckMessages())
35+
.next().atLine(1)
36+
// .withMessageThat(containsString("NEWLINE expected but \" \" [INDENT] found"))
37+
.noMore();
38+
}
39+
40+
}

0 commit comments

Comments
 (0)