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
2 changes: 1 addition & 1 deletion android-plugin/RULES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;
Expand Down Expand Up @@ -93,7 +95,8 @@ public static List<Class<? extends JavaCheck>> getJavaEnergyChecks() {
JobCoalesceRule.class,
SaveModeAwarenessRule.class,
ThriftyGeolocationCriteriaRule.class,
HighFrameRateRule.class
HighFrameRateRule.class,
LazyLoadingComposeRule.class
));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> 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<ImportTree> wrongImports = new ArrayList<>();

@Override
public List<Tree.Kind> 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"EC531",
"EC532",
"EC533",
"EC534"
"EC534",
"EC535"
]
}
19 changes: 19 additions & 0 deletions android-plugin/src/main/resources/io/ecocode/rules/EC535.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<img src="http://www.neomades.com/extern/partage/ecoCode/3sur5_1x.png">
<p>When displaying scrollable data on screen, the new Jetpack Compose API
introduced lazy views instead of <code>ListView</code>, <code>GridView</code> and even <code>RecycleView</code>.
These components use the technique of lazy loading, which consists of loading data
<br>only when it arrives at the display area.
Import <code>androidx.compose.foundation.lazy.*</code> to benefit from objects like <code>LazyColumn</code>, <code>LazyRow</code>,
<code>LazyVerticalGrid</code> or <code>LazyHorizontalGrid</code>.

</p>
<h2>Noncompliant Code Example</h2>
<pre>
import ListView;
import GridView;
import RecycleView;
</pre>
<h2>Compliant Solution</h2>
<pre>
import androidx.compose.foundation.lazy.*;
</pre>
17 changes: 17 additions & 0 deletions android-plugin/src/main/resources/io/ecocode/rules/EC535.json
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@
"eco-design"
],
"defaultSeverity": "Info"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* ecoCode Android plugin - Provides rules to reduce the environmental footprint of your Android applications
* Copyright © 2020 Green Code Initiative ([email protected])
*
* 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/>.
*/

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 {

}
Original file line number Diff line number Diff line change
@@ -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 {
}
Original file line number Diff line number Diff line change
@@ -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();
}

}
2 changes: 1 addition & 1 deletion codenarc-converter/CodeNarc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down