From c65a03b358cd5fe727cdf9816a82e41520f92e54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ophas=20Fournier?= Date: Mon, 19 May 2025 15:59:44 +0200 Subject: [PATCH 1/3] GCI97 PreferAppendLeft #Python #DLG #Build Co-authored-by: DataLabGroupe-CreditAgricole --- CHANGELOG.md | 2 + .../python/PythonRuleRepository.java | 3 +- .../python/checks/PreferAppendLeft.java | 73 +++++++++++++++++++ .../python/checks/PreferAppendLeftTest.java | 29 ++++++++ src/test/resources/checks/preferAppendLeft.py | 45 ++++++++++++ 5 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/greencodeinitiative/creedengo/python/checks/PreferAppendLeft.java create mode 100644 src/test/java/org/greencodeinitiative/creedengo/python/checks/PreferAppendLeftTest.java create mode 100644 src/test/resources/checks/preferAppendLeft.py diff --git a/CHANGELOG.md b/CHANGELOG.md index bea9b72..34c72ba 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 +- Add rule GCI97 Prefer Append Left (a rule to prefer the use of `append` over `insert` for list, using deques) + ### Changed - compatibility updates for SonarQube 25.5.0 diff --git a/src/main/java/org/greencodeinitiative/creedengo/python/PythonRuleRepository.java b/src/main/java/org/greencodeinitiative/creedengo/python/PythonRuleRepository.java index c385979..578a27d 100644 --- a/src/main/java/org/greencodeinitiative/creedengo/python/PythonRuleRepository.java +++ b/src/main/java/org/greencodeinitiative/creedengo/python/PythonRuleRepository.java @@ -40,7 +40,8 @@ public class PythonRuleRepository implements RulesDefinition, PythonCustomRuleRe AvoidFullSQLRequest.class, AvoidListComprehensionInIterations.class, DetectUnoptimizedImageFormat.class, - AvoidMultipleIfElseStatementCheck.class + AvoidMultipleIfElseStatementCheck.class, + PreferAppendLeft.class ); public static final String LANGUAGE = "py"; diff --git a/src/main/java/org/greencodeinitiative/creedengo/python/checks/PreferAppendLeft.java b/src/main/java/org/greencodeinitiative/creedengo/python/checks/PreferAppendLeft.java new file mode 100644 index 0000000..a711073 --- /dev/null +++ b/src/main/java/org/greencodeinitiative/creedengo/python/checks/PreferAppendLeft.java @@ -0,0 +1,73 @@ +/* + * creedengo - Python language - Provides rules to reduce the environmental footprint of your Python programs + * Copyright © 2024 Green Code Initiative (https://green-code-initiative.org) + * + * 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 org.greencodeinitiative.creedengo.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.Expression; +import org.sonar.plugins.python.api.tree.QualifiedExpression; +import org.sonar.plugins.python.api.tree.RegularArgument; +import org.sonar.plugins.python.api.tree.NumericLiteral; + +import java.util.List; + +import static org.sonar.plugins.python.api.tree.Tree.Kind.*; + +@Rule(key = "GCI302") +public class PreferAppendLeft extends PythonSubscriptionCheck { + public static final String DESCRIPTION = "Use appendleft with deque instead of .insert(0, val) for modification at the beginning of a list"; + + @Override + public void initialize(Context context) { + context.registerSyntaxNodeConsumer(CALL_EXPR, this::visitCallExpression); + } + + private void visitCallExpression(SubscriptionContext context) { + CallExpression callExpression = (CallExpression) context.syntaxNode(); + + if (callExpression.callee().is(QUALIFIED_EXPR)) { + QualifiedExpression qualifiedExpression = (QualifiedExpression) callExpression.callee(); + + + + if (qualifiedExpression.name().name().equals("insert")) { + List arguments = callExpression.arguments(); + + if (arguments.size() >= 2) {// because it should be like insert(0,val) so there's two arguments + Expression firstArg; + firstArg = ((RegularArgument) arguments.get(0)).expression(); + + if (isZeroLiteral(firstArg)) { + context.addIssue(callExpression, DESCRIPTION); + } + } + } + } + } + + private boolean isZeroLiteral(Expression expression) { + if (expression.is(NUMERIC_LITERAL)) { + NumericLiteral numericLiteral = (NumericLiteral) expression; + String value = numericLiteral.valueAsString(); + return "0".equals(value); + } + return false; + } +} \ No newline at end of file diff --git a/src/test/java/org/greencodeinitiative/creedengo/python/checks/PreferAppendLeftTest.java b/src/test/java/org/greencodeinitiative/creedengo/python/checks/PreferAppendLeftTest.java new file mode 100644 index 0000000..4ac3401 --- /dev/null +++ b/src/test/java/org/greencodeinitiative/creedengo/python/checks/PreferAppendLeftTest.java @@ -0,0 +1,29 @@ +/* + * creedengo - Python language - Provides rules to reduce the environmental footprint of your Python programs + * Copyright © 2024 Green Code Initiative (https://green-code-initiative.org) + * + * 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 org.greencodeinitiative.creedengo.python.checks; + +import org.junit.Test; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class PreferAppendLeftTest { + + @Test + public void test() { + PythonCheckVerifier.verify("src/test/resources/checks/preferAppendLeft.py", new PreferAppendLeft()); + } +} diff --git a/src/test/resources/checks/preferAppendLeft.py b/src/test/resources/checks/preferAppendLeft.py new file mode 100644 index 0000000..10dc686 --- /dev/null +++ b/src/test/resources/checks/preferAppendLeft.py @@ -0,0 +1,45 @@ +from collections import deque + +numbers = [] +numbers.insert(0, val) # Noncompliant {{Use appendleft with deque instead of .insert(0, val) for modification at the beginning of a list}} + +items = [] +items.insert(0, "start") # Noncompliant {{Use appendleft with deque instead of .insert(0, val) for modification at the beginning of a list}} + +mylist = [] +mylist.insert(0.0, val) + +a = 0 +mylist.insert(a, val) + +dq = deque() +dq.appendleft("start") + +data = [] +data.insert(1, "something") + +lst = [] +(lst).insert(0, 'x') # Noncompliant {{Use appendleft with deque instead of .insert(0, val) for modification at the beginning of a list}} + +x = [] +(x).insert(0, value) # Noncompliant {{Use appendleft with deque instead of .insert(0, val) for modification at the beginning of a list}} + +def insert_first(l, v): + l.insert(0, v) # Noncompliant {{Use appendleft with deque instead of .insert(0, val) for modification at the beginning of a list}} + +def add_to_front(dq, item): + dq.appendleft(item) + +some_list = [] +some_list.insert(index=0, object=val) # Noncompliant {{Use appendleft with deque instead of .insert(0, val) for modification at the beginning of a list}} + +deque_like = [] +deque_like.insert(0, "bad") # Noncompliant {{Use appendleft with deque instead of .insert(0, val) for modification at the beginning of a list}} + +real_deque = deque() +real_deque.appendleft("good") + +fn = getattr(mylist, "insert") +fn(0, val) + +[1, 2, 3].insert(0, 9) # Noncompliant {{Use appendleft with deque instead of .insert(0, val) for modification at the beginning of a list}} From 1635727acdb331fa74b550a290bf3f1f31ea27c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ophas=20Fournier?= Date: Mon, 19 May 2025 16:09:28 +0200 Subject: [PATCH 2/3] GCI97 PreferAppendLeft #Python #DLG #Build Co-authored-by: DataLabGroupe-CreditAgricole --- .../creedengo/python/checks/PreferAppendLeft.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/greencodeinitiative/creedengo/python/checks/PreferAppendLeft.java b/src/main/java/org/greencodeinitiative/creedengo/python/checks/PreferAppendLeft.java index a711073..8ed6032 100644 --- a/src/main/java/org/greencodeinitiative/creedengo/python/checks/PreferAppendLeft.java +++ b/src/main/java/org/greencodeinitiative/creedengo/python/checks/PreferAppendLeft.java @@ -30,7 +30,7 @@ import static org.sonar.plugins.python.api.tree.Tree.Kind.*; -@Rule(key = "GCI302") +@Rule(key = "GCI97") public class PreferAppendLeft extends PythonSubscriptionCheck { public static final String DESCRIPTION = "Use appendleft with deque instead of .insert(0, val) for modification at the beginning of a list"; From e557fc59d04d4eae0690f17e3bb926661890de72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ophas=20Fournier?= Date: Mon, 2 Jun 2025 10:22:08 +0200 Subject: [PATCH 3/3] GCI97 Add unit tests and detect 0.0 and 0.00 case Co-authored-by: DataLabGroupe-CreditAgricole --- .../python/checks/PreferAppendLeft.java | 14 ++--- src/test/resources/checks/preferAppendLeft.py | 56 ++++++++++++------- 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/greencodeinitiative/creedengo/python/checks/PreferAppendLeft.java b/src/main/java/org/greencodeinitiative/creedengo/python/checks/PreferAppendLeft.java index 8ed6032..1903261 100644 --- a/src/main/java/org/greencodeinitiative/creedengo/python/checks/PreferAppendLeft.java +++ b/src/main/java/org/greencodeinitiative/creedengo/python/checks/PreferAppendLeft.java @@ -30,6 +30,7 @@ import static org.sonar.plugins.python.api.tree.Tree.Kind.*; + @Rule(key = "GCI97") public class PreferAppendLeft extends PythonSubscriptionCheck { public static final String DESCRIPTION = "Use appendleft with deque instead of .insert(0, val) for modification at the beginning of a list"; @@ -41,21 +42,18 @@ public void initialize(Context context) { private void visitCallExpression(SubscriptionContext context) { CallExpression callExpression = (CallExpression) context.syntaxNode(); - if (callExpression.callee().is(QUALIFIED_EXPR)) { QualifiedExpression qualifiedExpression = (QualifiedExpression) callExpression.callee(); - - if (qualifiedExpression.name().name().equals("insert")) { List arguments = callExpression.arguments(); - - if (arguments.size() >= 2) {// because it should be like insert(0,val) so there's two arguments + if (arguments.size() >= 2) { Expression firstArg; firstArg = ((RegularArgument) arguments.get(0)).expression(); - - if (isZeroLiteral(firstArg)) { + if (firstArg.is(NUMERIC_LITERAL)) { + if (isZeroLiteral(firstArg)) { context.addIssue(callExpression, DESCRIPTION); + } } } } @@ -66,7 +64,7 @@ private boolean isZeroLiteral(Expression expression) { if (expression.is(NUMERIC_LITERAL)) { NumericLiteral numericLiteral = (NumericLiteral) expression; String value = numericLiteral.valueAsString(); - return "0".equals(value); + return "0".equals(value) || "0.0".equals(value) || "0.00".equals(value); } return false; } diff --git a/src/test/resources/checks/preferAppendLeft.py b/src/test/resources/checks/preferAppendLeft.py index 10dc686..39e15b9 100644 --- a/src/test/resources/checks/preferAppendLeft.py +++ b/src/test/resources/checks/preferAppendLeft.py @@ -1,23 +1,12 @@ from collections import deque +# Cas non conformes numbers = [] numbers.insert(0, val) # Noncompliant {{Use appendleft with deque instead of .insert(0, val) for modification at the beginning of a list}} items = [] items.insert(0, "start") # Noncompliant {{Use appendleft with deque instead of .insert(0, val) for modification at the beginning of a list}} -mylist = [] -mylist.insert(0.0, val) - -a = 0 -mylist.insert(a, val) - -dq = deque() -dq.appendleft("start") - -data = [] -data.insert(1, "something") - lst = [] (lst).insert(0, 'x') # Noncompliant {{Use appendleft with deque instead of .insert(0, val) for modification at the beginning of a list}} @@ -27,19 +16,46 @@ def insert_first(l, v): l.insert(0, v) # Noncompliant {{Use appendleft with deque instead of .insert(0, val) for modification at the beginning of a list}} -def add_to_front(dq, item): - dq.appendleft(item) - some_list = [] -some_list.insert(index=0, object=val) # Noncompliant {{Use appendleft with deque instead of .insert(0, val) for modification at the beginning of a list}} +some_list.insert(index=0, object=val) # Noncompliant {{Use appendleft with deque instead of .insert(0, val) for modification at the beginning of a list}} deque_like = [] deque_like.insert(0, "bad") # Noncompliant {{Use appendleft with deque instead of .insert(0, val) for modification at the beginning of a list}} +[1, 2, 3].insert(0, 9) # Noncompliant {{Use appendleft with deque instead of .insert(0, val) for modification at the beginning of a list}} + +class MyList(list): + pass + +custom_list = MyList() +custom_list.insert(0, 'z') # Noncompliant {{Use appendleft with deque instead of .insert(0, val) for modification at the beginning of a list}} + +def wrapper(): + lst = [] + lst.insert(0, "wrapped") # Noncompliant {{Use appendleft with deque instead of .insert(0, val) for modification at the beginning of a list}} + + +dq = deque() +dq.appendleft("start") + +mylist = [] +mylist.insert(0.0, val) # Noncompliant {{Use appendleft with deque instead of .insert(0, val) for modification at the beginning of a list}} + + +data = [] +data.insert(1, "something") + real_deque = deque() -real_deque.appendleft("good") +real_deque.appendleft("good") -fn = getattr(mylist, "insert") -fn(0, val) -[1, 2, 3].insert(0, 9) # Noncompliant {{Use appendleft with deque instead of .insert(0, val) for modification at the beginning of a list}} +other_list = [] +position = 1 +other_list.insert(position, "ok") + + + +val = "new" +queue = deque() +queue.appendleft(val) +