Skip to content

Commit

Permalink
green-code-initiative#92 refactor(rule/php): moves PHP rules into `ec…
Browse files Browse the repository at this point in the history
…ocode-rules-specifications` module
  • Loading branch information
jycr committed Jun 28, 2023
1 parent b364bcb commit cd7dbc6
Show file tree
Hide file tree
Showing 18 changed files with 194 additions and 87 deletions.
19 changes: 19 additions & 0 deletions ecocode-rules-specifications/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@
</fileset>
<flattenmapper/>
</copy>
<copy todir="${project.build.outputDirectory}/io/ecocode/rules/php">
<fileset dir="${project.build.directory}/rules">
<include name="*/php/EC*.html"/>
<include name="*/EC*.json"/>
</fileset>
<flattenmapper/>
</copy>
</target>
</configuration>
<goals>
Expand All @@ -129,6 +136,18 @@
</descriptors>
</configuration>
</execution>
<execution>
<id>assembly-php</id>
<phase>prepare-package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>${project.basedir}/src/main/assembly/php.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
<configuration>
<appendAssemblyId>true</appendAssemblyId>
Expand Down
18 changes: 18 additions & 0 deletions ecocode-rules-specifications/src/main/assembly/php.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.1 https://maven.apache.org/xsd/assembly-2.1.1.xsd">
<id>php</id>
<formats>
<format>jar</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>${project.build.outputDirectory}</directory>
<includes>
<include>io/ecocode/rules/php/*.*</include>
</includes>
<outputDirectory/>
</fileSet>
</fileSets>
</assembly>
15 changes: 15 additions & 0 deletions ecocode-rules-specifications/src/main/rules/EC22/EC22.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"title": "Use of methods for basic operations",
"type": "CODE_SMELL",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "5min"
},
"tags": [
"eco-design",
"performance",
"ecocode"
],
"defaultSeverity": "Minor"
}
15 changes: 15 additions & 0 deletions ecocode-rules-specifications/src/main/rules/EC34/EC34.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"title": "Avoid using try-catch-finally statement",
"type": "CODE_SMELL",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "5min"
},
"tags": [
"eco-design",
"performance",
"ecocode"
],
"defaultSeverity": "Minor"
}
15 changes: 15 additions & 0 deletions ecocode-rules-specifications/src/main/rules/EC66/EC66.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"title": "Avoid using double quote (\"), prefer using simple quote (')",
"type": "CODE_SMELL",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "5min"
},
"tags": [
"eco-design",
"performance",
"ecocode"
],
"defaultSeverity": "Minor"
}
28 changes: 26 additions & 2 deletions php-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,24 @@
<url>https://github.com/green-code-initiative/ecoCode/tree/main/php-plugin</url>

<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>ecocode-rules-specifications</artifactId>
<version>${project.version}</version>
<classifier>php</classifier>
</dependency>

<dependency>
<groupId>org.sonarsource.php</groupId>
<artifactId>sonar-php-plugin</artifactId>
<type>sonar-plugin</type>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.sonarsource.sonarqube</groupId>
<artifactId>sonar-plugin-api</artifactId>
<scope>provided</scope>
</dependency>

<!-- for security on regex patterns : https://github.com/google/re2j -->
Expand All @@ -35,6 +43,19 @@
<!-- <artifactId>re2j</artifactId>-->
<!-- </dependency>-->

<!-- TEST sources dependencies -->
<dependency>
<groupId>org.sonarsource.java</groupId>
<artifactId>java-checks-testkit</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
Expand Down Expand Up @@ -62,10 +83,13 @@
<sonarLintSupported>true</sonarLintSupported>
<sonarQubeMinVersion>${sonarqube.version}</sonarQubeMinVersion>
<jreMinVersion>${java.version}</jreMinVersion>
<sonarQubeMinVersion>${sonarqube.version}</sonarQubeMinVersion>
<jreMinVersion>${java.version}</jreMinVersion>
</configuration>
</plugin>
<plugin>
<!-- shade plugin configuration is on parent pom.xml because of use for other modules -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,7 @@
*/
package fr.greencodeinitiative.php;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import fr.greencodeinitiative.php.checks.AvoidGettingSizeCollectionInLoopCheck;
import fr.greencodeinitiative.php.checks.AvoidDoubleQuoteCheck;
Expand All @@ -34,48 +27,29 @@
import fr.greencodeinitiative.php.checks.IncrementCheck;
import fr.greencodeinitiative.php.checks.NoFunctionCallWhenDeclaringForLoop;
import fr.greencodeinitiative.php.checks.UseOfMethodsForBasicOperations;
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.php.api.visitors.PHPCustomRuleRepository;
import org.sonarsource.analyzer.commons.RuleMetadataLoader;

public class PhpRuleRepository implements RulesDefinition, PHPCustomRuleRepository {

public static final String LANGUAGE = "php";
public static final String NAME = "ecoCode";
public static final String RESOURCE_BASE_PATH = "/fr/greencodeinitiative/l10n/php/rules/custom/";
public static final String REPOSITORY_KEY = "ecocode-php";
private static final String LANGUAGE = "php";
private static final String NAME = "ecoCode";
private static final String RESOURCE_BASE_PATH = "io/ecocode/rules/php";
private static final String REPOSITORY_KEY = "ecocode-php";

@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<String, String> remediationCosts = new HashMap<>();
remediationCosts.put(AvoidSQLRequestInLoopCheck.RULE_KEY, "10min");
remediationCosts.put(AvoidFullSQLRequestCheck.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 PhpRuleRepository(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, checkClasses());
repository.done();
}

Expand All @@ -98,21 +72,4 @@ public List<Class<?>> checkClasses() {
UseOfMethodsForBasicOperations.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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,26 @@
*/
package fr.greencodeinitiative.php;

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 PhpPluginTest {
class PhpPluginTest {
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 PHPPlugin().define(context);
}

@Test
void test() {
assertThat(context.getExtensions()).hasSize(1);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,42 +16,80 @@
*/
package fr.greencodeinitiative.php;

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;

class PhpRuleRepositoryTest {

public class PhpRuleRepositoryTest {
private RulesDefinition.Repository repository;

private PhpRuleRepository phpRuleRepository;
private RulesDefinition.Context context;
@BeforeEach
void init() {
// TODO: Remove this check after Git repo split
if (PhpRuleRepository.class.getResource("/io/ecocode/rules/php/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);
}

@Before
public void init() {
phpRuleRepository = new PhpRuleRepository();
context = new RulesDefinition.Context();
phpRuleRepository.define(context);
final SonarRuntime sonarRuntime = mock(SonarRuntime.class);
doReturn(Version.create(0, 0)).when(sonarRuntime).getApiVersion();
PhpRuleRepository rulesDefinition = new PhpRuleRepository(sonarRuntime);
RulesDefinition.Context context = new RulesDefinition.Context();
rulesDefinition.define(context);
repository = context.repository(rulesDefinition.repositoryKey());
}

@Test
@DisplayName("Test repository metadata")
void testMetadata() {
assertThat(repository.name()).isEqualTo("ecoCode");
assertThat(repository.language()).isEqualTo("php");
assertThat(repository.key()).isEqualTo("ecocode-php");
}

@Test
public void test() {
assertThat(phpRuleRepository.repositoryKey()).isEqualTo(PhpRuleRepository.REPOSITORY_KEY);
assertThat(context.repositories()).hasSize(1).extracting("key").containsExactly(phpRuleRepository.repositoryKey());
assertThat(context.repositories().get(0).rules()).hasSize(9);
assertThat(phpRuleRepository.checkClasses()).hasSize(9);
void testRegistredRules() {
assertThat(repository.rules()).hasSize(9);
}

/**
* Check all rule keys must be prefixed by 'EC'
*/
@Test()
public void testRuleKeyPrefix() {
RulesDefinition.Repository repository = context.repository(PhpRuleRepository.REPOSITORY_KEY);
@Test
@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();
}
}

0 comments on commit cd7dbc6

Please sign in to comment.