From e1b8f645adb5953e3adc1a97b58f551f2435fe48 Mon Sep 17 00:00:00 2001 From: chouaibou Date: Tue, 20 May 2025 16:44:26 +0200 Subject: [PATCH 1/2] Add Lazy Loading Rule --- android-plugin/pom.xml | 2 +- .../java/io/ecocode/java/JavaCheckList.java | 4 +- .../optimized_api/LazyLoadingComposeRule.java | 92 +++++++++++++++++++ .../android/java/ecocode_java_profile.json | 3 +- .../io/ecocode/rules/java/EC533.html | 17 ++++ .../io/ecocode/rules/java/EC533.json | 17 ++++ .../optimized_api/LazyLoadingCheck.java | 27 ++++++ .../LazyLoadingCheckNoIssue.java | 10 ++ .../optimized_api/LazyLoadingRuleTest | 22 +++++ codenarc-converter/CodeNarc/build.gradle | 4 +- .../src/main/resources/codenarc-version.txt | 2 +- codenarc-converter/pom.xml | 2 +- tool_prepare-codenarc.bat | 2 +- tool_prepare-codenarc.sh | 2 +- 14 files changed, 197 insertions(+), 9 deletions(-) create mode 100644 android-plugin/src/main/java/io/ecocode/java/checks/environment/optimized_api/LazyLoadingComposeRule.java create mode 100644 android-plugin/src/main/resources/io/ecocode/rules/java/EC533.html create mode 100644 android-plugin/src/main/resources/io/ecocode/rules/java/EC533.json create mode 100644 android-plugin/src/test/files/environment/optimized_api/LazyLoadingCheck.java create mode 100644 android-plugin/src/test/files/environment/optimized_api/LazyLoadingCheckNoIssue.java create mode 100644 android-plugin/src/test/java/io/ecocode/java/checks/environment/optimized_api/LazyLoadingRuleTest diff --git a/android-plugin/pom.xml b/android-plugin/pom.xml index 0ae85e19..ebee0b56 100644 --- a/android-plugin/pom.xml +++ b/android-plugin/pom.xml @@ -184,7 +184,7 @@ org.codenarc CodeNarc - 2.2.5 + 3.5.0 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 5dd2f46d..a2644c3b 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,7 @@ 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.sobriety.*; @@ -89,7 +90,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..a76b23b7 --- /dev/null +++ b/android-plugin/src/main/java/io/ecocode/java/checks/environment/optimized_api/LazyLoadingComposeRule.java @@ -0,0 +1,92 @@ +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; + +@Rule(key = "EC533") +@DeprecatedRuleKey(repositoryKey = "ecoCode-android", ruleKey = "EOPT003") +public class LazyLoadingComposeRule extends IssuableSubscriptionVisitor { + + private static final String WRONG_SCROLLABLE_IMPORT = "androidx.compose.foundation."; + private static final String LAZY_LOADING_IMPORT = "androidx.compose.foundation.lazy"; + + private static final Set SCROLLABLE_COMPONENTS = Set.of( + "ScrollableColumn", + "VerticalScroller", + "HorizontalScroller", + "ScrollableRow" + ); + + private boolean hasSeenWrongImport = false; + private boolean hasSeenLazyImport = false; + private List wrongImports = new ArrayList<>(); + private List wrongUsages = new ArrayList<>(); + + @Override + public List nodesToVisit() { + return List.of(Tree.Kind.IMPORT, Tree.Kind.IDENTIFIER); + } + + @Override + public void visitNode(Tree tree) { + if (tree.is(Tree.Kind.IMPORT)) { + checkImport((ImportTree) tree); + } else { + checkIdentifier((IdentifierTree) tree); + } + } + + private void checkImport(ImportTree importTree) { + String fullQualifiedName = TreeHelper.fullQualifiedName(importTree.qualifiedIdentifier()); + + if (fullQualifiedName.startsWith(WRONG_SCROLLABLE_IMPORT) && + !fullQualifiedName.startsWith(LAZY_LOADING_IMPORT)) { + hasSeenWrongImport = true; + wrongImports.add(importTree); + } + + if (fullQualifiedName.startsWith(LAZY_LOADING_IMPORT)) { + hasSeenLazyImport = true; + } + } + + private void checkIdentifier(IdentifierTree identifierTree) { + if (SCROLLABLE_COMPONENTS.contains(identifierTree.name())) { + wrongUsages.add(identifierTree); + } + } + + @Override + public void leaveFile(JavaFileScannerContext context) { + // Remove super.leaveFile call since it's not needed + if (hasSeenWrongImport && !hasSeenLazyImport) { + // Report issues for wrong imports + wrongImports.forEach(importTree -> + reportIssue(importTree, + "Use Lazy components (LazyColumn, LazyRow, etc.) from androidx.compose.foundation.lazy " + + "instead of scrollable components for better performance with large datasets.")); + + // Report issues for wrong component usages + wrongUsages.forEach(identifierTree -> + reportIssue(identifierTree, + "Replace " + identifierTree.name() + " with equivalent Lazy component " + + "(LazyColumn/LazyRow) for efficient lazy loading.")); + } + + // Reset state + hasSeenWrongImport = false; + hasSeenLazyImport = false; + wrongImports.clear(); + wrongUsages.clear(); + } +} \ No newline at end of file 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 088b28ef..3cc951ab 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 @@ -33,6 +33,7 @@ "EC529", "EC530", "EC531", - "EC532" + "EC532", + "EC533" ] } diff --git a/android-plugin/src/main/resources/io/ecocode/rules/java/EC533.html b/android-plugin/src/main/resources/io/ecocode/rules/java/EC533.html new file mode 100644 index 00000000..2f8a46c0 --- /dev/null +++ b/android-plugin/src/main/resources/io/ecocode/rules/java/EC533.html @@ -0,0 +1,17 @@ + +

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, GridView and even 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/java/EC533.json b/android-plugin/src/main/resources/io/ecocode/rules/java/EC533.json new file mode 100644 index 00000000..d0a570c4 --- /dev/null +++ b/android-plugin/src/main/resources/io/ecocode/rules/java/EC533.json @@ -0,0 +1,17 @@ +{ + "title": "Optimized API: Lazy Loading", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "20min" + }, + "tags": [ + "optimized-api", + "environment", + "ecocode", + "android", + "eco-design" + ], + "defaultSeverity": "Major" + } \ 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..d8fae216 100644 --- a/codenarc-converter/CodeNarc/build.gradle +++ b/codenarc-converter/CodeNarc/build.gradle @@ -14,7 +14,7 @@ shadowJar { group = 'org.codenarc' archivesBaseName = 'CodeNarc' -version = '2.2.5' +version = '3.5.0' sourceCompatibility = '1.7' targetCompatibility = '1.7' @@ -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 { diff --git a/codenarc-converter/CodeNarc/src/main/resources/codenarc-version.txt b/codenarc-converter/CodeNarc/src/main/resources/codenarc-version.txt index 21bb5e15..1545d966 100644 --- a/codenarc-converter/CodeNarc/src/main/resources/codenarc-version.txt +++ b/codenarc-converter/CodeNarc/src/main/resources/codenarc-version.txt @@ -1 +1 @@ -2.2.5 +3.5.0 diff --git a/codenarc-converter/pom.xml b/codenarc-converter/pom.xml index 8e4f65c8..ea02976a 100644 --- a/codenarc-converter/pom.xml +++ b/codenarc-converter/pom.xml @@ -16,7 +16,7 @@ https://github.com/green-code-initiative/ecocode-android/tree/main/codenarc-converter - 2.2.5 + 3.5.0 1.8.2 true diff --git a/tool_prepare-codenarc.bat b/tool_prepare-codenarc.bat index c5223c9d..48fff796 100644 --- a/tool_prepare-codenarc.bat +++ b/tool_prepare-codenarc.bat @@ -1,5 +1,5 @@ REM == Define CodeNarc version -set codenarc_version=2.2.5 +set codenarc_version=3.5.0 REM == Build CodeNarc cd codenarc-converter/CodeNarc diff --git a/tool_prepare-codenarc.sh b/tool_prepare-codenarc.sh index 993baf85..058f7494 100755 --- a/tool_prepare-codenarc.sh +++ b/tool_prepare-codenarc.sh @@ -1,7 +1,7 @@ #!/usr/bin/env sh # Define CodeNarc version -codenarcVersion="2.2.5" +codenarcVersion="3.5.0" # Build CodeNarc cd codenarc-converter/CodeNarc From ce61dca5cbd07a65fefa57d4e5b44e4a6a8898ed Mon Sep 17 00:00:00 2001 From: matc78 Date: Wed, 21 May 2025 15:48:20 +0200 Subject: [PATCH 2/2] add lazyLoading --- android-plugin/RULES.md | 2 +- android-plugin/pom.xml | 2 +- .../optimized_api/LazyLoadingComposeRule.java | 48 +++++++------------ .../io/ecocode/rules/java/EC533.html | 4 +- .../io/ecocode/rules/java/EC533.json | 2 +- codenarc-converter/CodeNarc/build.gradle | 2 +- .../src/main/resources/codenarc-version.txt | 2 +- codenarc-converter/pom.xml | 2 +- tool_prepare-codenarc.bat | 2 +- tool_prepare-codenarc.sh | 2 +- 10 files changed, 29 insertions(+), 39 deletions(-) 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/pom.xml b/android-plugin/pom.xml index ebee0b56..0ae85e19 100644 --- a/android-plugin/pom.xml +++ b/android-plugin/pom.xml @@ -184,7 +184,7 @@ org.codenarc CodeNarc - 3.5.0 + 2.2.5 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 index a76b23b7..c15d01de 100644 --- 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 @@ -13,44 +13,48 @@ 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 WRONG_SCROLLABLE_IMPORT = "androidx.compose.foundation."; private static final String LAZY_LOADING_IMPORT = "androidx.compose.foundation.lazy"; - private static final Set SCROLLABLE_COMPONENTS = Set.of( - "ScrollableColumn", - "VerticalScroller", - "HorizontalScroller", - "ScrollableRow" + 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<>(); - private List wrongUsages = new ArrayList<>(); @Override public List nodesToVisit() { - return List.of(Tree.Kind.IMPORT, Tree.Kind.IDENTIFIER); + return List.of(Tree.Kind.IMPORT); } @Override public void visitNode(Tree tree) { if (tree.is(Tree.Kind.IMPORT)) { checkImport((ImportTree) tree); - } else { - checkIdentifier((IdentifierTree) tree); } } private void checkImport(ImportTree importTree) { String fullQualifiedName = TreeHelper.fullQualifiedName(importTree.qualifiedIdentifier()); - if (fullQualifiedName.startsWith(WRONG_SCROLLABLE_IMPORT) && - !fullQualifiedName.startsWith(LAZY_LOADING_IMPORT)) { + if (LEGACY_IMPORTS.contains(fullQualifiedName)) { hasSeenWrongImport = true; wrongImports.add(importTree); } @@ -60,33 +64,17 @@ private void checkImport(ImportTree importTree) { } } - private void checkIdentifier(IdentifierTree identifierTree) { - if (SCROLLABLE_COMPONENTS.contains(identifierTree.name())) { - wrongUsages.add(identifierTree); - } - } - @Override public void leaveFile(JavaFileScannerContext context) { - // Remove super.leaveFile call since it's not needed if (hasSeenWrongImport && !hasSeenLazyImport) { - // Report issues for wrong imports wrongImports.forEach(importTree -> reportIssue(importTree, - "Use Lazy components (LazyColumn, LazyRow, etc.) from androidx.compose.foundation.lazy " + - "instead of scrollable components for better performance with large datasets.")); - - // Report issues for wrong component usages - wrongUsages.forEach(identifierTree -> - reportIssue(identifierTree, - "Replace " + identifierTree.name() + " with equivalent Lazy component " + - "(LazyColumn/LazyRow) for efficient lazy loading.")); + "Prefer using lazy loading view components from Jetpack-Compose to save energy")); } // Reset state hasSeenWrongImport = false; hasSeenLazyImport = false; wrongImports.clear(); - wrongUsages.clear(); } -} \ No newline at end of file +} diff --git a/android-plugin/src/main/resources/io/ecocode/rules/java/EC533.html b/android-plugin/src/main/resources/io/ecocode/rules/java/EC533.html index 2f8a46c0..040c5ec3 100644 --- a/android-plugin/src/main/resources/io/ecocode/rules/java/EC533.html +++ b/android-plugin/src/main/resources/io/ecocode/rules/java/EC533.html @@ -9,7 +9,9 @@

Noncompliant Code Example

-    import ListView, GridView and even RecycleView;
+    import ListView; 
+    import GridView;
+    import RecycleView;
 

Compliant Solution

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 d0a570c4..8ebf2e59 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
@@ -4,7 +4,7 @@
     "status": "ready",
     "remediation": {
       "func": "Constant\/Issue",
-      "constantCost": "20min"
+      "constantCost": "1440min"
     },
     "tags": [
       "optimized-api",
diff --git a/codenarc-converter/CodeNarc/build.gradle b/codenarc-converter/CodeNarc/build.gradle
index d8fae216..5b3cf620 100644
--- a/codenarc-converter/CodeNarc/build.gradle
+++ b/codenarc-converter/CodeNarc/build.gradle
@@ -14,7 +14,7 @@ shadowJar {
 
 group = 'org.codenarc'
 archivesBaseName = 'CodeNarc'
-version = '3.5.0'
+version = '2.2.5'
 
 sourceCompatibility = '1.7'
 targetCompatibility = '1.7'
diff --git a/codenarc-converter/CodeNarc/src/main/resources/codenarc-version.txt b/codenarc-converter/CodeNarc/src/main/resources/codenarc-version.txt
index 1545d966..21bb5e15 100644
--- a/codenarc-converter/CodeNarc/src/main/resources/codenarc-version.txt
+++ b/codenarc-converter/CodeNarc/src/main/resources/codenarc-version.txt
@@ -1 +1 @@
-3.5.0
+2.2.5
diff --git a/codenarc-converter/pom.xml b/codenarc-converter/pom.xml
index ea02976a..8e4f65c8 100644
--- a/codenarc-converter/pom.xml
+++ b/codenarc-converter/pom.xml
@@ -16,7 +16,7 @@
   https://github.com/green-code-initiative/ecocode-android/tree/main/codenarc-converter
 
   
-    3.5.0
+    2.2.5
     1.8.2
     true
   
diff --git a/tool_prepare-codenarc.bat b/tool_prepare-codenarc.bat
index 48fff796..c5223c9d 100644
--- a/tool_prepare-codenarc.bat
+++ b/tool_prepare-codenarc.bat
@@ -1,5 +1,5 @@
 REM == Define CodeNarc version
-set codenarc_version=3.5.0
+set codenarc_version=2.2.5
 
 REM == Build CodeNarc
 cd codenarc-converter/CodeNarc
diff --git a/tool_prepare-codenarc.sh b/tool_prepare-codenarc.sh
index 058f7494..993baf85 100755
--- a/tool_prepare-codenarc.sh
+++ b/tool_prepare-codenarc.sh
@@ -1,7 +1,7 @@
 #!/usr/bin/env sh
 
 # Define CodeNarc version
-codenarcVersion="3.5.0"
+codenarcVersion="2.2.5"
 
 # Build CodeNarc
 cd codenarc-converter/CodeNarc