Skip to content

Commit c077c88

Browse files
authored
Merge pull request #27 from B3ND3L/main
[Issue #26][EC89] Avoid unlimited cache
2 parents 325296d + 316958a commit c077c88

File tree

8 files changed

+162
-2
lines changed

8 files changed

+162
-2
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- [#26](https://github.com/green-code-initiative/ecoCode-python/issues/26) [EC89] Avoid unlimited cache
13+
1214
### Changed
1315

1416
### Deleted

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
<mockito.version>5.3.1</mockito.version>
6060

6161
<!-- temporary version waiting for real automatic release in ecocode repository -->
62-
<ecocode-rules-specifications.version>1.4.7</ecocode-rules-specifications.version>
62+
<ecocode-rules-specifications.version>1.6.0</ecocode-rules-specifications.version>
6363

6464
<sonar-analyzer-commons.version>2.5.0.1358</sonar-analyzer-commons.version>
6565

src/main/java/fr/greencodeinitiative/python/PythonRuleRepository.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public List<Class> checkClasses() {
6060
AvoidGlobalVariableInFunctionCheck.class,
6161
AvoidSQLRequestInLoop.class,
6262
AvoidTryCatchWithFileOpenedCheck.class,
63+
AvoidUnlimitedCache.class,
6364
AvoidUnoptimizedVectorImagesCheck.class,
6465
NoFunctionCallWhenDeclaringForLoop.class,
6566
AvoidFullSQLRequest.class,
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* ecoCode - Python language - Provides rules to reduce the environmental footprint of your Python programs
3+
* Copyright © 2023 Green Code Initiative (https://www.ecocode.io)
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package fr.greencodeinitiative.python.checks;
19+
20+
import org.sonar.check.Rule;
21+
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
22+
import org.sonar.plugins.python.api.SubscriptionContext;
23+
import org.sonar.plugins.python.api.tree.CallExpression;
24+
import org.sonar.plugins.python.api.tree.Decorator;
25+
import org.sonar.plugins.python.api.tree.FunctionDef;
26+
import org.sonar.plugins.python.api.tree.Name;
27+
import org.sonar.plugins.python.api.tree.RegularArgument;
28+
import org.sonar.plugins.python.api.tree.Tree;
29+
30+
@Rule(key = "EC89")
31+
public class AvoidUnlimitedCache extends PythonSubscriptionCheck {
32+
33+
public static final String DESCRIPTION = "Do not set cache size to unlimited";
34+
35+
public static final String LRU_CACHE = "lru_cache";
36+
public static final String MAX_SIZE_ARGUMENT = "maxsize";
37+
38+
public static final String CACHE = "cache";
39+
40+
@Override
41+
public void initialize(Context context) {
42+
// Check function decorators
43+
context.registerSyntaxNodeConsumer(Tree.Kind.FUNCDEF, this::checkFunction);
44+
}
45+
46+
private void checkFunction(SubscriptionContext ctx) {
47+
FunctionDef function = (FunctionDef) ctx.syntaxNode();
48+
49+
function.decorators().forEach(decorator -> {
50+
// If decorator is @cache
51+
if (isCacheDecorator(decorator)) {
52+
ctx.addIssue(decorator, AvoidUnlimitedCache.DESCRIPTION);
53+
// If decorator is @lru_cache
54+
} else if (isLruCacheDecorator(decorator)
55+
&& decorator.arguments() != null
56+
&& decorator.arguments().arguments() != null
57+
) {
58+
decorator.arguments().arguments().forEach(arg -> {
59+
RegularArgument regArg = (RegularArgument) arg;
60+
if (MAX_SIZE_ARGUMENT.equals(regArg.keywordArgument().name()) && regArg.expression().is(Tree.Kind.NONE)) {
61+
ctx.addIssue(decorator, AvoidUnlimitedCache.DESCRIPTION);
62+
}
63+
});
64+
}
65+
});
66+
}
67+
68+
private boolean isCacheDecorator(Decorator decorator) {
69+
return isDecorator(decorator, CACHE);
70+
}
71+
72+
private boolean isLruCacheDecorator(Decorator decorator) {
73+
return isDecorator(decorator, LRU_CACHE);
74+
}
75+
76+
private boolean isDecorator(Decorator decorator, String expression) {
77+
Name name = null;
78+
// Manage decarator detected as simple expression
79+
if (decorator.expression().is(Tree.Kind.NAME)) {
80+
name = (Name) decorator.expression();
81+
// manage decorator detected as callable expression
82+
} else if(decorator.expression().is(Tree.Kind.CALL_EXPR)) {
83+
CallExpression callExpression = (CallExpression) decorator.expression();
84+
if (callExpression.callee().is(Tree.Kind.NAME)) {
85+
name = (Name) callExpression.callee();
86+
}
87+
}
88+
return name != null && expression.equals(name.name());
89+
}
90+
}

src/test/java/fr/greencodeinitiative/python/PythonRuleRepositoryTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ void testMetadata() {
7878

7979
@Test
8080
void testRegistredRules() {
81-
assertThat(repository.rules()).hasSize(11);
81+
assertThat(repository.rules()).hasSize(12);
8282
}
8383

8484
@Test
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* ecoCode - Python language - Provides rules to reduce the environmental footprint of your Python programs
3+
* Copyright © 2023 Green Code Initiative (https://www.ecocode.io)
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package fr.greencodeinitiative.python.checks;
19+
20+
import org.junit.Test;
21+
import org.sonar.python.checks.utils.PythonCheckVerifier;
22+
23+
public class AvoidUnlimitedCacheTest {
24+
@Test
25+
public void test() {
26+
PythonCheckVerifier.verify("src/test/resources/checks/avoidUnlimitedCacheNonCompliant.py", new AvoidUnlimitedCache());
27+
PythonCheckVerifier.verifyNoIssue("src/test/resources/checks/avoidUnlimitedCacheCompliant.py", new AvoidUnlimitedCache());
28+
}
29+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from functools import lru_cache
2+
3+
4+
@lru_cache
5+
def cached_function():
6+
print('a')
7+
8+
9+
@lru_cache()
10+
def cached_function_a():
11+
print('a')
12+
13+
14+
@lru_cache(maxsize=30)
15+
def cached_function_b():
16+
print('b')
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from functools import cache
2+
from functools import lru_cache
3+
4+
5+
class A:
6+
@cache # Noncompliant {{Do not set cache size to unlimited}}
7+
def cached_method_a(self):
8+
print('a')
9+
10+
@lru_cache(maxsize=None) # Noncompliant {{Do not set cache size to unlimited}}
11+
def cached_method_b(self):
12+
print('b')
13+
14+
15+
@cache # Noncompliant {{Do not set cache size to unlimited}}
16+
def cached_function():
17+
print('a')
18+
19+
20+
@lru_cache(maxsize=None) # Noncompliant {{Do not set cache size to unlimited}}
21+
def cached_method():
22+
print('b')

0 commit comments

Comments
 (0)