diff --git a/CHANGELOG.md b/CHANGELOG.md
index 44f7fc9..998fb37 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
+- [#26](https://github.com/green-code-initiative/ecoCode-python/issues/26) [EC89] Avoid unlimited cache
+
### Changed
### Deleted
diff --git a/pom.xml b/pom.xml
index 554966f..9ceee8d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -59,7 +59,7 @@
5.3.1
- 1.4.7
+ 1.6.0
2.5.0.1358
diff --git a/src/main/java/fr/greencodeinitiative/python/PythonRuleRepository.java b/src/main/java/fr/greencodeinitiative/python/PythonRuleRepository.java
index c9ba42d..fb3b20f 100644
--- a/src/main/java/fr/greencodeinitiative/python/PythonRuleRepository.java
+++ b/src/main/java/fr/greencodeinitiative/python/PythonRuleRepository.java
@@ -60,6 +60,7 @@ public List checkClasses() {
AvoidGlobalVariableInFunctionCheck.class,
AvoidSQLRequestInLoop.class,
AvoidTryCatchWithFileOpenedCheck.class,
+ AvoidUnlimitedCache.class,
AvoidUnoptimizedVectorImagesCheck.class,
NoFunctionCallWhenDeclaringForLoop.class,
AvoidFullSQLRequest.class,
diff --git a/src/main/java/fr/greencodeinitiative/python/checks/AvoidUnlimitedCache.java b/src/main/java/fr/greencodeinitiative/python/checks/AvoidUnlimitedCache.java
new file mode 100644
index 0000000..1f62d87
--- /dev/null
+++ b/src/main/java/fr/greencodeinitiative/python/checks/AvoidUnlimitedCache.java
@@ -0,0 +1,90 @@
+/*
+ * ecoCode - Python language - Provides rules to reduce the environmental footprint of your Python programs
+ * Copyright © 2023 Green Code Initiative (https://www.ecocode.io)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package fr.greencodeinitiative.python.checks;
+
+import org.sonar.check.Rule;
+import org.sonar.plugins.python.api.PythonSubscriptionCheck;
+import org.sonar.plugins.python.api.SubscriptionContext;
+import org.sonar.plugins.python.api.tree.CallExpression;
+import org.sonar.plugins.python.api.tree.Decorator;
+import org.sonar.plugins.python.api.tree.FunctionDef;
+import org.sonar.plugins.python.api.tree.Name;
+import org.sonar.plugins.python.api.tree.RegularArgument;
+import org.sonar.plugins.python.api.tree.Tree;
+
+@Rule(key = "EC89")
+public class AvoidUnlimitedCache extends PythonSubscriptionCheck {
+
+ public static final String DESCRIPTION = "Do not set cache size to unlimited";
+
+ public static final String LRU_CACHE = "lru_cache";
+ public static final String MAX_SIZE_ARGUMENT = "maxsize";
+
+ public static final String CACHE = "cache";
+
+ @Override
+ public void initialize(Context context) {
+ // Check function decorators
+ context.registerSyntaxNodeConsumer(Tree.Kind.FUNCDEF, this::checkFunction);
+ }
+
+ private void checkFunction(SubscriptionContext ctx) {
+ FunctionDef function = (FunctionDef) ctx.syntaxNode();
+
+ function.decorators().forEach(decorator -> {
+ // If decorator is @cache
+ if (isCacheDecorator(decorator)) {
+ ctx.addIssue(decorator, AvoidUnlimitedCache.DESCRIPTION);
+ // If decorator is @lru_cache
+ } else if (isLruCacheDecorator(decorator)
+ && decorator.arguments() != null
+ && decorator.arguments().arguments() != null
+ ) {
+ decorator.arguments().arguments().forEach(arg -> {
+ RegularArgument regArg = (RegularArgument) arg;
+ if (MAX_SIZE_ARGUMENT.equals(regArg.keywordArgument().name()) && regArg.expression().is(Tree.Kind.NONE)) {
+ ctx.addIssue(decorator, AvoidUnlimitedCache.DESCRIPTION);
+ }
+ });
+ }
+ });
+ }
+
+ private boolean isCacheDecorator(Decorator decorator) {
+ return isDecorator(decorator, CACHE);
+ }
+
+ private boolean isLruCacheDecorator(Decorator decorator) {
+ return isDecorator(decorator, LRU_CACHE);
+ }
+
+ private boolean isDecorator(Decorator decorator, String expression) {
+ Name name = null;
+ // Manage decarator detected as simple expression
+ if (decorator.expression().is(Tree.Kind.NAME)) {
+ name = (Name) decorator.expression();
+ // manage decorator detected as callable expression
+ } else if(decorator.expression().is(Tree.Kind.CALL_EXPR)) {
+ CallExpression callExpression = (CallExpression) decorator.expression();
+ if (callExpression.callee().is(Tree.Kind.NAME)) {
+ name = (Name) callExpression.callee();
+ }
+ }
+ return name != null && expression.equals(name.name());
+ }
+}
diff --git a/src/test/java/fr/greencodeinitiative/python/PythonRuleRepositoryTest.java b/src/test/java/fr/greencodeinitiative/python/PythonRuleRepositoryTest.java
index ca37f45..77db56f 100644
--- a/src/test/java/fr/greencodeinitiative/python/PythonRuleRepositoryTest.java
+++ b/src/test/java/fr/greencodeinitiative/python/PythonRuleRepositoryTest.java
@@ -78,7 +78,7 @@ void testMetadata() {
@Test
void testRegistredRules() {
- assertThat(repository.rules()).hasSize(11);
+ assertThat(repository.rules()).hasSize(12);
}
@Test
diff --git a/src/test/java/fr/greencodeinitiative/python/checks/AvoidUnlimitedCacheTest.java b/src/test/java/fr/greencodeinitiative/python/checks/AvoidUnlimitedCacheTest.java
new file mode 100644
index 0000000..e99f092
--- /dev/null
+++ b/src/test/java/fr/greencodeinitiative/python/checks/AvoidUnlimitedCacheTest.java
@@ -0,0 +1,29 @@
+/*
+ * ecoCode - Python language - Provides rules to reduce the environmental footprint of your Python programs
+ * Copyright © 2023 Green Code Initiative (https://www.ecocode.io)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package fr.greencodeinitiative.python.checks;
+
+import org.junit.Test;
+import org.sonar.python.checks.utils.PythonCheckVerifier;
+
+public class AvoidUnlimitedCacheTest {
+ @Test
+ public void test() {
+ PythonCheckVerifier.verify("src/test/resources/checks/avoidUnlimitedCacheNonCompliant.py", new AvoidUnlimitedCache());
+ PythonCheckVerifier.verifyNoIssue("src/test/resources/checks/avoidUnlimitedCacheCompliant.py", new AvoidUnlimitedCache());
+ }
+}
diff --git a/src/test/resources/checks/avoidUnlimitedCacheCompliant.py b/src/test/resources/checks/avoidUnlimitedCacheCompliant.py
new file mode 100644
index 0000000..aa37e6b
--- /dev/null
+++ b/src/test/resources/checks/avoidUnlimitedCacheCompliant.py
@@ -0,0 +1,16 @@
+from functools import lru_cache
+
+
+@lru_cache
+def cached_function():
+ print('a')
+
+
+@lru_cache()
+def cached_function_a():
+ print('a')
+
+
+@lru_cache(maxsize=30)
+def cached_function_b():
+ print('b')
diff --git a/src/test/resources/checks/avoidUnlimitedCacheNonCompliant.py b/src/test/resources/checks/avoidUnlimitedCacheNonCompliant.py
new file mode 100644
index 0000000..a05c706
--- /dev/null
+++ b/src/test/resources/checks/avoidUnlimitedCacheNonCompliant.py
@@ -0,0 +1,22 @@
+from functools import cache
+from functools import lru_cache
+
+
+class A:
+ @cache # Noncompliant {{Do not set cache size to unlimited}}
+ def cached_method_a(self):
+ print('a')
+
+ @lru_cache(maxsize=None) # Noncompliant {{Do not set cache size to unlimited}}
+ def cached_method_b(self):
+ print('b')
+
+
+@cache # Noncompliant {{Do not set cache size to unlimited}}
+def cached_function():
+ print('a')
+
+
+@lru_cache(maxsize=None) # Noncompliant {{Do not set cache size to unlimited}}
+def cached_method():
+ print('b')