bc0c5730f8b2f3f067292b49b4d7c2ba8d5d2441
[JavaForFun] /
1 package de.example.plugins.custom.javascript;
2
3 import java.io.IOException;
4 import java.net.URL;
5 import java.util.List;
6
7 import javax.annotation.Nullable;
8
9 import org.sonar.api.rule.RuleStatus;
10 import org.sonar.api.server.debt.DebtRemediationFunction;
11 import org.sonar.api.server.rule.RulesDefinitionAnnotationLoader;
12 import org.sonar.api.utils.AnnotationUtils;
13 import org.sonar.plugins.javascript.JavaScriptLanguage;
14 import org.sonar.plugins.javascript.api.CustomJavaScriptRulesDefinition;
15 import org.sonar.squidbridge.annotations.RuleTemplate;
16
17 import com.google.common.annotations.VisibleForTesting;
18 import com.google.common.base.Charsets;
19 import com.google.common.base.Strings;
20 import com.google.common.collect.Iterables;
21 import com.google.common.io.Resources;
22 import com.google.gson.Gson;
23
24 import de.example.custom.javascript.checks.CheckList;
25
26
27 /**
28  * This class will be injected (SonarQube is using PicoContainer) in
29  * org.sonar.plugins.javascript.JavaScriptSquidSensor.
30  * 
31  * It seems like the SonarQube developers in charge of writing the JavaScript plugin tried to
32  * make easy the creation of custom JavaScript plugins.
33  * 
34  * So, JavaScriptSquidSensor will be the object that will run my rules (my Checks) whenever it finds JavaScript code.
35  * I do not have to do anything else, what is great!
36  *
37  */
38 public class CustomRulesDefinition extends CustomJavaScriptRulesDefinition {
39         private static final String RESOURCE_BASE_PATH = "/de/example/l10n/javascript/rules/custom";
40         
41         private final Gson gson = new Gson();
42         
43         @Override
44         public String repositoryName() {
45                 return CheckList.REPOSITORY_NAME;
46         }
47
48         @Override
49         public String repositoryKey() {
50                 return CheckList.REPOSITORY_KEY;
51         }
52
53         @Override
54         public Class[] checkClasses() {
55                 return CheckList.getChecks().stream().toArray(Class[]::new);
56 //              return CheckList.getChecks().stream().toArray(size -> {
57 //                      return new Class[size];
58 //              });
59         }
60         
61         /**
62          * I do not want to use the define method implemented in
63          * org.sonar.plugins.javascript.api.CustomJavaScriptRulesDefinition.
64          */
65         @Override
66         public void define(Context context) {
67                 NewRepository repository = context.createRepository(repositoryKey(), JavaScriptLanguage.KEY)
68                                 .setName(repositoryName());
69                 List<Class> checks = CheckList.getChecks();
70                 new RulesDefinitionAnnotationLoader().load(repository, Iterables.toArray(checks, Class.class));
71                 for (Class ruleClass : checks) {
72                         newRule(ruleClass, repository);
73                 }
74                 repository.done();
75         }
76   
77   @VisibleForTesting
78   protected void newRule(Class<?> ruleClass, NewRepository repository) {
79
80     org.sonar.check.Rule ruleAnnotation = AnnotationUtils.getAnnotation(ruleClass, org.sonar.check.Rule.class);
81     if (ruleAnnotation == null) {
82       throw new IllegalArgumentException("No Rule annotation was found on " + ruleClass);
83     }
84     String ruleKey = ruleAnnotation.key();
85     if (Strings.isNullOrEmpty(ruleKey)) {
86       throw new IllegalArgumentException("No key is defined in Rule annotation of " + ruleClass);
87     }
88     NewRule rule = repository.rule(ruleKey);
89     if (rule == null) {
90       throw new IllegalStateException("No rule was created for " + ruleClass + " in " + repository.key());
91     }
92     
93     // Check whether it is a Rule Template.
94     rule.setTemplate(AnnotationUtils.getAnnotation(ruleClass, RuleTemplate.class) != null);
95     ruleMetadata(ruleClass, rule);
96   }
97
98   private void ruleMetadata(Class<?> ruleClass, NewRule rule) {
99     String metadataKey = rule.key();
100     addHtmlDescription(rule, metadataKey);
101     addMetadata(rule, metadataKey);
102
103   }
104
105   private void addMetadata(NewRule rule, String metadataKey) {
106     URL resource = CustomRulesDefinition.class.getResource(RESOURCE_BASE_PATH + "/" + metadataKey + "_javascript.json");
107     if (resource != null) {
108       RuleMetatada metatada = gson.fromJson(readResource(resource), RuleMetatada.class);
109       rule.setSeverity(metatada.defaultSeverity.toUpperCase());
110       rule.setName(metatada.title);
111       rule.addTags(metatada.tags);
112       rule.setStatus(RuleStatus.valueOf(metatada.status.toUpperCase()));
113       if(metatada.remediation != null) {
114         rule.setDebtRemediationFunction(metatada.remediation.remediationFunction(rule.debtRemediationFunctions()));
115         rule.setGapDescription(metatada.remediation.linearDesc);
116       }
117     }
118   }
119
120   private void addHtmlDescription(NewRule rule, String metadataKey) {
121     URL resource = CustomRulesDefinition.class.getResource(RESOURCE_BASE_PATH + "/" + metadataKey + "_javascript.html");
122     if (resource != null) {
123       rule.setHtmlDescription(readResource(resource));
124     }
125   }
126
127   private String readResource(URL resource) {
128     try {
129       return Resources.toString(resource, Charsets.UTF_8);
130     } catch (IOException e) {
131       throw new IllegalStateException("Failed to read: " + resource, e);
132     }
133   }
134
135   private static class RuleMetatada {
136     String title;
137     String status;
138     @Nullable
139     Remediation remediation;
140
141     String[] tags;
142     String defaultSeverity;
143   }
144
145   private static class Remediation {
146     String func;
147     String constantCost;
148     String linearDesc;
149     String linearOffset;
150     String linearFactor;
151
152     public DebtRemediationFunction remediationFunction(DebtRemediationFunctions drf) {
153       if(func.startsWith("Constant")) {
154         return drf.constantPerIssue(constantCost.replace("mn", "min"));
155       }
156       if("Linear".equals(func)) {
157         return drf.linear(linearFactor.replace("mn", "min"));
158       }
159       return drf.linearWithOffset(linearFactor.replace("mn", "min"), linearOffset.replace("mn", "min"));
160     }
161   }
162 }