Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit c7b5010

Browse filesBrowse files
ivandalboscopynicolas
authored andcommitted
SONARPY-183 Include docstrings in the comment lines (SonarSource#71)
* SONARPY-183 Include docstrings in the comment lines - Deduce measure COMMENT_LINES from the FileLineVisitor rather than from the CommentsVisitor (removes possible inconsistency) * SONARPY-183 Include docstrings in the comment lines - Move unit testing of lines of code/comment from PythonSquidSensorTest to FileLinesVisitorTest * SONARPY-183 Factorize the extraction of the docstring to a separate class * SONARPY-183 Include the docstrings in the comment lines * SONARPY-183 Include docstrings in the comment lines - Retrieve enableNoSonar, ignoreHeaderComments and empty comment line behaviour from CommentsVisitor * SONARPY-183 Avoid parsing the same file more than once * SONARPY-183 Reduce complexity * SONARPY-183 Post-review fixes * SONARPY-183 Fix handling of state in FileLinesVisitor * SONARPY-183 Make NCLOC consistent with other metrics
1 parent 4480258 commit c7b5010
Copy full SHA for c7b5010

File tree

Expand file treeCollapse file tree

17 files changed

+413
-225
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

17 files changed

+413
-225
lines changed
Open diff view settings
Collapse file

‎its/plugin/src/test/java/com/sonar/python/it/plugin/MetricsTest.java‎

Copy file name to clipboardExpand all lines: its/plugin/src/test/java/com/sonar/python/it/plugin/MetricsTest.java
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ public void should_be_compatible_with_DevCockpit() {
153153
.doesNotContain("1=1")
154154
.contains("5=1");
155155
assertThat(getFileMeasure("comment_lines_data").getData())
156-
.contains("1=1")
156+
.contains("2=1")
157157
.doesNotContain("4=1");
158158
}
159159

Collapse file

‎python-checks/src/main/java/org/sonar/python/checks/MissingDocstringCheck.java‎

Copy file name to clipboardExpand all lines: python-checks/src/main/java/org/sonar/python/checks/MissingDocstringCheck.java
+20-71Lines changed: 20 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,11 @@
2222
import com.sonar.sslr.api.AstNode;
2323
import com.sonar.sslr.api.Token;
2424
import java.util.regex.Pattern;
25-
import javax.annotation.Nullable;
2625
import org.sonar.check.Priority;
2726
import org.sonar.check.Rule;
27+
import org.sonar.python.DocstringExtractor;
2828
import org.sonar.python.PythonCheck;
2929
import org.sonar.python.api.PythonGrammar;
30-
import org.sonar.python.api.PythonTokenType;
3130
import org.sonar.squidbridge.annotations.SqaleConstantRemediation;
3231

3332
@Rule(
@@ -59,64 +58,33 @@ private enum DeclarationType {
5958

6059
@Override
6160
public void init() {
62-
subscribeTo(PythonGrammar.FILE_INPUT, PythonGrammar.FUNCDEF, PythonGrammar.CLASSDEF);
61+
DocstringExtractor.DOCUMENTABLE_NODE_TYPES.stream().forEach(this::subscribeTo);
6362
}
6463

6564
@Override
6665
public void visitNode(AstNode astNode) {
67-
if (astNode.is(PythonGrammar.FILE_INPUT)) {
68-
visitModule(astNode);
69-
}
70-
if (astNode.is(PythonGrammar.FUNCDEF)) {
71-
visitFuncDef(astNode);
72-
}
73-
if (astNode.is(PythonGrammar.CLASSDEF)) {
74-
visitClassDef(astNode);
75-
}
76-
}
77-
78-
private void visitModule(AstNode astNode) {
79-
AstNode firstStatement = astNode.getFirstChild(PythonGrammar.STATEMENT);
80-
AstNode firstSimpleStmt = null;
81-
if (firstStatement != null) {
82-
firstSimpleStmt = firstSimpleStmt(firstStatement);
83-
}
84-
checkSimpleStmt(astNode, firstSimpleStmt, DeclarationType.MODULE);
85-
}
86-
87-
private void visitClassDef(AstNode astNode) {
88-
checkFirstSuite(astNode, DeclarationType.CLASS);
89-
}
90-
91-
private void visitFuncDef(AstNode astNode) {
92-
// on methods we check only empty docstrings to avoid false positives on overriding methods
93-
if (!CheckUtils.isMethodDefinition(astNode)) {
94-
checkFirstSuite(astNode, DeclarationType.FUNCTION);
95-
} else {
96-
checkFirstSuite(astNode, DeclarationType.METHOD);
97-
}
98-
}
99-
100-
private void checkFirstSuite(AstNode astNode, DeclarationType type) {
101-
AstNode suite = astNode.getFirstChild(PythonGrammar.SUITE);
102-
AstNode firstStatement = suite.getFirstChild(PythonGrammar.STATEMENT);
103-
AstNode firstSimpleStmt;
104-
if (firstStatement == null) {
105-
firstSimpleStmt = suite
106-
.getFirstChild(PythonGrammar.STMT_LIST)
107-
.getFirstChild(PythonGrammar.SIMPLE_STMT);
108-
} else {
109-
firstSimpleStmt = firstSimpleStmt(firstStatement);
66+
DeclarationType type = getType(astNode);
67+
Token docstring = DocstringExtractor.extractDocstring(astNode);
68+
if (docstring == null) {
69+
raiseIssueNoDocstring(astNode, type);
70+
} else if (EMPTY_STRING_REGEXP.matcher(docstring.getValue()).matches()) {
71+
raiseIssue(astNode, MESSAGE_EMPTY_DOCSTRING, type);
11072
}
111-
checkSimpleStmt(astNode, firstSimpleStmt, type);
11273
}
11374

114-
private void checkSimpleStmt(AstNode astNode, @Nullable AstNode firstSimpleStmt, DeclarationType type) {
115-
if (firstSimpleStmt != null) {
116-
visitFirstStatement(astNode, firstSimpleStmt, type);
117-
} else {
118-
raiseIssueNoDocstring(astNode, type);
75+
private static DeclarationType getType(AstNode node) {
76+
if (node.is(PythonGrammar.FILE_INPUT)) {
77+
return DeclarationType.MODULE;
78+
} else if (node.is(PythonGrammar.FUNCDEF)) {
79+
if (CheckUtils.isMethodDefinition(node)) {
80+
return DeclarationType.METHOD;
81+
} else {
82+
return DeclarationType.FUNCTION;
83+
}
84+
} else if (node.is(PythonGrammar.CLASSDEF)) {
85+
return DeclarationType.CLASS;
11986
}
87+
return null;
12088
}
12189

12290
private void raiseIssueNoDocstring(AstNode astNode, DeclarationType type) {
@@ -138,23 +106,4 @@ private static AstNode getNameNode(AstNode astNode) {
138106
return astNode.getFirstChild(PythonGrammar.FUNCNAME, PythonGrammar.CLASSNAME);
139107
}
140108

141-
private void visitFirstStatement(AstNode astNode, AstNode firstSimpleStmt, DeclarationType type) {
142-
Token token = firstSimpleStmt.getToken();
143-
if (token.getType().equals(PythonTokenType.STRING)){
144-
if (EMPTY_STRING_REGEXP.matcher(token.getValue()).matches()){
145-
raiseIssue(astNode, MESSAGE_EMPTY_DOCSTRING, type);
146-
}
147-
} else {
148-
raiseIssueNoDocstring(astNode, type);
149-
}
150-
}
151-
152-
private static AstNode firstSimpleStmt(AstNode statement) {
153-
AstNode stmtList = statement.getFirstChild(PythonGrammar.STMT_LIST);
154-
if (stmtList != null) {
155-
return stmtList.getFirstChild(PythonGrammar.SIMPLE_STMT);
156-
}
157-
return null;
158-
}
159-
160109
}
Collapse file

‎python-checks/src/test/java/org/sonar/python/checks/MissingDocstringCheckTest.java‎

Copy file name to clipboardExpand all lines: python-checks/src/test/java/org/sonar/python/checks/MissingDocstringCheckTest.java
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public void testEmptyModule() throws Exception {
4343
}
4444

4545
private void testMissingDocStringAtModuleLevel(String fileName) {
46+
@SuppressWarnings("unchecked")
4647
SourceFile file = PythonAstScanner.scanSingleFile("src/test/resources/checks/" + fileName, new MissingDocstringCheck());
4748
CheckMessagesVerifier.verify(file.getCheckMessages())
4849
.next().atLine(null).withMessage("Add a docstring to this module.")
Collapse file
+90Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2016 SonarSource SA
4+
* mailto:contact 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;
21+
22+
import com.google.common.collect.ImmutableList;
23+
import com.sonar.sslr.api.AstNode;
24+
import com.sonar.sslr.api.AstNodeType;
25+
import com.sonar.sslr.api.Token;
26+
import java.util.List;
27+
import javax.annotation.Nullable;
28+
import org.sonar.python.api.PythonGrammar;
29+
import org.sonar.python.api.PythonTokenType;
30+
31+
/**
32+
* Extractor of docstring tokens.
33+
* <p>
34+
* Reminder: a docstring is a string literal that occurs as the first statement
35+
* in a module, function, class, or method definition.
36+
*/
37+
public class DocstringExtractor {
38+
39+
public static final List<AstNodeType> DOCUMENTABLE_NODE_TYPES = ImmutableList.of(PythonGrammar.FILE_INPUT, PythonGrammar.FUNCDEF, PythonGrammar.CLASSDEF);
40+
41+
private DocstringExtractor() {
42+
}
43+
44+
public static Token extractDocstring(AstNode documentableNode) {
45+
if (documentableNode.is(PythonGrammar.FILE_INPUT)) {
46+
return extractModuleDocstring(documentableNode);
47+
}
48+
return extractDocstringFromFirstSuite(documentableNode);
49+
}
50+
51+
private static Token extractModuleDocstring(AstNode astNode) {
52+
AstNode firstStatement = astNode.getFirstChild(PythonGrammar.STATEMENT);
53+
AstNode firstSimpleStmt = null;
54+
if (firstStatement != null) {
55+
firstSimpleStmt = getFirstSimpleStmt(firstStatement);
56+
}
57+
return extractDocstringFromSimpleStmt(firstSimpleStmt);
58+
}
59+
60+
private static AstNode getFirstSimpleStmt(AstNode statement) {
61+
AstNode stmtList = statement.getFirstChild(PythonGrammar.STMT_LIST);
62+
if (stmtList != null) {
63+
return stmtList.getFirstChild(PythonGrammar.SIMPLE_STMT);
64+
}
65+
return null;
66+
}
67+
68+
private static Token extractDocstringFromFirstSuite(AstNode documentableNode) {
69+
AstNode suite = documentableNode.getFirstChild(PythonGrammar.SUITE);
70+
AstNode firstStatement = suite.getFirstChild(PythonGrammar.STATEMENT);
71+
AstNode firstSimpleStmt;
72+
if (firstStatement == null) {
73+
firstSimpleStmt = suite.getFirstChild(PythonGrammar.STMT_LIST).getFirstChild(PythonGrammar.SIMPLE_STMT);
74+
} else {
75+
firstSimpleStmt = getFirstSimpleStmt(firstStatement);
76+
}
77+
return extractDocstringFromSimpleStmt(firstSimpleStmt);
78+
}
79+
80+
private static Token extractDocstringFromSimpleStmt(@Nullable AstNode firstSimpleStmt) {
81+
if (firstSimpleStmt != null) {
82+
Token token = firstSimpleStmt.getToken();
83+
if (token.getType().equals(PythonTokenType.STRING)) {
84+
return token;
85+
}
86+
}
87+
return null;
88+
}
89+
90+
}
Collapse file

‎python-squid/src/main/java/org/sonar/python/PythonAstScanner.java‎

Copy file name to clipboardExpand all lines: python-squid/src/main/java/org/sonar/python/PythonAstScanner.java
+6-11Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import com.sonar.sslr.api.AstNodeType;
2525
import com.sonar.sslr.api.Grammar;
2626
import com.sonar.sslr.impl.Parser;
27+
import java.io.File;
28+
import java.util.Collection;
2729
import org.sonar.python.api.PythonGrammar;
2830
import org.sonar.python.api.PythonKeyword;
2931
import org.sonar.python.api.PythonMetric;
@@ -40,14 +42,10 @@
4042
import org.sonar.squidbridge.api.SourceFunction;
4143
import org.sonar.squidbridge.api.SourceProject;
4244
import org.sonar.squidbridge.indexer.QueryByType;
43-
import org.sonar.squidbridge.metrics.CommentsVisitor;
4445
import org.sonar.squidbridge.metrics.ComplexityVisitor;
4546
import org.sonar.squidbridge.metrics.CounterVisitor;
4647
import org.sonar.squidbridge.metrics.LinesVisitor;
4748

48-
import java.io.File;
49-
import java.util.Collection;
50-
5149
public final class PythonAstScanner {
5250

5351
private PythonAstScanner() {
@@ -56,6 +54,7 @@ private PythonAstScanner() {
5654
/**
5755
* Helper method for testing checks without having to deploy them on a Sonar instance.
5856
*/
57+
@SuppressWarnings("unchecked")
5958
public static SourceFile scanSingleFile(String path, SquidAstVisitor<Grammar>... visitors) {
6059
File file = new File(path);
6160
if (!file.isFile()) {
@@ -86,7 +85,7 @@ public static AstScanner<Grammar> create(PythonConfiguration conf, SquidAstVisit
8685

8786
setMethodAnalyser(builder);
8887

89-
setMetrics(conf, builder);
88+
setMetrics(builder);
9089

9190
/* External visitors (typically Check ones) */
9291
for (SquidAstVisitor<Grammar> visitor : visitors) {
@@ -99,9 +98,8 @@ public static AstScanner<Grammar> create(PythonConfiguration conf, SquidAstVisit
9998
return builder.build();
10099
}
101100

102-
private static void setMetrics(PythonConfiguration conf, AstScanner.Builder<Grammar> builder) {
101+
private static void setMetrics(AstScanner.Builder<Grammar> builder) {
103102
builder.withSquidAstVisitor(new LinesVisitor<Grammar>(PythonMetric.LINES));
104-
builder.withSquidAstVisitor(new PythonLinesOfCodeVisitor<Grammar>(PythonMetric.LINES_OF_CODE));
105103
AstNodeType[] complexityAstNodeType = new AstNodeType[]{
106104
// Entry points
107105
PythonGrammar.FUNCDEF,
@@ -119,15 +117,12 @@ private static void setMetrics(PythonConfiguration conf, AstScanner.Builder<Gram
119117
PythonKeyword.AND,
120118
PythonKeyword.OR
121119
};
120+
122121
builder.withSquidAstVisitor(ComplexityVisitor.<Grammar>builder()
123122
.setMetricDef(PythonMetric.COMPLEXITY)
124123
.subscribeTo(complexityAstNodeType)
125124
.build());
126125

127-
builder.withSquidAstVisitor(CommentsVisitor.<Grammar>builder().withCommentMetric(PythonMetric.COMMENT_LINES)
128-
.withNoSonar(true)
129-
.withIgnoreHeaderComment(conf.getIgnoreHeaderComments())
130-
.build());
131126
builder.withSquidAstVisitor(CounterVisitor.<Grammar>builder()
132127
.setMetricDef(PythonMetric.STATEMENTS)
133128
.subscribeTo(PythonGrammar.STATEMENT)
Collapse file

‎python-squid/src/main/java/org/sonar/python/PythonLinesOfCodeVisitor.java‎

Copy file name to clipboardExpand all lines: python-squid/src/main/java/org/sonar/python/PythonLinesOfCodeVisitor.java
-71Lines changed: 0 additions & 71 deletions
This file was deleted.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.