diff --git a/CHANGELOG.md b/CHANGELOG.md
index f97f08d..ea66bd1 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..1903261
--- /dev/null
+++ b/src/main/java/org/greencodeinitiative/creedengo/python/checks/PreferAppendLeft.java
@@ -0,0 +1,71 @@
+/*
+ * 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 = "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";
+
+ @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) {
+ Expression firstArg;
+ firstArg = ((RegularArgument) arguments.get(0)).expression();
+ if (firstArg.is(NUMERIC_LITERAL)) {
+ 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) || "0.0".equals(value) || "0.00".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..39e15b9
--- /dev/null
+++ b/src/test/resources/checks/preferAppendLeft.py
@@ -0,0 +1,61 @@
+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}}
+
+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}}
+
+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}}
+
+[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")
+
+
+other_list = []
+position = 1
+other_list.insert(position, "ok")
+
+
+
+val = "new"
+queue = deque()
+queue.appendleft(val)
+