From 86460d851f61a0e2f4cfe4cca420e7bd38d41539 Mon Sep 17 00:00:00 2001 From: Gustavo Martin Morcuende Date: Tue, 2 Aug 2016 22:46:19 +0200 Subject: [PATCH] sonarqube 5.6.1: trying to implement custom rule. Based on SonarQube JavaPlugin.java implementation. This is too complicated. It is taking me too much time... --- Sonar/Plugins/hello-world-plugin/pom.xml | 126 +++++++++++++++++++ .../de/example/helloworld/checks/CheckList.java | 25 ++++ .../example/helloworld/checks/HelloWorldCheck.java | 37 ++++++ .../plugins/helloworld/HelloWorldPlugin.java | 19 +++ .../helloworld/HelloWorldRulesDefinition.java | 138 +++++++++++++++++++++ .../rules/gushelloworld/HELLOWORLDO001_java.html | 20 +++ .../rules/gushelloworld/HELLOWORLDO001_java.json | 12 ++ .../src/test/files/HelloWorldCheck.java | 17 +++ .../helloworld/checks/HelloWorldCheckTest.java | 12 ++ 9 files changed, 406 insertions(+) create mode 100644 Sonar/Plugins/hello-world-plugin/pom.xml create mode 100644 Sonar/Plugins/hello-world-plugin/src/main/java/de/example/helloworld/checks/CheckList.java create mode 100644 Sonar/Plugins/hello-world-plugin/src/main/java/de/example/helloworld/checks/HelloWorldCheck.java create mode 100644 Sonar/Plugins/hello-world-plugin/src/main/java/de/example/plugins/helloworld/HelloWorldPlugin.java create mode 100644 Sonar/Plugins/hello-world-plugin/src/main/java/de/example/plugins/helloworld/HelloWorldRulesDefinition.java create mode 100644 Sonar/Plugins/hello-world-plugin/src/main/resources/de/example/l10n/helloworld/rules/gushelloworld/HELLOWORLDO001_java.html create mode 100644 Sonar/Plugins/hello-world-plugin/src/main/resources/de/example/l10n/helloworld/rules/gushelloworld/HELLOWORLDO001_java.json create mode 100644 Sonar/Plugins/hello-world-plugin/src/test/files/HelloWorldCheck.java create mode 100644 Sonar/Plugins/hello-world-plugin/src/test/java/de/example/helloworld/checks/HelloWorldCheckTest.java diff --git a/Sonar/Plugins/hello-world-plugin/pom.xml b/Sonar/Plugins/hello-world-plugin/pom.xml new file mode 100644 index 0000000..58014b4 --- /dev/null +++ b/Sonar/Plugins/hello-world-plugin/pom.xml @@ -0,0 +1,126 @@ + + + 4.0.0 + + org.sonarsource.plugins.example + sonar-example-plugin + sonar-plugin + 0.1-SNAPSHOT + + HelloWorld Plugin for SonarQube + HelloWorld example plugin for SonarQube + https://gumartinm.name/ + + Gustavo Martin Morcuende + https://gumartinm.name/ + + + + UTF-8 + 5.6.1 + 4.0 + 1.8 + helloworld + + + + + org.sonarsource.sonarqube + sonar-plugin-api + ${sonar.apiVersion} + provided + + + org.sonarsource.java + sonar-java-plugin + sonar-plugin + ${java.plugin.version} + provided + + + + + org.sonarsource.sonarqube + sonar-testing-harness + ${sonar.apiVersion} + test + + + org.sonarsource.java + java-checks-testkit + ${java.plugin.version} + test + + + org.easytesting + fest-assert + 1.4 + test + + + junit + junit + 4.11 + test + + + + org.apache.logging.log4j + log4j-slf4j-impl + 2.6.1 + test + + + + org.apache.logging.log4j + log4j-core + 2.6.1 + test + + + + org.slf4j + jcl-over-slf4j + 1.7.21 + test + + + + + + + + org.sonarsource.sonar-packaging-maven-plugin + sonar-packaging-maven-plugin + 1.17 + true + + de.example.plugins.helloworld.HelloWorldPlugin + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + ${jdk.min.version} + ${jdk.min.version} + ${project.build.sourceEncoding} + + + + + + diff --git a/Sonar/Plugins/hello-world-plugin/src/main/java/de/example/helloworld/checks/CheckList.java b/Sonar/Plugins/hello-world-plugin/src/main/java/de/example/helloworld/checks/CheckList.java new file mode 100644 index 0000000..d93b33d --- /dev/null +++ b/Sonar/Plugins/hello-world-plugin/src/main/java/de/example/helloworld/checks/CheckList.java @@ -0,0 +1,25 @@ +package de.example.helloworld.checks; + +import java.util.List; + +import org.sonar.api.internal.google.common.collect.ImmutableList; +import org.sonar.plugins.java.api.JavaCheck; + +public final class CheckList { + public static final String REPOSITORY_KEY = "gushelloworld"; + + private CheckList() { + } + + public static List getChecks() { + return ImmutableList.builder().addAll(getJavaChecks()) + .build(); + } + + public static List> getJavaChecks() { + return ImmutableList.>builder() + .add(HelloWorldCheck.class) + .build(); + } + +} diff --git a/Sonar/Plugins/hello-world-plugin/src/main/java/de/example/helloworld/checks/HelloWorldCheck.java b/Sonar/Plugins/hello-world-plugin/src/main/java/de/example/helloworld/checks/HelloWorldCheck.java new file mode 100644 index 0000000..901ce08 --- /dev/null +++ b/Sonar/Plugins/hello-world-plugin/src/main/java/de/example/helloworld/checks/HelloWorldCheck.java @@ -0,0 +1,37 @@ +package de.example.helloworld.checks; + +import java.util.List; + +import org.sonar.check.Rule; +import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.semantic.Symbol.MethodSymbol; +import org.sonar.plugins.java.api.semantic.Type; +import org.sonar.plugins.java.api.tree.MethodTree; +import org.sonar.plugins.java.api.tree.Tree; +import org.sonar.plugins.java.api.tree.Tree.Kind; + +import com.google.common.collect.ImmutableList; + +@Rule(key = "HELLOWORLDO001") +public class HelloWorldCheck extends IssuableSubscriptionVisitor { + + @Override + public List nodesToVisit() { + return ImmutableList.of(Kind.METHOD); + } + + @Override + public void visitNode(Tree tree) { + MethodTree method = (MethodTree) tree; + + if (method.parameters().size() == 1) { + MethodSymbol symbol = method.symbol(); + Type firstParameterType = symbol.parameterTypes().get(0); + Type returnType = symbol.returnType().type(); + if(returnType.is(firstParameterType.fullyQualifiedName())) { + reportIssue(method.simpleName(), "Never do that!"); + } + } + + } +} diff --git a/Sonar/Plugins/hello-world-plugin/src/main/java/de/example/plugins/helloworld/HelloWorldPlugin.java b/Sonar/Plugins/hello-world-plugin/src/main/java/de/example/plugins/helloworld/HelloWorldPlugin.java new file mode 100644 index 0000000..b8edfcd --- /dev/null +++ b/Sonar/Plugins/hello-world-plugin/src/main/java/de/example/plugins/helloworld/HelloWorldPlugin.java @@ -0,0 +1,19 @@ +package de.example.plugins.helloworld; + +import org.sonar.api.Plugin; + +import com.google.common.collect.ImmutableList; + + +public class HelloWorldPlugin implements Plugin { + + @Override + public void define(Context context) { + + ImmutableList.Builder builder = ImmutableList.builder(); + builder.add( + HelloWorldRulesDefinition.class); + context.addExtensions(builder.build()); + + } +} diff --git a/Sonar/Plugins/hello-world-plugin/src/main/java/de/example/plugins/helloworld/HelloWorldRulesDefinition.java b/Sonar/Plugins/hello-world-plugin/src/main/java/de/example/plugins/helloworld/HelloWorldRulesDefinition.java new file mode 100644 index 0000000..41ec76c --- /dev/null +++ b/Sonar/Plugins/hello-world-plugin/src/main/java/de/example/plugins/helloworld/HelloWorldRulesDefinition.java @@ -0,0 +1,138 @@ +package de.example.plugins.helloworld; + +import java.io.IOException; +import java.net.URL; +import java.util.List; + +import javax.annotation.Nullable; + +import org.apache.commons.lang.StringUtils; +import org.sonar.api.rule.RuleStatus; +import org.sonar.api.server.debt.DebtRemediationFunction; +import org.sonar.api.server.rule.RulesDefinition; +import org.sonar.api.server.rule.RulesDefinitionAnnotationLoader; +import org.sonar.api.utils.AnnotationUtils; +import org.sonar.check.Cardinality; +import org.sonar.plugins.java.Java; +import org.sonar.squidbridge.annotations.RuleTemplate; +import org.sonar.squidbridge.rules.ExternalDescriptionLoader; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Charsets; +import com.google.common.collect.Iterables; +import com.google.common.io.Resources; +import com.google.gson.Gson; + +import de.example.helloworld.checks.CheckList; + +/** + * Definition of rules. + */ +public class HelloWorldRulesDefinition implements RulesDefinition { + private static final String RESOURCE_BASE_PATH = "/de/example/l10n/helloworld/rules/gushelloworld"; + private final Gson gson = new Gson(); + + @Override + public void define(Context context) { + NewRepository repository = context.createRepository(CheckList.REPOSITORY_KEY, Java.KEY).setName("Gus HelloWorld Definition"); + List checks = CheckList.getChecks(); + new RulesDefinitionAnnotationLoader().load(repository, Iterables.toArray(checks, Class.class)); + for (Class ruleClass : checks) { + newRule(ruleClass, repository); + } + repository.done(); + } + + @VisibleForTesting + protected void newRule(Class ruleClass, NewRepository repository) { + + org.sonar.check.Rule ruleAnnotation = AnnotationUtils.getAnnotation(ruleClass, org.sonar.check.Rule.class); + if (ruleAnnotation == null) { + throw new IllegalArgumentException("No Rule annotation was found on " + ruleClass); + } + String ruleKey = ruleAnnotation.key(); + if (StringUtils.isEmpty(ruleKey)) { + throw new IllegalArgumentException("No key is defined in Rule annotation of " + ruleClass); + } + NewRule rule = repository.rule(ruleKey); + if (rule == null) { + throw new IllegalStateException("No rule was created for " + ruleClass + " in " + repository.key()); + } + rule.setTemplate(AnnotationUtils.getAnnotation(ruleClass, RuleTemplate.class) != null); + if (ruleAnnotation.cardinality() == Cardinality.MULTIPLE) { + throw new IllegalArgumentException("Cardinality is not supported, use the RuleTemplate annotation instead for " + ruleClass); + } + ruleMetadata(ruleClass, rule); + } + + private void ruleMetadata(Class ruleClass, NewRule rule) { + String metadataKey = rule.key(); + org.sonar.java.RspecKey rspecKeyAnnotation = AnnotationUtils.getAnnotation(ruleClass, org.sonar.java.RspecKey.class); + if (rspecKeyAnnotation != null) { + metadataKey = rspecKeyAnnotation.value(); + rule.setInternalKey(metadataKey); + } + addHtmlDescription(rule, metadataKey); + addMetadata(rule, metadataKey); + + } + + private void addMetadata(NewRule rule, String metadataKey) { + URL resource = ExternalDescriptionLoader.class.getResource(RESOURCE_BASE_PATH + "/" + metadataKey + "_java.json"); + if (resource != null) { + RuleMetatada metatada = gson.fromJson(readResource(resource), RuleMetatada.class); + rule.setSeverity(metatada.defaultSeverity.toUpperCase()); + rule.setName(metatada.title); + rule.addTags(metatada.tags); + rule.setStatus(RuleStatus.valueOf(metatada.status.toUpperCase())); + if(metatada.remediation != null) { + rule.setDebtRemediationFunction(metatada.remediation.remediationFunction(rule.debtRemediationFunctions())); + rule.setGapDescription(metatada.remediation.linearDesc); + } + } + } + + private static void addHtmlDescription(NewRule rule, String metadataKey) { + URL resource = HelloWorldRulesDefinition.class.getResource(RESOURCE_BASE_PATH + "/" + metadataKey + "_java.html"); + if (resource != null) { + rule.setHtmlDescription(readResource(resource)); + } + } + + private static String readResource(URL resource) { + try { + return Resources.toString(resource, Charsets.UTF_8); + } catch (IOException e) { + throw new IllegalStateException("Failed to read: " + resource, e); + } + } + + private static class RuleMetatada { + String title; + String status; + @Nullable + Remediation remediation; + + String[] tags; + String defaultSeverity; + } + + private static class Remediation { + String func; + String constantCost; + String linearDesc; + String linearOffset; + String linearFactor; + + public DebtRemediationFunction remediationFunction(DebtRemediationFunctions drf) { + if(func.startsWith("Constant")) { + return drf.constantPerIssue(constantCost.replace("mn", "min")); + } + if("Linear".equals(func)) { + return drf.linear(linearFactor.replace("mn", "min")); + } + return drf.linearWithOffset(linearFactor.replace("mn", "min"), linearOffset.replace("mn", "min")); + } + } + +} diff --git a/Sonar/Plugins/hello-world-plugin/src/main/resources/de/example/l10n/helloworld/rules/gushelloworld/HELLOWORLDO001_java.html b/Sonar/Plugins/hello-world-plugin/src/main/resources/de/example/l10n/helloworld/rules/gushelloworld/HELLOWORLDO001_java.html new file mode 100644 index 0000000..73bff1b --- /dev/null +++ b/Sonar/Plugins/hello-world-plugin/src/main/resources/de/example/l10n/helloworld/rules/gushelloworld/HELLOWORLDO001_java.html @@ -0,0 +1,20 @@ +

HelloWorld rule description.

+

Noncompliant Code Example

+
+public class MyServlet extends HttpServlet {
+  private String userName;  //As this field is shared by all users, it's obvious that this piece of information should be managed differently
+  ...
+}
+
+

or

+
+public class MyAction extends Action {
+  private String userName;  //Same reason
+  ...
+}
+
+

See

+
    +
  • CERT, MSC11-J. - Do not let session information leak within a servlet
  • +
+ diff --git a/Sonar/Plugins/hello-world-plugin/src/main/resources/de/example/l10n/helloworld/rules/gushelloworld/HELLOWORLDO001_java.json b/Sonar/Plugins/hello-world-plugin/src/main/resources/de/example/l10n/helloworld/rules/gushelloworld/HELLOWORLDO001_java.json new file mode 100644 index 0000000..4421cd9 --- /dev/null +++ b/Sonar/Plugins/hello-world-plugin/src/main/resources/de/example/l10n/helloworld/rules/gushelloworld/HELLOWORLDO001_java.json @@ -0,0 +1,12 @@ +{ + "title": "HelloWorld SonarQube rule", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + "bad-practice" + ], + "defaultSeverity": "Major" +} diff --git a/Sonar/Plugins/hello-world-plugin/src/test/files/HelloWorldCheck.java b/Sonar/Plugins/hello-world-plugin/src/test/files/HelloWorldCheck.java new file mode 100644 index 0000000..488a749 --- /dev/null +++ b/Sonar/Plugins/hello-world-plugin/src/test/files/HelloWorldCheck.java @@ -0,0 +1,17 @@ +class MyClass { + + MyClass(MyClass mc) { } + + int foo1() { return 0; } + + void foo2(int value) { } + + // Noncompliant@+1 [[startColumn=6;endLine=+0;endColumn=10;effortToFix=4]] {{Never do that!}} + int foo3(int value) { return 0; } + + Object foo4(int value) { return null; } + + // Noncompliant@+1 [[startColumn=10;endLine=+0;endColumn=14;effortToFix=4]] {{Never do that!}} + MyClass foo5(MyClass value) { return null; } + +} diff --git a/Sonar/Plugins/hello-world-plugin/src/test/java/de/example/helloworld/checks/HelloWorldCheckTest.java b/Sonar/Plugins/hello-world-plugin/src/test/java/de/example/helloworld/checks/HelloWorldCheckTest.java new file mode 100644 index 0000000..d941f61 --- /dev/null +++ b/Sonar/Plugins/hello-world-plugin/src/test/java/de/example/helloworld/checks/HelloWorldCheckTest.java @@ -0,0 +1,12 @@ +package de.example.helloworld.checks; + +import org.junit.Test; +import org.sonar.java.checks.verifier.JavaCheckVerifier; + +public class HelloWorldCheckTest { + + @Test + public void test() { + JavaCheckVerifier.verify("src/test/files/HelloWorldCheck.java", new HelloWorldCheck()); + } +} -- 2.1.4