diff --git a/CHANGELOG.md b/CHANGELOG.md index 24cc3c0de..cb03809f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#113](https://github.com/green-code-initiative/ecoCode/issues/113) new PYTHON rule : Use unoptimized vector images - [#127](https://github.com/green-code-initiative/ecoCode/issues/127) Add Python rule EC404: Usage of generator comprehension instead of list comprehension in for loop declaration +- [#192](https://github.com/green-code-initiative/ecoCode/pull/192) Add Python rule EC203: Detect unoptimized file formats ### Changed diff --git a/RULES.md b/RULES.md index dbbc7871e..1837f959c 100644 --- a/RULES.md +++ b/RULES.md @@ -48,3 +48,4 @@ Some are applicable for different technologies. | EC5 | Usage of preparedStatement instead of Statement | SQL will only commit the query once, whereas if you used only one statement, it would commit the query every time and thus induce unnecessary calculations by the CPU and therefore superfluous energy consumption. | | βœ… | 🚫 | 🚫 | 🚫 | 🚫 | | EC27 | Usage of system.arraycopy to copy arrays | Programs spend most of the time in loops. These can be resource consuming, especially when they integrate heavy processing (IO access). Moreover, the size of the data and processing inside the loops will not allow full use of hardware mechanisms such as the cache or compiler optimization mechanisms. | | βœ… | 🚫 | 🚫 | 🚫 | 🚫 | | EC404 | Avoid list comprehension in iterations | Use generator comprehension instead of list comprehension in for loop declaration | | 🚫 | 🚫 | 🚫 | βœ… | 🚫 | +| EC203 | Detect unoptimized file formats | When it is possible, to use svg format image over other image format | | πŸš€ | πŸš€ | πŸš€ | βœ… | πŸš€ | diff --git a/java-plugin/src/main/java/fr/greencodeinitiative/java/RulesList.java b/java-plugin/src/main/java/fr/greencodeinitiative/java/RulesList.java index d6a5cdbf4..8f2a47c47 100644 --- a/java-plugin/src/main/java/fr/greencodeinitiative/java/RulesList.java +++ b/java-plugin/src/main/java/fr/greencodeinitiative/java/RulesList.java @@ -24,25 +24,7 @@ import java.util.Collections; import java.util.List; -import fr.greencodeinitiative.java.checks.ArrayCopyCheck; -import fr.greencodeinitiative.java.checks.AvoidConcatenateStringsInLoop; -import fr.greencodeinitiative.java.checks.AvoidFullSQLRequest; -import fr.greencodeinitiative.java.checks.AvoidGettingSizeCollectionInLoop; -import fr.greencodeinitiative.java.checks.AvoidMultipleIfElseStatement; -import fr.greencodeinitiative.java.checks.AvoidRegexPatternNotStatic; -import fr.greencodeinitiative.java.checks.AvoidSQLRequestInLoop; -import fr.greencodeinitiative.java.checks.AvoidSetConstantInBatchUpdate; -import fr.greencodeinitiative.java.checks.AvoidSpringRepositoryCallInLoopCheck; -import fr.greencodeinitiative.java.checks.AvoidStatementForDMLQueries; -import fr.greencodeinitiative.java.checks.AvoidUsageOfStaticCollections; -import fr.greencodeinitiative.java.checks.AvoidUsingGlobalVariablesCheck; -import fr.greencodeinitiative.java.checks.FreeResourcesOfAutoCloseableInterface; -import fr.greencodeinitiative.java.checks.IncrementCheck; -import fr.greencodeinitiative.java.checks.InitializeBufferWithAppropriateSize; -import fr.greencodeinitiative.java.checks.NoFunctionCallWhenDeclaringForLoop; -import fr.greencodeinitiative.java.checks.OptimizeReadFileExceptions; -import fr.greencodeinitiative.java.checks.UnnecessarilyAssignValuesToVariables; -import fr.greencodeinitiative.java.checks.UseCorrectForLoop; +import fr.greencodeinitiative.java.checks.*; import org.sonar.plugins.java.api.JavaCheck; public final class RulesList { diff --git a/python-plugin/src/main/java/fr/greencodeinitiative/python/PythonRuleRepository.java b/python-plugin/src/main/java/fr/greencodeinitiative/python/PythonRuleRepository.java index ff96bd13d..484cae93f 100644 --- a/python-plugin/src/main/java/fr/greencodeinitiative/python/PythonRuleRepository.java +++ b/python-plugin/src/main/java/fr/greencodeinitiative/python/PythonRuleRepository.java @@ -91,7 +91,8 @@ public List checkClasses() { AvoidUnoptimizedVectorImagesCheck.class, NoFunctionCallWhenDeclaringForLoop.class, AvoidFullSQLRequest.class, - AvoidListComprehensionInIterations.class + AvoidListComprehensionInIterations.class, + DetectUnoptimizedImageFormat.class ); } diff --git a/python-plugin/src/main/java/fr/greencodeinitiative/python/checks/DetectUnoptimizedImageFormat.java b/python-plugin/src/main/java/fr/greencodeinitiative/python/checks/DetectUnoptimizedImageFormat.java new file mode 100644 index 000000000..5cd3c48c7 --- /dev/null +++ b/python-plugin/src/main/java/fr/greencodeinitiative/python/checks/DetectUnoptimizedImageFormat.java @@ -0,0 +1,41 @@ +package fr.greencodeinitiative.python.checks; + +import org.sonar.check.Priority; +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.StringLiteral; +import org.sonar.plugins.python.api.tree.Tree; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Rule( + key = DetectUnoptimizedImageFormat.RULE_KEY, + name = DetectUnoptimizedImageFormat.MESSAGERULE, + description = DetectUnoptimizedImageFormat.MESSAGEERROR, + priority = Priority.MINOR, + tags = {"eco-design", "ecocode", "performance", "user-experience"}) +public class DetectUnoptimizedImageFormat extends PythonSubscriptionCheck { + + protected static final String RULE_KEY = "EC203"; + protected static final String MESSAGERULE = "Detect unoptimized image format"; + protected static final String MESSAGEERROR = "If possible, the utilisation of svg image format (or html tag) is recommended over other image format."; + protected static final Pattern IMGEXTENSION = Pattern.compile("\\.(bmp|ico|tiff|webp|png|jpg|jpeg|jfif|pjpeg|pjp|gif|avif|apng)"); + + @Override + public void initialize(Context context) { + context.registerSyntaxNodeConsumer(Tree.Kind.STRING_LITERAL, this::visitNodeString); + } + + public void visitNodeString(SubscriptionContext ctx) { + if (ctx.syntaxNode().is(Tree.Kind.STRING_LITERAL)) { + final StringLiteral stringLiteral = (StringLiteral) ctx.syntaxNode(); + final String strValue = stringLiteral.trimmedQuotesValue(); + final Matcher matcher = IMGEXTENSION.matcher(strValue); + if(matcher.find()) { + ctx.addIssue(stringLiteral, MESSAGEERROR); + } + } + } +} diff --git a/python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC203.html b/python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC203.html new file mode 100644 index 000000000..65516edfc --- /dev/null +++ b/python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC203.html @@ -0,0 +1,73 @@ +

If possible, the utilisation of svg image format (or <svg/> html tag) is recommended over other image format.

+

Because SVGs are generally smaller than other image format, they’re less taxing on your server despite needing to render on load.

+

When to use SVG : +

+

+

Some advantages of using SVG : +

+

+ + +

Noncompliant Code Example

+
+    ...
+    img_jpg = "image.jpg"
+    ...
+
+

Compliant Solution

+
+    ...
+    img_svg = "image.svg"
+    ...
+
+ +

Noncompliant Code Example

+
+    public void foo() {
+        ...
+        image_format = testImage("image.jpg")
+        ...
+    }
+
+

Compliant Solution

+
+    public void foo() {
+        ...
+        image_format = testImage("image.svg")
+        ...
+   }
+
+ +

Noncompliant Code Example

+
+    public void foo() {
+        ...
+        return '<html><img src="xx/xx/image.bmp"></html>'
+        ...
+    }
+
+

Compliant Solution

+
+    public void foo() {
+        ...
+        return '<html><img src="xx/xx/image.svg"></html>'
+        ...
+   }
+
+     public void foo2() {
+        ...
+        return ('<html><svg width="100" height="100">' +
+                '<circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow" />' +
+                '</svg></html>')
+        ...
+   }
+
diff --git a/python-plugin/src/test/java/fr/greencodeinitiative/python/PythonRuleRepositoryTest.java b/python-plugin/src/test/java/fr/greencodeinitiative/python/PythonRuleRepositoryTest.java index 3562dc92d..0c734cbfa 100644 --- a/python-plugin/src/test/java/fr/greencodeinitiative/python/PythonRuleRepositoryTest.java +++ b/python-plugin/src/test/java/fr/greencodeinitiative/python/PythonRuleRepositoryTest.java @@ -44,8 +44,8 @@ public void init() { public void test() { assertThat(pythonRuleRepository.repositoryKey()).isEqualTo(PythonRuleRepository.REPOSITORY_KEY); assertThat(context.repositories()).hasSize(1).extracting("key").containsExactly(pythonRuleRepository.repositoryKey()); - assertThat(context.repositories().get(0).rules()).hasSize(8); - assertThat(pythonRuleRepository.checkClasses()).hasSize(8); + assertThat(context.repositories().get(0).rules()).hasSize(9); + assertThat(pythonRuleRepository.checkClasses()).hasSize(9); } diff --git a/python-plugin/src/test/java/fr/greencodeinitiative/python/checks/DetectUnoptimizedImageFormatTest.java b/python-plugin/src/test/java/fr/greencodeinitiative/python/checks/DetectUnoptimizedImageFormatTest.java new file mode 100644 index 000000000..7c735f59f --- /dev/null +++ b/python-plugin/src/test/java/fr/greencodeinitiative/python/checks/DetectUnoptimizedImageFormatTest.java @@ -0,0 +1,13 @@ +package fr.greencodeinitiative.python.checks; + +import org.junit.Test; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class DetectUnoptimizedImageFormatTest { + + @Test + public void test() { + PythonCheckVerifier.verify("src/test/resources/checks/detectUnoptimizedImageFormat.py", new DetectUnoptimizedImageFormat()); + PythonCheckVerifier.verifyNoIssue("src/test/resources/checks/detectUnoptimizedImageFormatCompliant.py", new DetectUnoptimizedImageFormat()); + } +} diff --git a/python-plugin/src/test/resources/checks/avoidFullSQLRequest.py b/python-plugin/src/test/resources/checks/avoidFullSQLRequest.py index 217cb6c9a..66c6adb4c 100644 --- a/python-plugin/src/test/resources/checks/avoidFullSQLRequest.py +++ b/python-plugin/src/test/resources/checks/avoidFullSQLRequest.py @@ -8,5 +8,3 @@ def displayMessage(argument1): requestCompiliant = ' SeLeCt user FrOm myTable' displayMessage(requestNonCompiliant) displayMessage(requestCompiliant) - - diff --git a/python-plugin/src/test/resources/checks/avoidGettersAndSetters.py b/python-plugin/src/test/resources/checks/avoidGettersAndSetters.py index fd72053ad..538e79a79 100644 --- a/python-plugin/src/test/resources/checks/avoidGettersAndSetters.py +++ b/python-plugin/src/test/resources/checks/avoidGettersAndSetters.py @@ -23,4 +23,4 @@ def is_major(self): return self.age >= 18 def get_weight(self): # Noncompliant {{Avoid creating getter and setter methods in classes}} - return self.weight \ No newline at end of file + return self.weight diff --git a/python-plugin/src/test/resources/checks/avoidTryCatchFinallyCheck.py b/python-plugin/src/test/resources/checks/avoidTryCatchFinallyCheck.py index 1e24bb491..cbd081e0e 100644 --- a/python-plugin/src/test/resources/checks/avoidTryCatchFinallyCheck.py +++ b/python-plugin/src/test/resources/checks/avoidTryCatchFinallyCheck.py @@ -26,4 +26,4 @@ def boo(): if os.path.isfile(path): fh = open(path, 'r') print(fh.read()) - fh.close \ No newline at end of file + fh.close diff --git a/python-plugin/src/test/resources/checks/detectUnoptimizedImageFormat.py b/python-plugin/src/test/resources/checks/detectUnoptimizedImageFormat.py new file mode 100644 index 000000000..37ddac218 --- /dev/null +++ b/python-plugin/src/test/resources/checks/detectUnoptimizedImageFormat.py @@ -0,0 +1,36 @@ + +def testImage(image) : + return "path/to/" + image + + +def testImageFormat2() : + + img_bmp = "test/image.bmp" # Noncompliant {{If possible, the utilisation of svg image format (or html tag) is recommended over other image format.}} + img_ico = "image.ico" # Noncompliant {{If possible, the utilisation of svg image format (or html tag) is recommended over other image format.}} + img_tiff = "test/path/to/image.tiff" # Noncompliant {{If possible, the utilisation of svg image format (or html tag) is recommended over other image format.}} + img_webp = "test/path/to/" + "image.webp" # Noncompliant {{If possible, the utilisation of svg image format (or html tag) is recommended over other image format.}} + img_jpg = "image.jpg" # Noncompliant {{If possible, the utilisation of svg image format (or html tag) is recommended over other image format.}} + img_jpeg = "image.jpeg" # Noncompliant {{If possible, the utilisation of svg image format (or html tag) is recommended over other image format.}} + img_jfif = "image.jfif" # Noncompliant {{If possible, the utilisation of svg image format (or html tag) is recommended over other image format.}} + img_pjpeg = "image.pjpeg" # Noncompliant {{If possible, the utilisation of svg image format (or html tag) is recommended over other image format.}} + img_pjp = "image.pjp" # Noncompliant {{If possible, the utilisation of svg image format (or html tag) is recommended over other image format.}} + img_gif = "image.gif" # Noncompliant {{If possible, the utilisation of svg image format (or html tag) is recommended over other image format.}} + img_avif = "image.avif" # Noncompliant {{If possible, the utilisation of svg image format (or html tag) is recommended over other image format.}} + img_apng = "image.apng" # Noncompliant {{If possible, the utilisation of svg image format (or html tag) is recommended over other image format.}} + + image_format = testImage("image.jpg") # Noncompliant {{If possible, the utilisation of svg image format (or html tag) is recommended over other image format.}} + + return ('' # Noncompliant {{If possible, the utilisation of svg image format (or html tag) is recommended over other image format.}} + + '' # Noncompliant {{If possible, the utilisation of svg image format (or html tag) is recommended over other image format.}} + + '' # Noncompliant {{If possible, the utilisation of svg image format (or html tag) is recommended over other image format.}} + + '' # Noncompliant {{If possible, the utilisation of svg image format (or html tag) is recommended over other image format.}} + + '' # Noncompliant {{If possible, the utilisation of svg image format (or html tag) is recommended over other image format.}} + + '' # Noncompliant {{If possible, the utilisation of svg image format (or html tag) is recommended over other image format.}} + + '' # Noncompliant {{If possible, the utilisation of svg image format (or html tag) is recommended over other image format.}} + + '' # Noncompliant {{If possible, the utilisation of svg image format (or html tag) is recommended over other image format.}} + + '' # Noncompliant {{If possible, the utilisation of svg image format (or html tag) is recommended over other image format.}} + + '' # Noncompliant {{If possible, the utilisation of svg image format (or html tag) is recommended over other image format.}} + + '' # Noncompliant {{If possible, the utilisation of svg image format (or html tag) is recommended over other image format.}} + + '' # Noncompliant {{If possible, the utilisation of svg image format (or html tag) is recommended over other image format.}} + + '' # Noncompliant {{If possible, the utilisation of svg image format (or html tag) is recommended over other image format.}} + + '' ) diff --git a/python-plugin/src/test/resources/checks/detectUnoptimizedImageFormatCompliant.py b/python-plugin/src/test/resources/checks/detectUnoptimizedImageFormatCompliant.py new file mode 100644 index 000000000..205f125e7 --- /dev/null +++ b/python-plugin/src/test/resources/checks/detectUnoptimizedImageFormatCompliant.py @@ -0,0 +1,17 @@ + +def testImage(image) : + return "path/to/" + image + + +def testImageFormat2() : + + img_svg = "test/image.svg" # Compliant + + image_format = testImage("image.svg") # Compliant + + image_svg_html = ('' + # Compliant + '' + + '') + + return ('' # Compliant + + '' ) diff --git a/python-plugin/src/test/resources/checks/noFunctionCallWhenDeclaringForLoop.py b/python-plugin/src/test/resources/checks/noFunctionCallWhenDeclaringForLoop.py index a3755d3e3..97cb01e63 100644 --- a/python-plugin/src/test/resources/checks/noFunctionCallWhenDeclaringForLoop.py +++ b/python-plugin/src/test/resources/checks/noFunctionCallWhenDeclaringForLoop.py @@ -6,6 +6,4 @@ def my_function(): my_function() pass - - -my_function() \ No newline at end of file +my_function()