sonar JavaScript plugin: AngularJS $rootScope.$on detection
authorGustavo Martin Morcuende <gu.martinm@gmail.com>
Sun, 21 Aug 2016 18:58:23 +0000 (20:58 +0200)
committerGustavo Martin Morcuende <gu.martinm@gmail.com>
Sun, 21 Aug 2016 19:03:47 +0000 (21:03 +0200)
Sonar/Plugins/sonar-custom-javascript-plugin/pom.xml
Sonar/Plugins/sonar-custom-javascript-plugin/src/main/java/de/example/custom/javascript/checks/AngularJSRootOnEventSubscriptionCheck.java
Sonar/Plugins/sonar-custom-javascript-plugin/src/main/java/de/example/plugins/custom/javascript/CustomRulesDefinition.java
Sonar/Plugins/sonar-custom-javascript-plugin/src/main/resources/de/example/l10n/javascript/rules/custom/GUJS0001_javascript.html [new file with mode: 0644]
Sonar/Plugins/sonar-custom-javascript-plugin/src/main/resources/de/example/l10n/javascript/rules/custom/GUJS0001_javascript.json [new file with mode: 0644]
Sonar/Plugins/sonar-custom-javascript-plugin/src/test/files/checks/AngularJSRootOnEventSubscriptionCheck.js [new file with mode: 0644]
Sonar/Plugins/sonar-custom-javascript-plugin/src/test/java/de/example/custom/javascript/checks/AngularJSRootOnEventSubscriptionCheckTest.java [new file with mode: 0644]

index 2b85884..9dabcfe 100644 (file)
   <dependency>
       <groupId>org.sonarsource.javascript</groupId>
       <artifactId>sonar-javascript-plugin</artifactId>
-      <type>sonar-plugin</type>
       <version>${javascript.plugin.version}</version>
-      <scope>provided</scope>
   </dependency>
       <dependency>
         <groupId>com.google.code.gson</groupId>
         <artifactId>gson</artifactId>
         <version>2.6.2</version>
-        <scope>compile</scope>
       </dependency>
-    <dependency>
-      <groupId>com.google.guava</groupId>
-      <artifactId>guava</artifactId>
-      <version>19.0</version>
-    </dependency>
+
     
 
 
@@ -59,7 +52,7 @@
       <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>org.sonarsource.java</groupId>
+      <groupId>org.sonarsource.javascript</groupId>
       <artifactId>javascript-checks-testkit</artifactId>
       <version>${javascript.plugin.version}</version>
       <scope>test</scope>
         <version>1.17</version>
         <extensions>true</extensions>
         <configuration>
-            <!--
-          <pluginClass>de.example.plugins.helloworld.HelloWorldPlugin</pluginClass>
-            -->
+          <pluginClass>de.example.plugins.custom.javascript.CustomPlugin</pluginClass>
         </configuration>
       </plugin>
       <plugin>
index 2c8cc6c..daf5f76 100644 (file)
@@ -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<Kind> 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.");
+                               }
+                       }
+               }
+
+       }
 
 }
index 5d98de2..bc0c573 100644 (file)
@@ -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<Class> 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 (file)
index 0000000..ab6882f
--- /dev/null
@@ -0,0 +1,18 @@
+<p>Custom rule description.</p>
+<h2>Noncompliant Code Example</h2>
+<pre>
+class MyClass {
+       
+       int foo1(int value) { return 0; }
+               
+       MyClass foo2(MyClass value) { return null; }
+       
+       ...
+}
+</pre>
+<h2>See</h2>
+<ul>
+  <li> <a href="https://www.securecoding.cert.org/confluence/x/EYBUC">CERT, MSC11-J.</a> - You are doing wrong!!! </li>
+</ul>
+
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 (file)
index 0000000..928a6af
--- /dev/null
@@ -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 (file)
index 0000000..ccb137d
--- /dev/null
@@ -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 (file)
index 0000000..40994ec
--- /dev/null
@@ -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"));
+         }
+}