Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- [#146](https://github.com/green-code-initiative/creedengo-python/pull/146) Add rule GCI24: Returned SQL results should be limited (avoid SELECT...FROM without LIMIT)
- Add rule GCI113: Prefer XGBoost over RandomForest for better energy efficiency

### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,28 @@ void testGCI203_compliant() {
checkIssuesForFile(filePath, ruleId, ruleMsg, startLines, endLines, SEVERITY, TYPE, EFFORT_1H);
}

@Test
void testGCI113_nonCompliant() {
String filePath = "src/GCI113/preferXGBoostOverRandomForestNonCompliant.py";
String ruleId = "creedengo-python:GCI113";
String ruleMsg = "Prefer XGBoost over RandomForest for better energy efficiency";
int[] startLines = new int[]{1, 2, 5, 8, 11};
int[] endLines = new int[]{1, 2, 5, 8, 11};

checkIssuesForFile(filePath, ruleId, ruleMsg, startLines, endLines, SEVERITY, TYPE, EFFORT_10MIN);
}

@Test
void testGCI113_compliant() {
String filePath = "src/GCI113/preferXGBoostOverRandomForestCompliant.py";
String ruleId = "creedengo-python:GCI113";
String ruleMsg = "Prefer XGBoost over RandomForest for better energy efficiency";
int[] startLines = new int[]{};
int[] endLines = new int[]{};

checkIssuesForFile(filePath, ruleId, ruleMsg, startLines, endLines, SEVERITY, TYPE, EFFORT_10MIN);
}

@Test
void testGCI404() {
String filePath = "src/GCI404/avoidListComprehensionInIterations.py";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from xgboost import XGBClassifier
from xgboost import XGBRegressor
from sklearn.ensemble import GradientBoostingClassifier


# Compliant: using XGBoost classifier
model = XGBClassifier(eval_metric="logloss")
model.fit(X_train, y_train)

# Compliant: using XGBoost regressor
model2 = XGBRegressor(n_estimators=100)
model2.fit(X_train, y_train)

# Compliant: using other sklearn classifiers
model3 = GradientBoostingClassifier(n_estimators=100)
model3.fit(X_train, y_train)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from sklearn.ensemble import RandomForestClassifier # Noncompliant {{Prefer XGBoost over RandomForest for better energy efficiency}}
from sklearn.ensemble import RandomForestRegressor # Noncompliant {{Prefer XGBoost over RandomForest for better energy efficiency}}

model = RandomForestClassifier(n_estimators=100) # Noncompliant {{Prefer XGBoost over RandomForest for better energy efficiency}}

model2 = RandomForestRegressor(n_estimators=50) # Noncompliant {{Prefer XGBoost over RandomForest for better energy efficiency}}

model3 = sklearn.ensemble.RandomForestClassifier(n_estimators=100) # Noncompliant {{Prefer XGBoost over RandomForest for better energy efficiency}}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ public record PythonRuleRepository(SonarRuntime sonarRuntime) implements RulesDe
GCI104AvoidCreatingTensorUsingNumpyOrNativePython.class,
GCI110AvoidWildcardImportsCheck.class,
GCI109AvoidExceptionsForControlFlowCheck.class,
GCI112UsingSlotsOnDataClasses.class
GCI112UsingSlotsOnDataClasses.class,
GCI113PreferXGBoostOverRandomForest.class
);

public static final String LANGUAGE = "py";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
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.ImportFrom;
import org.sonar.plugins.python.api.tree.Name;
import org.sonar.plugins.python.api.tree.QualifiedExpression;
import org.sonar.plugins.python.api.tree.Tree;

@Rule(key = "GCI113")
public class GCI113PreferXGBoostOverRandomForest extends PythonSubscriptionCheck {

private static final String DESCRIPTION = "Prefer XGBoost over RandomForest for better energy efficiency";
private static final String RANDOM_FOREST_CLASSIFIER = "RandomForestClassifier";
private static final String RANDOM_FOREST_REGRESSOR = "RandomForestRegressor";

@Override
public void initialize(Context context) {
context.registerSyntaxNodeConsumer(Tree.Kind.IMPORT_FROM, this::checkImport);
context.registerSyntaxNodeConsumer(Tree.Kind.CALL_EXPR, this::checkInstantiation);
}

private void checkImport(SubscriptionContext context) {
ImportFrom importFrom = (ImportFrom) context.syntaxNode();
if (importFrom.importedNames() == null) {
return;
}
importFrom.importedNames().forEach(aliasedName -> {
String importedName = aliasedName.dottedName().names().stream()
.map(Name::name)
.reduce((a, b) -> a + "." + b)
.orElse("");
if (RANDOM_FOREST_CLASSIFIER.equals(importedName) || RANDOM_FOREST_REGRESSOR.equals(importedName)) {
context.addIssue(aliasedName, DESCRIPTION);
}
});
}

private void checkInstantiation(SubscriptionContext context) {
CallExpression callExpression = (CallExpression) context.syntaxNode();
Expression callee = callExpression.callee();

if (callee.is(Tree.Kind.NAME)) {
Name name = (Name) callee;
if (RANDOM_FOREST_CLASSIFIER.equals(name.name()) || RANDOM_FOREST_REGRESSOR.equals(name.name())) {
context.addIssue(callExpression, DESCRIPTION);
}
} else if (callee.is(Tree.Kind.QUALIFIED_EXPR)) {
QualifiedExpression qualifiedExpr = (QualifiedExpression) callee;
String methodName = qualifiedExpr.name().name();
if (RANDOM_FOREST_CLASSIFIER.equals(methodName) || RANDOM_FOREST_REGRESSOR.equals(methodName)) {
context.addIssue(callExpression, DESCRIPTION);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"GCI110",
"GCI111",
"GCI112",
"GCI113",
"GCI203",
"GCI404"
]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.greencodeinitiative.creedengo.python.checks;

import org.junit.jupiter.api.Test;
import org.sonar.python.checks.utils.PythonCheckVerifier;

class GCI113PreferXGBoostOverRandomForestTest {

@Test
void testNonCompliant() {
PythonCheckVerifier.verify(System.getProperty("testfiles.path") + "/GCI113/preferXGBoostOverRandomForestNonCompliant.py", new GCI113PreferXGBoostOverRandomForest());
}

@Test
void testCompliant() {
PythonCheckVerifier.verifyNoIssue(System.getProperty("testfiles.path") + "/GCI113/preferXGBoostOverRandomForestCompliant.py", new GCI113PreferXGBoostOverRandomForest());
}
}