From 6f8f567d874badce08df73e17413e156a8f27bcd Mon Sep 17 00:00:00 2001 From: jycr Date: Fri, 14 Apr 2023 18:25:32 +0200 Subject: [PATCH] green-code-initiative/ecoCode#92 refactor(rule/python): moves Python rules into `ecocode-rules-specifications` module --- ecocode-rules-specifications/pom.xml | 19 ++++ .../src/main/assembly/python.xml | 18 ++++ .../src/main/rules/EC10/EC10.json | 14 +++ .../src/main/rules/EC10/python/EC10.asciidoc | 0 .../src/main/rules/EC203/EC203.json | 16 ++++ .../main/rules/EC203/python/EC203.asciidoc | 0 .../src/main/rules/EC34/python/EC34.asciidoc | 0 .../src/main/rules/EC4/python/EC4.asciidoc | 0 .../src/main/rules/EC404/EC404.json | 15 ++++ .../main/rules/EC404/python/EC404.asciidoc | 0 .../src/main/rules/EC66/python/EC66.asciidoc | 0 .../src/main/rules/EC69/python/EC69.asciidoc | 0 .../src/main/rules/EC7/EC7.json | 15 ++++ .../src/main/rules/EC7/python/EC7.asciidoc | 0 .../src/main/rules/EC72/python/EC72.asciidoc | 0 .../src/main/rules/EC74/python/EC74.asciidoc | 0 python-plugin/pom.xml | 13 +++ .../python/PythonRuleRepository.java | 67 +++----------- .../python/PythonPluginTest.java | 16 ++-- .../python/PythonRuleRepositoryTest.java | 87 ++++++++++++++----- 20 files changed, 198 insertions(+), 82 deletions(-) create mode 100644 ecocode-rules-specifications/src/main/assembly/python.xml create mode 100644 ecocode-rules-specifications/src/main/rules/EC10/EC10.json rename python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC10.html => ecocode-rules-specifications/src/main/rules/EC10/python/EC10.asciidoc (100%) create mode 100644 ecocode-rules-specifications/src/main/rules/EC203/EC203.json rename python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC203.html => ecocode-rules-specifications/src/main/rules/EC203/python/EC203.asciidoc (100%) rename python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC34.html => ecocode-rules-specifications/src/main/rules/EC34/python/EC34.asciidoc (100%) rename python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC4.html => ecocode-rules-specifications/src/main/rules/EC4/python/EC4.asciidoc (100%) create mode 100644 ecocode-rules-specifications/src/main/rules/EC404/EC404.json rename python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC404.html => ecocode-rules-specifications/src/main/rules/EC404/python/EC404.asciidoc (100%) rename python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC66.html => ecocode-rules-specifications/src/main/rules/EC66/python/EC66.asciidoc (100%) rename python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC69.html => ecocode-rules-specifications/src/main/rules/EC69/python/EC69.asciidoc (100%) create mode 100644 ecocode-rules-specifications/src/main/rules/EC7/EC7.json rename python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC7.html => ecocode-rules-specifications/src/main/rules/EC7/python/EC7.asciidoc (100%) rename python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC72.html => ecocode-rules-specifications/src/main/rules/EC72/python/EC72.asciidoc (100%) rename python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC74.html => ecocode-rules-specifications/src/main/rules/EC74/python/EC74.asciidoc (100%) diff --git a/ecocode-rules-specifications/pom.xml b/ecocode-rules-specifications/pom.xml index df7b95fe3..59423ee7a 100644 --- a/ecocode-rules-specifications/pom.xml +++ b/ecocode-rules-specifications/pom.xml @@ -126,6 +126,13 @@ + + + + + + + @@ -171,6 +178,18 @@ + + assembly-python + prepare-package + + single + + + + ${project.basedir}/src/main/assembly/python.xml + + + true diff --git a/ecocode-rules-specifications/src/main/assembly/python.xml b/ecocode-rules-specifications/src/main/assembly/python.xml new file mode 100644 index 000000000..7c294fd24 --- /dev/null +++ b/ecocode-rules-specifications/src/main/assembly/python.xml @@ -0,0 +1,18 @@ + + python + + jar + + false + + + ${project.build.outputDirectory} + + io/ecocode/rules/python/*.* + + + + + diff --git a/ecocode-rules-specifications/src/main/rules/EC10/EC10.json b/ecocode-rules-specifications/src/main/rules/EC10/EC10.json new file mode 100644 index 000000000..ec7fbffd9 --- /dev/null +++ b/ecocode-rules-specifications/src/main/rules/EC10/EC10.json @@ -0,0 +1,14 @@ +{ + "title": "Avoid using unoptimized vector images", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "60min" + }, + "tags": [ + "eco-design", + "ecocode" + ], + "defaultSeverity": "Minor" +} diff --git a/python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC10.html b/ecocode-rules-specifications/src/main/rules/EC10/python/EC10.asciidoc similarity index 100% rename from python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC10.html rename to ecocode-rules-specifications/src/main/rules/EC10/python/EC10.asciidoc diff --git a/ecocode-rules-specifications/src/main/rules/EC203/EC203.json b/ecocode-rules-specifications/src/main/rules/EC203/EC203.json new file mode 100644 index 000000000..3f46aefd8 --- /dev/null +++ b/ecocode-rules-specifications/src/main/rules/EC203/EC203.json @@ -0,0 +1,16 @@ +{ + "title": "Detect unoptimized image format", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "60min" + }, + "tags": [ + "performance", + "user-experience", + "eco-design", + "ecocode" + ], + "defaultSeverity": "Minor" +} diff --git a/python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC203.html b/ecocode-rules-specifications/src/main/rules/EC203/python/EC203.asciidoc similarity index 100% rename from python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC203.html rename to ecocode-rules-specifications/src/main/rules/EC203/python/EC203.asciidoc diff --git a/python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC34.html b/ecocode-rules-specifications/src/main/rules/EC34/python/EC34.asciidoc similarity index 100% rename from python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC34.html rename to ecocode-rules-specifications/src/main/rules/EC34/python/EC34.asciidoc diff --git a/python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC4.html b/ecocode-rules-specifications/src/main/rules/EC4/python/EC4.asciidoc similarity index 100% rename from python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC4.html rename to ecocode-rules-specifications/src/main/rules/EC4/python/EC4.asciidoc diff --git a/ecocode-rules-specifications/src/main/rules/EC404/EC404.json b/ecocode-rules-specifications/src/main/rules/EC404/EC404.json new file mode 100644 index 000000000..c04d919d3 --- /dev/null +++ b/ecocode-rules-specifications/src/main/rules/EC404/EC404.json @@ -0,0 +1,15 @@ +{ + "title": "Use generator comprehension instead of list comprehension in for loop declaration", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "15min" + }, + "tags": [ + "performance", + "eco-design", + "ecocode" + ], + "defaultSeverity": "Minor" +} diff --git a/python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC404.html b/ecocode-rules-specifications/src/main/rules/EC404/python/EC404.asciidoc similarity index 100% rename from python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC404.html rename to ecocode-rules-specifications/src/main/rules/EC404/python/EC404.asciidoc diff --git a/python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC66.html b/ecocode-rules-specifications/src/main/rules/EC66/python/EC66.asciidoc similarity index 100% rename from python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC66.html rename to ecocode-rules-specifications/src/main/rules/EC66/python/EC66.asciidoc diff --git a/python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC69.html b/ecocode-rules-specifications/src/main/rules/EC69/python/EC69.asciidoc similarity index 100% rename from python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC69.html rename to ecocode-rules-specifications/src/main/rules/EC69/python/EC69.asciidoc diff --git a/ecocode-rules-specifications/src/main/rules/EC7/EC7.json b/ecocode-rules-specifications/src/main/rules/EC7/EC7.json new file mode 100644 index 000000000..46c194d56 --- /dev/null +++ b/ecocode-rules-specifications/src/main/rules/EC7/EC7.json @@ -0,0 +1,15 @@ +{ + "title": "Avoid creating getter and setter methods in classes", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + "eco-design", + "performance", + "ecocode" + ], + "defaultSeverity": "Minor" +} diff --git a/python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC7.html b/ecocode-rules-specifications/src/main/rules/EC7/python/EC7.asciidoc similarity index 100% rename from python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC7.html rename to ecocode-rules-specifications/src/main/rules/EC7/python/EC7.asciidoc diff --git a/python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC72.html b/ecocode-rules-specifications/src/main/rules/EC72/python/EC72.asciidoc similarity index 100% rename from python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC72.html rename to ecocode-rules-specifications/src/main/rules/EC72/python/EC72.asciidoc diff --git a/python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC74.html b/ecocode-rules-specifications/src/main/rules/EC74/python/EC74.asciidoc similarity index 100% rename from python-plugin/src/main/resources/fr/greencodeinitiative/l10n/python/rules/python/EC74.html rename to ecocode-rules-specifications/src/main/rules/EC74/python/EC74.asciidoc diff --git a/python-plugin/pom.xml b/python-plugin/pom.xml index 7fd808178..508826430 100644 --- a/python-plugin/pom.xml +++ b/python-plugin/pom.xml @@ -16,16 +16,24 @@ https://github.com/green-code-initiative/ecoCode/tree/main/python-plugin + + ${project.groupId} + ecocode-rules-specifications + ${project.version} + python + org.sonarsource.python sonar-python-plugin sonar-plugin + provided org.sonarsource.sonarqube sonar-plugin-api + provided @@ -63,6 +71,11 @@ ${java.version} + + + org.apache.maven.plugins + maven-shade-plugin + org.apache.maven.plugins maven-dependency-plugin 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 2718b28f0..23ff4f51e 100644 --- a/python-plugin/src/main/java/fr/greencodeinitiative/python/PythonRuleRepository.java +++ b/python-plugin/src/main/java/fr/greencodeinitiative/python/PythonRuleRepository.java @@ -17,58 +17,32 @@ package fr.greencodeinitiative.python; import fr.greencodeinitiative.python.checks.*; -import org.sonar.api.rules.RuleType; +import org.sonar.api.SonarRuntime; import org.sonar.api.server.rule.RulesDefinition; -import org.sonar.api.server.rule.RulesDefinitionAnnotationLoader; import org.sonar.plugins.python.api.PythonCustomRuleRepository; +import org.sonarsource.analyzer.commons.RuleMetadataLoader; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.nio.charset.StandardCharsets; import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; public class PythonRuleRepository implements RulesDefinition, PythonCustomRuleRepository { public static final String LANGUAGE = "py"; public static final String NAME = "ecoCode"; - public static final String RESOURCE_BASE_PATH = "/fr/greencodeinitiative/l10n/python/rules/python/"; + public static final String RESOURCE_BASE_PATH = "io/ecocode/rules/python"; public static final String REPOSITORY_KEY = "ecocode-python"; - @Override - public void define(Context context) { - NewRepository repository = context.createRepository(repositoryKey(), LANGUAGE).setName(NAME); - - new RulesDefinitionAnnotationLoader().load(repository, checkClasses().toArray(new Class[] {})); - - // technical debt - Map remediationCosts = new HashMap<>(); - remediationCosts.put(AvoidSQLRequestInLoop.RULE_KEY, "10min"); - remediationCosts.put(AvoidFullSQLRequest.RULE_KEY, "20min"); - repository.rules().forEach(rule -> { - rule.setType(RuleType.CODE_SMELL); - String debt = remediationCosts.get(rule.key()); - - // TODO DDC : create support to use org.apache.commons.lang.StringUtils -// if (StringUtils.isBlank(debt)) { - if (debt == null || debt.trim().equals("")) { - // default debt to 5min for issue correction - rule.setDebtRemediationFunction( - rule.debtRemediationFunctions().constantPerIssue("5min")); - } else { - rule.setDebtRemediationFunction( - rule.debtRemediationFunctions().constantPerIssue(debt)); - } - }); + private final SonarRuntime sonarRuntime; - // HTML description - repository.rules().forEach(rule -> - rule.setHtmlDescription(loadResource(RESOURCE_BASE_PATH + rule.key() + ".html"))); + public PythonRuleRepository(SonarRuntime sonarRuntime) { + this.sonarRuntime = sonarRuntime; + } + @Override + public void define(Context context) { + NewRepository repository = context.createRepository(REPOSITORY_KEY, LANGUAGE).setName(NAME); + RuleMetadataLoader ruleMetadataLoader = new RuleMetadataLoader(RESOURCE_BASE_PATH, sonarRuntime); + ruleMetadataLoader.addRulesByAnnotatedClass(repository, (List) checkClasses()); repository.done(); } @@ -92,21 +66,4 @@ public List checkClasses() { DetectUnoptimizedImageFormat.class ); } - - private String loadResource(String path) { - URL resource = getClass().getResource(path); - if (resource == null) { - throw new IllegalStateException("Resource not found: " + path); - } - ByteArrayOutputStream result = new ByteArrayOutputStream(); - try (InputStream in = resource.openStream()) { - byte[] buffer = new byte[1024]; - for (int len = in.read(buffer); len != -1; len = in.read(buffer)) { - result.write(buffer, 0, len); - } - return new String(result.toByteArray(), StandardCharsets.UTF_8); - } catch (IOException e) { - throw new IllegalStateException("Failed to read resource: " + path, e); - } - } } diff --git a/python-plugin/src/test/java/fr/greencodeinitiative/python/PythonPluginTest.java b/python-plugin/src/test/java/fr/greencodeinitiative/python/PythonPluginTest.java index 477f5d4bb..e270ad449 100644 --- a/python-plugin/src/test/java/fr/greencodeinitiative/python/PythonPluginTest.java +++ b/python-plugin/src/test/java/fr/greencodeinitiative/python/PythonPluginTest.java @@ -16,20 +16,26 @@ */ package fr.greencodeinitiative.python; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.sonar.api.Plugin; import org.sonar.api.SonarRuntime; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; -public class PythonPluginTest { +class PythonPluginTest { + private Plugin.Context context; - @Test - public void test() { + @BeforeEach + void init() { SonarRuntime sonarRuntime = mock(SonarRuntime.class); - Plugin.Context context = new Plugin.Context(sonarRuntime); + context = new Plugin.Context(sonarRuntime); new PythonPlugin().define(context); + } + + @Test + void test() { assertThat(context.getExtensions()).hasSize(1); } 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 828447a4d..714ef278e 100644 --- a/python-plugin/src/test/java/fr/greencodeinitiative/python/PythonRuleRepositoryTest.java +++ b/python-plugin/src/test/java/fr/greencodeinitiative/python/PythonRuleRepositoryTest.java @@ -16,45 +16,88 @@ */ package fr.greencodeinitiative.python; -import static org.assertj.core.api.Assertions.assertThat; - import org.assertj.core.api.SoftAssertions; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.sonar.api.SonarRuntime; import org.sonar.api.server.rule.RulesDefinition; +import org.sonar.api.utils.Version; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; -public class PythonRuleRepositoryTest { +class PythonRuleRepositoryTest { - private PythonRuleRepository pythonRuleRepository; - private RulesDefinition.Context context; private RulesDefinition.Repository repository; - @Before - public void init() { - pythonRuleRepository = new PythonRuleRepository(); - context = new RulesDefinition.Context(); - pythonRuleRepository.define(context); - repository = context.repository(PythonRuleRepository.REPOSITORY_KEY); + @BeforeEach + void init() { + // TODO: Remove this check after Git repo split + /* + On an IDE (like IntelliJ), if the developer runs the unit tests without building/generating the Maven goals on the + "ecocode-rules-specifications" module before, the unit tests will not see the generated HTML descriptions (from ASCIIDOC files). + The developer must therefore configure his IDE to build the `ecocode-rules-specifications` module before launching the Tests. + + When the `python-plugin` submodule is in a specific Git repository, `ecocode-rules-specifications` will be fetched from a classic + external Maven dependency. There will therefore no longer be any need to perform this specific configuration. + */ + if (PythonRuleRepository.class.getResource("/io/ecocode/rules/python/EC4.json") == null) { + String message = "'ecocode-rules-specification' resources corrupted. Please check build of 'ecocode-rules-specification' module"; + if (System.getProperties().keySet().stream().anyMatch(k -> k.toString().startsWith("idea."))) { + message += "\n\nOn 'IntelliJ IDEA':" + + "\n1. go to settings :" + + "\n > Build, Execution, Deployment > Build Tools > Maven > Runner" + + "\n2. check option:" + + "\n > Delegate IDE build/run actions to Maven" + + "\n3. Click on menu: " + + "\n > Build > Build Project" + ; + } + fail(message); + } + + final SonarRuntime sonarRuntime = mock(SonarRuntime.class); + doReturn(Version.create(0, 0)).when(sonarRuntime).getApiVersion(); + PythonRuleRepository rulesDefinition = new PythonRuleRepository(sonarRuntime); + RulesDefinition.Context context = new RulesDefinition.Context(); + rulesDefinition.define(context); + repository = context.repository(rulesDefinition.repositoryKey()); } @Test - 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(10); - assertThat(pythonRuleRepository.checkClasses()).hasSize(10); + @DisplayName("Test repository metadata") + void testMetadata() { + assertThat(repository.name()).isEqualTo("ecoCode"); + assertThat(repository.language()).isEqualTo("py"); + assertThat(repository.key()).isEqualTo("ecocode-python"); } + @Test + void testRegistredRules() { + assertThat(repository.rules()).hasSize(10); + } - /** - * Check all rule keys must be prefixed by 'EC' - */ @Test - public void testRuleKeyPrefix() { + @DisplayName("All rule keys must be prefixed by 'EC'") + void testRuleKeyPrefix() { SoftAssertions assertions = new SoftAssertions(); repository.rules().forEach( rule -> assertions.assertThat(rule.key()).startsWith("EC") ); assertions.assertAll(); } + + @Test + void testAllRuleParametersHaveDescription() { + SoftAssertions assertions = new SoftAssertions(); + repository.rules().stream() + .flatMap(rule -> rule.params().stream()) + .forEach(param -> assertions.assertThat(param.description()) + .as("description for " + param.key()) + .isNotEmpty()); + assertions.assertAll(); + } }