001 /**
002 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003 */
004 package net.sourceforge.pmd.lang.java.rule.design;
005
006 import net.sourceforge.pmd.lang.ast.Node;
007 import net.sourceforge.pmd.lang.java.ast.ASTConditionalAndExpression;
008 import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression;
009 import net.sourceforge.pmd.lang.java.ast.ASTConditionalOrExpression;
010 import net.sourceforge.pmd.lang.java.ast.ASTEqualityExpression;
011 import net.sourceforge.pmd.lang.java.ast.ASTExpression;
012 import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
013 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
014 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
015 import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpressionNotPlusMinus;
016 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
017
018 /**
019 * if (x != y) { diff(); } else { same(); } and<br>
020 * (!x ? diff() : same());.
021 * <p/>
022 * XPath can handle the easy cases, e.g.:<pre>
023 * //IfStatement[
024 * Statement[2]
025 * and Expression[
026 * EqualityExpression[@Image="!="] or
027 * UnaryExpressionNotPlusMinus[@Image="!"]]]
028 * </pre>
029 * but "&&" and "||" are difficult, since we need a match
030 * for <i>all</i> children instead of just one. This can be done by
031 * using a double-negative, e.g.:<pre>
032 * not(*[not(<i>matchme</i>)])
033 * </pre>
034 * Still, XPath is unable to handle arbitrarily nested cases, since it
035 * lacks recursion, e.g.:<pre>
036 * if (((x != !y)) || !(x)) { diff(); } else { same(); }
037 * </pre>
038 */
039 public class ConfusingTernaryRule extends AbstractJavaRule {
040
041 public Object visit(ASTIfStatement node, Object data) {
042 // look for "if (match) ..; else .."
043 if (node.jjtGetNumChildren() == 3) {
044 Node inode = node.jjtGetChild(0);
045 if (inode instanceof ASTExpression &&
046 inode.jjtGetNumChildren() == 1) {
047 Node jnode = inode.jjtGetChild(0);
048 if (isMatch(jnode)) {
049 addViolation(data, node);
050 }
051 }
052 }
053 return super.visit(node, data);
054 }
055
056 public Object visit(ASTConditionalExpression node, Object data) {
057 // look for "match ? .. : .."
058 if (node.jjtGetNumChildren() > 0) {
059 Node inode = node.jjtGetChild(0);
060 if (isMatch(inode)) {
061 addViolation(data, node);
062 }
063 }
064 return super.visit(node, data);
065 }
066
067 // recursive!
068 private static boolean isMatch(Node node) {
069 return
070 isUnaryNot(node) ||
071 isNotEquals(node) ||
072 isConditionalWithAllMatches(node) ||
073 isParenthesisAroundMatch(node);
074 }
075
076 private static boolean isUnaryNot(Node node) {
077 // look for "!x"
078 return
079 node instanceof ASTUnaryExpressionNotPlusMinus &&
080 "!".equals(node.getImage());
081 }
082
083 private static boolean isNotEquals(Node node) {
084 // look for "x != y"
085 return
086 node instanceof ASTEqualityExpression &&
087 "!=".equals(node.getImage());
088 }
089
090 private static boolean isConditionalWithAllMatches(Node node) {
091 // look for "match && match" or "match || match"
092 if (!(node instanceof ASTConditionalAndExpression) &&
093 !(node instanceof ASTConditionalOrExpression)) {
094 return false;
095 }
096 int n = node.jjtGetNumChildren();
097 if (n <= 0) {
098 return false;
099 }
100 for (int i = 0; i < n; i++) {
101 Node inode = node.jjtGetChild(i);
102 // recurse!
103 if (!isMatch(inode)) {
104 return false;
105 }
106 }
107 // all match
108 return true;
109 }
110
111 private static boolean isParenthesisAroundMatch(Node node) {
112 // look for "(match)"
113 if (!(node instanceof ASTPrimaryExpression) ||
114 (node.jjtGetNumChildren() != 1)) {
115 return false;
116 }
117 Node inode = node.jjtGetChild(0);
118 if (!(inode instanceof ASTPrimaryPrefix) ||
119 (inode.jjtGetNumChildren() != 1)) {
120 return false;
121 }
122 Node jnode = inode.jjtGetChild(0);
123 if (!(jnode instanceof ASTExpression) ||
124 (jnode.jjtGetNumChildren() != 1)) {
125 return false;
126 }
127 Node knode = jnode.jjtGetChild(0);
128 // recurse!
129 return isMatch(knode);
130 }
131 }
|