View Javadoc

1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.java.rule.design;
5   
6   import net.sourceforge.pmd.lang.ast.Node;
7   import net.sourceforge.pmd.lang.java.ast.ASTConditionalAndExpression;
8   import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression;
9   import net.sourceforge.pmd.lang.java.ast.ASTConditionalOrExpression;
10  import net.sourceforge.pmd.lang.java.ast.ASTEqualityExpression;
11  import net.sourceforge.pmd.lang.java.ast.ASTExpression;
12  import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
13  import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
14  import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
15  import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpressionNotPlusMinus;
16  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
17  import net.sourceforge.pmd.lang.rule.properties.BooleanProperty;
18  
19  /**
20   * if (x != y) { diff(); } else { same(); } and<br>
21   * (!x ? diff() : same());.
22   * <p/>
23   * XPath can handle the easy cases, e.g.:
24   * 
25   * <pre>
26   *    //IfStatement[
27   *      Statement[2]
28   *      and Expression[
29   *        EqualityExpression[@Image="!="] or
30   *        UnaryExpressionNotPlusMinus[@Image="!"]]]
31   * </pre>
32   * 
33   * but "&amp;&amp;" and "||" are difficult, since we need a match for <i>all</i>
34   * children instead of just one. This can be done by using a double-negative,
35   * e.g.:
36   * 
37   * <pre>
38   *    not(*[not(<i>matchme</i>)])
39   * </pre>
40   * 
41   * Still, XPath is unable to handle arbitrarily nested cases, since it lacks
42   * recursion, e.g.:
43   * 
44   * <pre>
45   * if (((x != !y)) || !(x)) {
46   *     diff();
47   * } else {
48   *     same();
49   * }
50   * </pre>
51   */
52  public class ConfusingTernaryRule extends AbstractJavaRule {
53      private static BooleanProperty ignoreElseIfProperty = new BooleanProperty("ignoreElseIf",
54              "Ignore conditions with an else-if case",
55              Boolean.FALSE, 0);
56  
57      public ConfusingTernaryRule() {
58          super();
59          definePropertyDescriptor(ignoreElseIfProperty);
60      }
61  
62      public Object visit(ASTIfStatement node, Object data) {
63          // look for "if (match) ..; else .."
64          if (node.jjtGetNumChildren() == 3) {
65              Node inode = node.jjtGetChild(0);
66              if (inode instanceof ASTExpression && inode.jjtGetNumChildren() == 1) {
67                  Node jnode = inode.jjtGetChild(0);
68                  if (isMatch(jnode)) {
69  
70                      if (!getProperty(ignoreElseIfProperty)
71                              || !(node.jjtGetChild(2).jjtGetChild(0) instanceof ASTIfStatement)) {
72                          addViolation(data, node);
73                      }
74                  }
75              }
76          }
77          return super.visit(node, data);
78      }
79  
80      public Object visit(ASTConditionalExpression node, Object data) {
81          // look for "match ? .. : .."
82          if (node.jjtGetNumChildren() > 0) {
83              Node inode = node.jjtGetChild(0);
84              if (isMatch(inode)) {
85                  addViolation(data, node);
86              }
87          }
88          return super.visit(node, data);
89      }
90  
91      // recursive!
92      private static boolean isMatch(Node node) {
93          return
94                  isUnaryNot(node) ||
95                  isNotEquals(node) ||
96                  isConditionalWithAllMatches(node) ||
97                  isParenthesisAroundMatch(node);
98      }
99  
100     private static boolean isUnaryNot(Node node) {
101         // look for "!x"
102         return
103                 node instanceof ASTUnaryExpressionNotPlusMinus &&
104                 "!".equals(node.getImage());
105     }
106 
107     private static boolean isNotEquals(Node node) {
108         // look for "x != y"
109         return
110                 node instanceof ASTEqualityExpression &&
111                 "!=".equals(node.getImage());
112     }
113 
114     private static boolean isConditionalWithAllMatches(Node node) {
115         // look for "match && match" or "match || match"
116         if (!(node instanceof ASTConditionalAndExpression) &&
117                 !(node instanceof ASTConditionalOrExpression)) {
118             return false;
119         }
120         int n = node.jjtGetNumChildren();
121         if (n <= 0) {
122             return false;
123         }
124         for (int i = 0; i < n; i++) {
125             Node inode = node.jjtGetChild(i);
126             // recurse!
127             if (!isMatch(inode)) {
128                 return false;
129             }
130         }
131         // all match
132         return true;
133     }
134 
135     private static boolean isParenthesisAroundMatch(Node node) {
136         // look for "(match)"
137         if (!(node instanceof ASTPrimaryExpression) ||
138                 (node.jjtGetNumChildren() != 1)) {
139             return false;
140         }
141         Node inode = node.jjtGetChild(0);
142         if (!(inode instanceof ASTPrimaryPrefix) ||
143                 (inode.jjtGetNumChildren() != 1)) {
144             return false;
145         }
146         Node jnode = inode.jjtGetChild(0);
147         if (!(jnode instanceof ASTExpression) ||
148                 (jnode.jjtGetNumChildren() != 1)) {
149             return false;
150         }
151         Node knode = jnode.jjtGetChild(0);
152         // recurse!
153         return isMatch(knode);
154     }
155 }