Skip to content

Commit

Permalink
Merge pull request #27 from B3ND3L/main
Browse files Browse the repository at this point in the history
[Issue #26][EC89] Avoid unlimited cache
  • Loading branch information
dedece35 authored Jun 5, 2024
2 parents 325296d + 316958a commit c077c88
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
<mockito.version>5.3.1</mockito.version>

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public List<Class> checkClasses() {
AvoidGlobalVariableInFunctionCheck.class,
AvoidSQLRequestInLoop.class,
AvoidTryCatchWithFileOpenedCheck.class,
AvoidUnlimitedCache.class,
AvoidUnoptimizedVectorImagesCheck.class,
NoFunctionCallWhenDeclaringForLoop.class,
AvoidFullSQLRequest.class,
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ void testMetadata() {

@Test
void testRegistredRules() {
assertThat(repository.rules()).hasSize(11);
assertThat(repository.rules()).hasSize(12);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
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());
}
}
16 changes: 16 additions & 0 deletions src/test/resources/checks/avoidUnlimitedCacheCompliant.py
Original file line number Diff line number Diff line change
@@ -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')
22 changes: 22 additions & 0 deletions src/test/resources/checks/avoidUnlimitedCacheNonCompliant.py
Original file line number Diff line number Diff line change
@@ -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')

0 comments on commit c077c88

Please sign in to comment.