diff --git a/android-plugin/RULES.md b/android-plugin/RULES.md index df7ecc6d..dbf9c3ac 100644 --- a/android-plugin/RULES.md +++ b/android-plugin/RULES.md @@ -13,7 +13,7 @@ Android-specific rules rely on a multi-scope scanning, including Java source fil | EBOT004 | Uncached Data Reception | Java | Requires `PostProjectAnalysisTask()` callback | | ESOB009 | Day Night Mode | File System, Xml | Requires `PostProjectAnalysisTask()` callback | | ESOB015 | Extraneous Animation | Java, Xml, File System | | -| ESOBxxx | Extraneous Init | Java | | +| ESOB017 | Extraneous Init | Java | | | ESOB016 | Hardware acceleration | Xml | | | EPOW008 | Battery-constrained Work | Java | | | EBAT001 | Service@Boot-time | Java, Xml | Likely detectable in Xml only | diff --git a/android-plugin/src/main/java/io/ecocode/java/JavaCheckList.java b/android-plugin/src/main/java/io/ecocode/java/JavaCheckList.java index ac6e6c90..a43dd4aa 100644 --- a/android-plugin/src/main/java/io/ecocode/java/JavaCheckList.java +++ b/android-plugin/src/main/java/io/ecocode/java/JavaCheckList.java @@ -26,6 +26,8 @@ import io.ecocode.java.checks.environment.leakage.*; import io.ecocode.java.checks.environment.optimized_api.BluetoothLowEnergyRule; import io.ecocode.java.checks.environment.optimized_api.FusedLocationRule; +import io.ecocode.java.checks.environment.optimized_api.LazyLoadingComposeRule; +import io.ecocode.java.checks.environment.power.SaveModeAwarenessRule; import io.ecocode.java.checks.environment.power.ChargeAwarenessRule; import io.ecocode.java.checks.environment.power.SaveModeAwarenessRule; import io.ecocode.java.checks.environment.sobriety.*; @@ -93,7 +95,8 @@ public static List> getJavaEnergyChecks() { JobCoalesceRule.class, SaveModeAwarenessRule.class, ThriftyGeolocationCriteriaRule.class, - HighFrameRateRule.class + HighFrameRateRule.class, + LazyLoadingComposeRule.class )); } diff --git a/android-plugin/src/main/java/io/ecocode/java/checks/environment/optimized_api/LazyLoadingComposeRule.java b/android-plugin/src/main/java/io/ecocode/java/checks/environment/optimized_api/LazyLoadingComposeRule.java new file mode 100644 index 00000000..c15d01de --- /dev/null +++ b/android-plugin/src/main/java/io/ecocode/java/checks/environment/optimized_api/LazyLoadingComposeRule.java @@ -0,0 +1,80 @@ +package io.ecocode.java.checks.environment.optimized_api; + +import io.ecocode.java.checks.helpers.TreeHelper; +import org.sonar.check.Rule; +import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.JavaFileScannerContext; +import org.sonar.plugins.java.api.tree.ImportTree; +import org.sonar.plugins.java.api.tree.Tree; +import org.sonar.plugins.java.api.tree.IdentifierTree; +import org.sonarsource.analyzer.commons.annotations.DeprecatedRuleKey; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import org.sonar.plugins.java.api.tree.*; +import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.JavaFileScannerContext; + +@Rule(key = "EC533") +@DeprecatedRuleKey(repositoryKey = "ecoCode-android", ruleKey = "EOPT003") +public class LazyLoadingComposeRule extends IssuableSubscriptionVisitor { + + private static final String LAZY_LOADING_IMPORT = "androidx.compose.foundation.lazy"; + + private static final Set LEGACY_IMPORTS = Set.of( + "android.view.View", + "android.view.ViewGroup", + "android.widget.TextView", + "android.widget.ListView", + "android.widget.GridView", + "androidx.recyclerview.widget.RecyclerView" + ); + + private boolean hasSeenWrongImport = false; + private boolean hasSeenLazyImport = false; + private List wrongImports = new ArrayList<>(); + + @Override + public List nodesToVisit() { + return List.of(Tree.Kind.IMPORT); + } + + @Override + public void visitNode(Tree tree) { + if (tree.is(Tree.Kind.IMPORT)) { + checkImport((ImportTree) tree); + } + } + + private void checkImport(ImportTree importTree) { + String fullQualifiedName = TreeHelper.fullQualifiedName(importTree.qualifiedIdentifier()); + + if (LEGACY_IMPORTS.contains(fullQualifiedName)) { + hasSeenWrongImport = true; + wrongImports.add(importTree); + } + + if (fullQualifiedName.startsWith(LAZY_LOADING_IMPORT)) { + hasSeenLazyImport = true; + } + } + + @Override + public void leaveFile(JavaFileScannerContext context) { + if (hasSeenWrongImport && !hasSeenLazyImport) { + wrongImports.forEach(importTree -> + reportIssue(importTree, + "Prefer using lazy loading view components from Jetpack-Compose to save energy")); + } + + // Reset state + hasSeenWrongImport = false; + hasSeenLazyImport = false; + wrongImports.clear(); + } +} diff --git a/android-plugin/src/main/resources/io/ecocode/android/java/ecocode_java_profile.json b/android-plugin/src/main/resources/io/ecocode/android/java/ecocode_java_profile.json index 9e1b91c5..5d220c93 100644 --- a/android-plugin/src/main/resources/io/ecocode/android/java/ecocode_java_profile.json +++ b/android-plugin/src/main/resources/io/ecocode/android/java/ecocode_java_profile.json @@ -35,6 +35,7 @@ "EC531", "EC532", "EC533", - "EC534" + "EC534", + "EC535" ] } diff --git a/android-plugin/src/main/resources/io/ecocode/rules/EC535.html b/android-plugin/src/main/resources/io/ecocode/rules/EC535.html new file mode 100644 index 00000000..040c5ec3 --- /dev/null +++ b/android-plugin/src/main/resources/io/ecocode/rules/EC535.html @@ -0,0 +1,19 @@ + +

When displaying scrollable data on screen, the new Jetpack Compose API + introduced lazy views instead of ListView, GridView and even RecycleView. + These components use the technique of lazy loading, which consists of loading data +
only when it arrives at the display area. + Import androidx.compose.foundation.lazy.* to benefit from objects like LazyColumn, LazyRow, + LazyVerticalGrid or LazyHorizontalGrid. + +

+

Noncompliant Code Example

+
+    import ListView; 
+    import GridView;
+    import RecycleView;
+
+

Compliant Solution

+
+    import androidx.compose.foundation.lazy.*;
+
\ No newline at end of file diff --git a/android-plugin/src/main/resources/io/ecocode/rules/EC535.json b/android-plugin/src/main/resources/io/ecocode/rules/EC535.json new file mode 100644 index 00000000..cd068eda --- /dev/null +++ b/android-plugin/src/main/resources/io/ecocode/rules/EC535.json @@ -0,0 +1,17 @@ + { + "title": "Optimized API: Lazy Loading", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "1440min" + }, + "tags": [ + "optimized-api", + "environment", + "ecocode", + "android", + "eco-design" + ], + "defaultSeverity": "Major" + } \ No newline at end of file diff --git a/android-plugin/src/main/resources/io/ecocode/rules/java/EC533.json b/android-plugin/src/main/resources/io/ecocode/rules/java/EC533.json index ecd65d10..f08e30f7 100644 --- a/android-plugin/src/main/resources/io/ecocode/rules/java/EC533.json +++ b/android-plugin/src/main/resources/io/ecocode/rules/java/EC533.json @@ -14,4 +14,4 @@ "eco-design" ], "defaultSeverity": "Info" -} \ No newline at end of file +} diff --git a/android-plugin/src/test/files/environment/optimized_api/LazyLoadingCheck.java b/android-plugin/src/test/files/environment/optimized_api/LazyLoadingCheck.java new file mode 100644 index 00000000..162fba0f --- /dev/null +++ b/android-plugin/src/test/files/environment/optimized_api/LazyLoadingCheck.java @@ -0,0 +1,27 @@ +/* + * ecoCode Android plugin - Provides rules to reduce the environmental footprint of your Android applications + * Copyright © 2020 Green Code Initiative (contact@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 . + */ + + import androidx.compose.foundation.ScrollableColumn; // Noncompliant {{Use Lazy components (LazyColumn, LazyRow, etc.) from androidx.compose.foundation.lazy instead of scrollable components for better performance with large datasets.}} + import androidx.compose.foundation.VerticalScroller; // Noncompliant + import androidx.compose.foundation.HorizontalScroller; // Noncompliant + import androidx.compose.foundation.ScrollableRow; // Noncompliant + import androidx.compose.ui.Modifier; + + public class LazyLoadingCheck { + + } \ No newline at end of file diff --git a/android-plugin/src/test/files/environment/optimized_api/LazyLoadingCheckNoIssue.java b/android-plugin/src/test/files/environment/optimized_api/LazyLoadingCheckNoIssue.java new file mode 100644 index 00000000..d85678c2 --- /dev/null +++ b/android-plugin/src/test/files/environment/optimized_api/LazyLoadingCheckNoIssue.java @@ -0,0 +1,10 @@ +import androidx.compose.foundation.lazy.LazyColumn; +import androidx.compose.foundation.lazy.LazyRow; +import androidx.compose.foundation.lazy.items; +import androidx.compose.ui.Modifier; +import androidx.compose.foundation.lazy.LazyVerticalGrid; +import androidx.compose.foundation.lazy.LazyHorizontalGrid; +import androidx.compose.foundation.lazy.GridCells; + +public class Dijon { +} \ No newline at end of file diff --git a/android-plugin/src/test/java/io/ecocode/java/checks/environment/optimized_api/LazyLoadingRuleTest b/android-plugin/src/test/java/io/ecocode/java/checks/environment/optimized_api/LazyLoadingRuleTest new file mode 100644 index 00000000..ac920f66 --- /dev/null +++ b/android-plugin/src/test/java/io/ecocode/java/checks/environment/optimized_api/LazyLoadingRuleTest @@ -0,0 +1,22 @@ +import org.junit.Test; +import org.sonar.java.checks.verifier.CheckVerifier; + +public class LazyLoadingRuleTest { + + @Test setx JAVA_HOME "C:\Program Files\Eclipse Adoptium\jdk-21.0.7.6-hotspot" /M + public void verifyIssues() { + CheckVerifier.newVerifier() + .onFile("src/test/files/environment/optimized_api/LazyLoadingCheckNoIssue.java") + .withCheck(new LazyLoadingComposeRule()) + .verifyIssues(); + } + + @Test + public void verifyNoIssuesWhenUsingLazyComponents() { + CheckVerifier.newVerifier() + .onFile("src/test/files/environment/optimized_api/LazyLoadingCheck.java") + .withCheck(new LazyLoadingComposeRule()) + .verifyNoIssues(); + } + +} \ No newline at end of file diff --git a/codenarc-converter/CodeNarc/build.gradle b/codenarc-converter/CodeNarc/build.gradle index 50e3b8df..5b3cf620 100644 --- a/codenarc-converter/CodeNarc/build.gradle +++ b/codenarc-converter/CodeNarc/build.gradle @@ -47,7 +47,7 @@ dependencies { testImplementation 'junit:junit:4.12' testImplementation 'com.github.stefanbirkner:system-rules:1.16.1' - testRuntime "org.codehaus.groovy:groovy-macro:$groovyVersion" + testRuntimeOnly "org.codehaus.groovy:groovy-macro:$groovyVersion" } sourceSets {