From 76c056c807dcc06f14d022c6868d4e203d10c5d4 Mon Sep 17 00:00:00 2001 From: Gustavo Martin Morcuende Date: Sun, 21 Aug 2016 20:58:23 +0200 Subject: [PATCH] sonar JavaScript plugin: AngularJS $rootScope.$on detection --- .../Plugins/sonar-custom-javascript-plugin/pom.xml | 15 ++----- .../AngularJSRootOnEventSubscriptionCheck.java | 35 +++++++++++++++- .../custom/javascript/CustomRulesDefinition.java | 47 ++++++++++++---------- .../rules/custom/GUJS0001_javascript.html | 18 +++++++++ .../rules/custom/GUJS0001_javascript.json | 12 ++++++ .../AngularJSRootOnEventSubscriptionCheck.js | 15 +++++++ .../AngularJSRootOnEventSubscriptionCheckTest.java | 16 ++++++++ 7 files changed, 122 insertions(+), 36 deletions(-) create mode 100644 Sonar/Plugins/sonar-custom-javascript-plugin/src/main/resources/de/example/l10n/javascript/rules/custom/GUJS0001_javascript.html create mode 100644 Sonar/Plugins/sonar-custom-javascript-plugin/src/main/resources/de/example/l10n/javascript/rules/custom/GUJS0001_javascript.json create mode 100644 Sonar/Plugins/sonar-custom-javascript-plugin/src/test/files/checks/AngularJSRootOnEventSubscriptionCheck.js create mode 100644 Sonar/Plugins/sonar-custom-javascript-plugin/src/test/java/de/example/custom/javascript/checks/AngularJSRootOnEventSubscriptionCheckTest.java diff --git a/Sonar/Plugins/sonar-custom-javascript-plugin/pom.xml b/Sonar/Plugins/sonar-custom-javascript-plugin/pom.xml index 2b85884..9dabcfe 100644 --- a/Sonar/Plugins/sonar-custom-javascript-plugin/pom.xml +++ b/Sonar/Plugins/sonar-custom-javascript-plugin/pom.xml @@ -33,21 +33,14 @@ org.sonarsource.javascript sonar-javascript-plugin - sonar-plugin ${javascript.plugin.version} - provided com.google.code.gson gson 2.6.2 - compile - - com.google.guava - guava - 19.0 - + @@ -59,7 +52,7 @@ test - org.sonarsource.java + org.sonarsource.javascript javascript-checks-testkit ${javascript.plugin.version} test @@ -120,9 +113,7 @@ 1.17 true - + de.example.plugins.custom.javascript.CustomPlugin diff --git a/Sonar/Plugins/sonar-custom-javascript-plugin/src/main/java/de/example/custom/javascript/checks/AngularJSRootOnEventSubscriptionCheck.java b/Sonar/Plugins/sonar-custom-javascript-plugin/src/main/java/de/example/custom/javascript/checks/AngularJSRootOnEventSubscriptionCheck.java index 2c8cc6c..daf5f76 100644 --- a/Sonar/Plugins/sonar-custom-javascript-plugin/src/main/java/de/example/custom/javascript/checks/AngularJSRootOnEventSubscriptionCheck.java +++ b/Sonar/Plugins/sonar-custom-javascript-plugin/src/main/java/de/example/custom/javascript/checks/AngularJSRootOnEventSubscriptionCheck.java @@ -1,9 +1,40 @@ package de.example.custom.javascript.checks; +import java.util.List; + import org.sonar.check.Rule; -import org.sonar.plugins.javascript.api.visitors.DoubleDispatchVisitorCheck; +import org.sonar.javascript.tree.impl.expression.CallExpressionTreeImpl; +import org.sonar.javascript.tree.impl.expression.DotMemberExpressionTreeImpl; +import org.sonar.javascript.tree.impl.expression.IdentifierTreeImpl; +import org.sonar.plugins.javascript.api.tree.Tree; +import org.sonar.plugins.javascript.api.tree.Tree.Kind; +import org.sonar.plugins.javascript.api.visitors.SubscriptionVisitorCheck; + +import com.google.common.collect.ImmutableList; @Rule(key = "GUJS0001") -public class AngularJSRootOnEventSubscriptionCheck extends DoubleDispatchVisitorCheck { +public class AngularJSRootOnEventSubscriptionCheck extends SubscriptionVisitorCheck { + + @Override + public List nodesToVisit() { + return ImmutableList.of(Kind.CALL_EXPRESSION); + } + + @Override + public void visitNode(Tree tree) { + CallExpressionTreeImpl callExpression = (CallExpressionTreeImpl) tree; + if (callExpression.callee() instanceof DotMemberExpressionTreeImpl) { + DotMemberExpressionTreeImpl callee = (DotMemberExpressionTreeImpl) callExpression.callee(); + if (callee.object() instanceof IdentifierTreeImpl) { + IdentifierTreeImpl object = (IdentifierTreeImpl) callee.object(); + String objectName = object.name(); + String calleeName = callee.property().name(); + if ("$rootScope".equals(objectName) && "$on".equals(calleeName)) { + addIssue(callExpression.getFirstToken(), "Do not use $rootScope.$on because it leaks the Controller instance."); + } + } + } + + } } diff --git a/Sonar/Plugins/sonar-custom-javascript-plugin/src/main/java/de/example/plugins/custom/javascript/CustomRulesDefinition.java b/Sonar/Plugins/sonar-custom-javascript-plugin/src/main/java/de/example/plugins/custom/javascript/CustomRulesDefinition.java index 5d98de2..bc0c573 100644 --- a/Sonar/Plugins/sonar-custom-javascript-plugin/src/main/java/de/example/plugins/custom/javascript/CustomRulesDefinition.java +++ b/Sonar/Plugins/sonar-custom-javascript-plugin/src/main/java/de/example/plugins/custom/javascript/CustomRulesDefinition.java @@ -6,7 +6,6 @@ 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.RulesDefinitionAnnotationLoader; @@ -17,6 +16,7 @@ import org.sonar.squidbridge.annotations.RuleTemplate; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Charsets; +import com.google.common.base.Strings; import com.google.common.collect.Iterables; import com.google.common.io.Resources; import com.google.gson.Gson; @@ -29,7 +29,7 @@ import de.example.custom.javascript.checks.CheckList; * org.sonar.plugins.javascript.JavaScriptSquidSensor. * * It seems like the SonarQube developers in charge of writing the JavaScript plugin tried to - * make easy the creation of custom Java plugins. + * make easy the creation of custom JavaScript plugins. * * So, JavaScriptSquidSensor will be the object that will run my rules (my Checks) whenever it finds JavaScript code. * I do not have to do anything else, what is great! @@ -40,28 +40,31 @@ public class CustomRulesDefinition extends CustomJavaScriptRulesDefinition { private final Gson gson = new Gson(); - @Override - public String repositoryName() { - return CheckList.REPOSITORY_NAME; - } - - @Override - public String repositoryKey() { - return CheckList.REPOSITORY_KEY; - } - - @Override - public Class[] checkClasses() { - return CheckList.getChecks().toArray(); - } + @Override + public String repositoryName() { + return CheckList.REPOSITORY_NAME; + } + + @Override + public String repositoryKey() { + return CheckList.REPOSITORY_KEY; + } + + @Override + public Class[] checkClasses() { + return CheckList.getChecks().stream().toArray(Class[]::new); +// return CheckList.getChecks().stream().toArray(size -> { +// return new Class[size]; +// }); + } - /** - * I do not want to use the define method implemented in org.sonar.plugins.javascript.api.CustomJavaScriptRulesDefinition. - */ + /** + * I do not want to use the define method implemented in + * org.sonar.plugins.javascript.api.CustomJavaScriptRulesDefinition. + */ @Override public void define(Context context) { - NewRepository repository = context - .createRepository(repositoryKey(), JavaScriptLanguage.KEY) + NewRepository repository = context.createRepository(repositoryKey(), JavaScriptLanguage.KEY) .setName(repositoryName()); List checks = CheckList.getChecks(); new RulesDefinitionAnnotationLoader().load(repository, Iterables.toArray(checks, Class.class)); @@ -79,7 +82,7 @@ public class CustomRulesDefinition extends CustomJavaScriptRulesDefinition { throw new IllegalArgumentException("No Rule annotation was found on " + ruleClass); } String ruleKey = ruleAnnotation.key(); - if (StringUtils.isEmpty(ruleKey)) { + if (Strings.isNullOrEmpty(ruleKey)) { throw new IllegalArgumentException("No key is defined in Rule annotation of " + ruleClass); } NewRule rule = repository.rule(ruleKey); diff --git a/Sonar/Plugins/sonar-custom-javascript-plugin/src/main/resources/de/example/l10n/javascript/rules/custom/GUJS0001_javascript.html b/Sonar/Plugins/sonar-custom-javascript-plugin/src/main/resources/de/example/l10n/javascript/rules/custom/GUJS0001_javascript.html new file mode 100644 index 0000000..ab6882f --- /dev/null +++ b/Sonar/Plugins/sonar-custom-javascript-plugin/src/main/resources/de/example/l10n/javascript/rules/custom/GUJS0001_javascript.html @@ -0,0 +1,18 @@ +

Custom rule description.

+

Noncompliant Code Example

+
+class MyClass {
+	
+	int foo1(int value) { return 0; }
+		
+	MyClass foo2(MyClass value) { return null; }
+	
+	...
+ 
+}
+
+

See

+ + diff --git a/Sonar/Plugins/sonar-custom-javascript-plugin/src/main/resources/de/example/l10n/javascript/rules/custom/GUJS0001_javascript.json b/Sonar/Plugins/sonar-custom-javascript-plugin/src/main/resources/de/example/l10n/javascript/rules/custom/GUJS0001_javascript.json new file mode 100644 index 0000000..928a6af --- /dev/null +++ b/Sonar/Plugins/sonar-custom-javascript-plugin/src/main/resources/de/example/l10n/javascript/rules/custom/GUJS0001_javascript.json @@ -0,0 +1,12 @@ +{ + "title": "Function names should comply with a naming convention", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + "bug" + ], + "defaultSeverity": "Major" +} diff --git a/Sonar/Plugins/sonar-custom-javascript-plugin/src/test/files/checks/AngularJSRootOnEventSubscriptionCheck.js b/Sonar/Plugins/sonar-custom-javascript-plugin/src/test/files/checks/AngularJSRootOnEventSubscriptionCheck.js new file mode 100644 index 0000000..ccb137d --- /dev/null +++ b/Sonar/Plugins/sonar-custom-javascript-plugin/src/test/files/checks/AngularJSRootOnEventSubscriptionCheck.js @@ -0,0 +1,15 @@ +function Controller($rootScope, USERS) { + var vm = this; + + $rootScope.$on(USERS.ROOTSCOPE.BROADCAST, usersChildOnRootBroadcast); // Noncompliant {{Do not use $rootScope.$on because it leaks the Controller instance.}} + + function NestedFunction() { + $rootScope // Noncompliant {{Do not use $rootScope.$on because it leaks the Controller instance.}} + + .$on(USERS.ROOTSCOPE.BROADCAST, usersChildOnRootBroadcast); + } + + function usersChildOnRootBroadcast(events, broadcastUser) { + vm.broadcastUser = broadcastUser; + } +} diff --git a/Sonar/Plugins/sonar-custom-javascript-plugin/src/test/java/de/example/custom/javascript/checks/AngularJSRootOnEventSubscriptionCheckTest.java b/Sonar/Plugins/sonar-custom-javascript-plugin/src/test/java/de/example/custom/javascript/checks/AngularJSRootOnEventSubscriptionCheckTest.java new file mode 100644 index 0000000..40994ec --- /dev/null +++ b/Sonar/Plugins/sonar-custom-javascript-plugin/src/test/java/de/example/custom/javascript/checks/AngularJSRootOnEventSubscriptionCheckTest.java @@ -0,0 +1,16 @@ +package de.example.custom.javascript.checks; + +import java.io.File; + +import org.junit.Test; +import org.sonar.javascript.checks.verifier.JavaScriptCheckVerifier; + +public class AngularJSRootOnEventSubscriptionCheckTest { + + private AngularJSRootOnEventSubscriptionCheck check = new AngularJSRootOnEventSubscriptionCheck(); + + @Test + public void testDefault() { + JavaScriptCheckVerifier.verify(check, new File("src/test/files/checks/AngularJSRootOnEventSubscriptionCheck.js")); + } +} -- 2.1.4