Skip to content

Commit 00a46a9

Browse files
SONARPY-484 Rule S3985: Unused private nested classes should be removed
1 parent e8551bb commit 00a46a9

File tree

7 files changed

+155
-0
lines changed

7 files changed

+155
-0
lines changed

python-checks/src/main/java/org/sonar/python/checks/CheckList.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ public static Iterable<Class> getChecks() {
170170
UndeclaredNameUsageCheck.class,
171171
UnreachableExceptCheck.class,
172172
UnreadPrivateAttributesCheck.class,
173+
UnreadPrivateInnerClassesCheck.class,
173174
UnreadPrivateMethodsCheck.class,
174175
UnusedLocalVariableCheck.class,
175176
UnusedNestedDefinitionCheck.class,
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2020 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.checks;
21+
22+
import org.sonar.check.Rule;
23+
import org.sonar.plugins.python.api.symbols.Symbol;
24+
25+
import static org.sonar.plugins.python.api.symbols.Symbol.Kind.CLASS;
26+
27+
@Rule(key = "S3985")
28+
public class UnreadPrivateInnerClassesCheck extends AbstractUnreadPrivateMembersCheck {
29+
@Override
30+
String memberPrefix() {
31+
return "_";
32+
}
33+
34+
@Override
35+
Symbol.Kind kind() {
36+
return CLASS;
37+
}
38+
39+
@Override
40+
String message(String memberName) {
41+
return "Remove this unused private '" + memberName + "' class.";
42+
}
43+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<p>"Private" nested classes that are never used inside the enclosing class are usually dead code: unnecessary, inoperative code that should be
2+
removed. Cleaning out dead code decreases the size of the maintained codebase, making it easier to understand the program and preventing bugs from
3+
being introduced.</p>
4+
<p>Python has no real private classes. Every class is accessible. There are however two conventions indicating that a class is not meant to be
5+
"public":</p>
6+
<ul>
7+
<li> classes with a name starting with a single underscore (ex: <code>_MyClass</code>) should be seen as non-public and might change without prior
8+
notice. They should not be used by third-party libraries or software. It is ok to use those classes inside the library defining them but it should
9+
be done with caution. </li>
10+
<li> "class-private" classes are defined inside another class, and have a name starting with at least two underscores and ending with at most one
11+
underscore. These classes' names will be automatically mangled to avoid collision with subclasses' nested classes. For example
12+
<code>__MyClass</code> will be renamed as <code>_classname__MyClass</code>, where <code>classname</code> is the enclosing class's name without its
13+
leading underscore(s). Class-Private classes shouldn't be used outside of their enclosing class. </li>
14+
</ul>
15+
<p>This rule raises an issue when a private nested class (either with one or two leading underscores) is never used inside its parent class.</p>
16+
<h2>Noncompliant Code Example</h2>
17+
<pre>
18+
class Noncompliant:
19+
class __MyClas1s(): # Noncompliant
20+
pass
21+
22+
class _MyClass2(): # Noncompliant
23+
pass
24+
</pre>
25+
<h2>Compliant Solution</h2>
26+
<pre>
27+
class Compliant:
28+
class __MyClass1():
29+
pass
30+
31+
class _MyClass2():
32+
pass
33+
34+
def process(self):
35+
return Compliant.__MyClass1()
36+
37+
def process(self):
38+
return Compliant._MyClass2()
39+
</pre>
40+
<h2>See</h2>
41+
<ul>
42+
<li> <a href="https://docs.python.org/3.8/tutorial/classes.html#private-variables">Python documentation <del></del> Private Variables</a> </li>
43+
<li> <a href="https://www.python.org/dev/peps/pep-0008/#designing-for-inheritance">PEP 8 <del></del> Style Guide for Python Code</a> </li>
44+
</ul>
45+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"title": "Unused private nested classes should be removed",
3+
"type": "CODE_SMELL",
4+
"status": "ready",
5+
"remediation": {
6+
"func": "Constant\/Issue",
7+
"constantCost": "2min"
8+
},
9+
"tags": [
10+
"unused"
11+
],
12+
"defaultSeverity": "Major",
13+
"ruleSpecification": "RSPEC-3985",
14+
"sqKey": "S3985",
15+
"scope": "All"
16+
}

python-checks/src/main/resources/org/sonar/l10n/py/rules/python/Sonar_way_profile.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"S3827",
6262
"S3923",
6363
"S3981",
64+
"S3985",
6465
"S4143",
6566
"S4144",
6667
"S4423",
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2020 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.checks;
21+
22+
import org.junit.Test;
23+
import org.sonar.python.checks.utils.PythonCheckVerifier;
24+
25+
public class UnreadPrivateInnerClassesCheckTest {
26+
27+
@Test
28+
public void test() {
29+
PythonCheckVerifier.verify("src/test/resources/checks/unreadPrivateInnerClasses.py", new UnreadPrivateInnerClassesCheck());
30+
}
31+
32+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
class A:
2+
__foo = 42 # OK, raised by S4487
3+
4+
def __unused(self): ... # OK, raised by S1144
5+
6+
class __unused_cls: ... # Noncompliant {{Remove this unused private '__unused_cls' class.}}
7+
# ^^^^^^^^^^^^
8+
9+
class _unused_cls: ... # Noncompliant
10+
11+
class __used_cls: ...
12+
13+
class __other_used_cls: ...
14+
15+
def __init__(self):
16+
print(self.__used_cls)
17+
print(A.__other_used_cls)

0 commit comments

Comments
 (0)