Based on SonarQube JavaPlugin.java implementation.
This is too complicated. It is taking me too much time...
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.sonarsource.plugins.example</groupId>
+ <artifactId>sonar-example-plugin</artifactId>
+ <packaging>sonar-plugin</packaging>
+ <version>0.1-SNAPSHOT</version>
+
+ <name>HelloWorld Plugin for SonarQube</name>
+ <description>HelloWorld example plugin for SonarQube</description>
+ <url>https://gumartinm.name/</url>
+ <organization>
+ <name>Gustavo Martin Morcuende</name>
+ <url>https://gumartinm.name/</url>
+ </organization>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <sonar.apiVersion>5.6.1</sonar.apiVersion>
+ <java.plugin.version>4.0</java.plugin.version>
+ <jdk.min.version>1.8</jdk.min.version>
+ <sonar.pluginKey>helloworld</sonar.pluginKey>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.sonarsource.sonarqube</groupId>
+ <artifactId>sonar-plugin-api</artifactId>
+ <version>${sonar.apiVersion}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.sonarsource.java</groupId>
+ <artifactId>sonar-java-plugin</artifactId>
+ <type>sonar-plugin</type>
+ <version>${java.plugin.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- unit tests -->
+ <dependency>
+ <groupId>org.sonarsource.sonarqube</groupId>
+ <artifactId>sonar-testing-harness</artifactId>
+ <version>${sonar.apiVersion}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.sonarsource.java</groupId>
+ <artifactId>java-checks-testkit</artifactId>
+ <version>${java.plugin.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.easytesting</groupId>
+ <artifactId>fest-assert</artifactId>
+ <version>1.4</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.11</version>
+ <scope>test</scope>
+ </dependency>
+ <!--
+ 1/3 Required dependency for log4j 2 with slf4j: binding between log4j
+ 2 and slf4j
+ -->
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-slf4j-impl</artifactId>
+ <version>2.6.1</version>
+ <scope>test</scope>
+ </dependency>
+ <!--
+ 2/3 Required dependency for log4j 2 with slf4j: log4j 2 maven plugin
+ (it is the log4j 2 implementation)
+ -->
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ <version>2.6.1</version>
+ <scope>test</scope>
+ </dependency>
+ <!--
+ 3/3 Required dependency for getting rid of commons logging. This is
+ the BRIDGE (no binding) between Jakarta Commons Logging (used by Spring)
+ and whatever I am using for logging (in this case I am using log4j 2) See:
+ http://www.slf4j.org/legacy.html We need exclusions in every dependency using
+ Jakarta Commons Logging (see Spring dependencies below)
+ -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>jcl-over-slf4j</artifactId>
+ <version>1.7.21</version>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId>
+ <artifactId>sonar-packaging-maven-plugin</artifactId>
+ <version>1.17</version>
+ <extensions>true</extensions>
+ <configuration>
+ <pluginClass>de.example.plugins.helloworld.HelloWorldPlugin</pluginClass>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.5.1</version>
+ <configuration>
+ <source>${jdk.min.version}</source>
+ <target>${jdk.min.version}</target>
+ <encoding>${project.build.sourceEncoding}</encoding>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /dev/null
+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<Class> getChecks() {
+ return ImmutableList.<Class>builder().addAll(getJavaChecks())
+ .build();
+ }
+
+ public static List<Class<? extends JavaCheck>> getJavaChecks() {
+ return ImmutableList.<Class<? extends JavaCheck>>builder()
+ .add(HelloWorldCheck.class)
+ .build();
+ }
+
+}
--- /dev/null
+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<Kind> 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!");
+ }
+ }
+
+ }
+}
--- /dev/null
+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<Object> builder = ImmutableList.builder();
+ builder.add(
+ HelloWorldRulesDefinition.class);
+ context.addExtensions(builder.build());
+
+ }
+}
--- /dev/null
+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<Class> 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"));
+ }
+ }
+
+}
--- /dev/null
+<p>HelloWorld rule description.</p>
+<h2>Noncompliant Code Example</h2>
+<pre>
+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
+ ...
+}
+</pre>
+<p>or </p>
+<pre>
+public class MyAction extends Action {
+ private String userName; //Same reason
+ ...
+}
+</pre>
+<h2>See</h2>
+<ul>
+ <li> <a href="https://www.securecoding.cert.org/confluence/x/EYBUC">CERT, MSC11-J.</a> - Do not let session information leak within a servlet </li>
+</ul>
+
--- /dev/null
+{
+ "title": "HelloWorld SonarQube rule",
+ "status": "ready",
+ "remediation": {
+ "func": "Constant\/Issue",
+ "constantCost": "5min"
+ },
+ "tags": [
+ "bad-practice"
+ ],
+ "defaultSeverity": "Major"
+}
--- /dev/null
+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; }
+
+}
--- /dev/null
+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());
+ }
+}