Coverage Report - com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
InnerAssignmentCheck
100%
37/37
88%
23/26
3.833
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2014  Oliver Burn
 4  
 //
 5  
 // This library is free software; you can redistribute it and/or
 6  
 // modify it under the terms of the GNU Lesser General Public
 7  
 // License as published by the Free Software Foundation; either
 8  
 // version 2.1 of the License, or (at your option) any later version.
 9  
 //
 10  
 // This library is distributed in the hope that it will be useful,
 11  
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 // Lesser General Public License for more details.
 14  
 //
 15  
 // You should have received a copy of the GNU Lesser General Public
 16  
 // License along with this library; if not, write to the Free Software
 17  
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 18  
 ////////////////////////////////////////////////////////////////////////////////
 19  
 
 20  
 package com.puppycrawl.tools.checkstyle.checks.coding;
 21  
 
 22  
 import java.util.Arrays;
 23  
 
 24  
 import antlr.collections.AST;
 25  
 
 26  
 import com.puppycrawl.tools.checkstyle.api.Check;
 27  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 28  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 29  
 
 30  
 /**
 31  
  * <p>
 32  
  * Checks for assignments in subexpressions, such as in
 33  
  * <code>String s = Integer.toString(i = 2);</code>.
 34  
  * </p>
 35  
  * <p>
 36  
  * Rationale: With the exception of <code>for</code> iterators, all assignments
 37  
  * should occur in their own toplevel statement to increase readability.
 38  
  * With inner assignments like the above it is difficult to see all places
 39  
  * where a variable is set.
 40  
  * </p>
 41  
  *
 42  
  * @author lkuehne
 43  
  */
 44  1
 public class InnerAssignmentCheck
 45  
         extends Check
 46  
 {
 47  
     /**
 48  
      * list of allowed AST types from an assignement AST node
 49  
      * towards the root.
 50  
      */
 51  1
     private static final int[][] ALLOWED_ASSIGMENT_CONTEXT = {
 52  
         {TokenTypes.EXPR, TokenTypes.SLIST},
 53  
         {TokenTypes.VARIABLE_DEF},
 54  
         {TokenTypes.EXPR, TokenTypes.ELIST, TokenTypes.FOR_INIT},
 55  
         {TokenTypes.EXPR, TokenTypes.ELIST, TokenTypes.FOR_ITERATOR},
 56  
         {TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR},
 57  
         {
 58  
             TokenTypes.RESOURCE,
 59  
             TokenTypes.RESOURCES,
 60  
             TokenTypes.RESOURCE_SPECIFICATION,
 61  
         },
 62  
     };
 63  
 
 64  
     /**
 65  
      * list of allowed AST types from an assignement AST node
 66  
      * towards the root.
 67  
      */
 68  1
     private static final int[][] CONTROL_CONTEXT = {
 69  
         {TokenTypes.EXPR, TokenTypes.LITERAL_DO},
 70  
         {TokenTypes.EXPR, TokenTypes.LITERAL_FOR},
 71  
         {TokenTypes.EXPR, TokenTypes.LITERAL_WHILE},
 72  
         {TokenTypes.EXPR, TokenTypes.LITERAL_IF},
 73  
         {TokenTypes.EXPR, TokenTypes.LITERAL_ELSE},
 74  
     };
 75  
 
 76  
     /**
 77  
      * list of allowed AST types from a comparison node (above an assignement)
 78  
      * towards the root.
 79  
      */
 80  1
     private static final int[][] ALLOWED_ASSIGMENT_IN_COMPARISON_CONTEXT = {
 81  
         {TokenTypes.EXPR, TokenTypes.LITERAL_WHILE, },
 82  
     };
 83  
 
 84  
     /**
 85  
      * The token types that identify comparison operators.
 86  
      */
 87  1
     private static final int[] COMPARISON_TYPES = {
 88  
         TokenTypes.EQUAL,
 89  
         TokenTypes.GE,
 90  
         TokenTypes.GT,
 91  
         TokenTypes.LE,
 92  
         TokenTypes.LT,
 93  
         TokenTypes.NOT_EQUAL,
 94  
     };
 95  
 
 96  
     static {
 97  1
         Arrays.sort(COMPARISON_TYPES);
 98  1
     }
 99  
 
 100  
     @Override
 101  
     public int[] getDefaultTokens()
 102  
     {
 103  1
         return new int[] {
 104  
             TokenTypes.ASSIGN,            // '='
 105  
             TokenTypes.DIV_ASSIGN,        // "/="
 106  
             TokenTypes.PLUS_ASSIGN,       // "+="
 107  
             TokenTypes.MINUS_ASSIGN,      //"-="
 108  
             TokenTypes.STAR_ASSIGN,       // "*="
 109  
             TokenTypes.MOD_ASSIGN,        // "%="
 110  
             TokenTypes.SR_ASSIGN,         // ">>="
 111  
             TokenTypes.BSR_ASSIGN,        // ">>>="
 112  
             TokenTypes.SL_ASSIGN,         // "<<="
 113  
             TokenTypes.BXOR_ASSIGN,       // "^="
 114  
             TokenTypes.BOR_ASSIGN,        // "|="
 115  
             TokenTypes.BAND_ASSIGN,       // "&="
 116  
         };
 117  
     }
 118  
 
 119  
     @Override
 120  
     public void visitToken(DetailAST aAST)
 121  
     {
 122  45
         if (isInContext(aAST, ALLOWED_ASSIGMENT_CONTEXT)) {
 123  22
             return;
 124  
         }
 125  
 
 126  23
         if (isInNoBraceControlStatement(aAST)) {
 127  6
             return;
 128  
         }
 129  
 
 130  17
         if (isInWhileIdiom(aAST)) {
 131  1
             return;
 132  
         }
 133  
 
 134  16
         log(aAST.getLineNo(), aAST.getColumnNo(), "assignment.inner.avoid");
 135  16
     }
 136  
 
 137  
     /**
 138  
      * Determines if aAST is in the body of a flow control statement without
 139  
      * braces. An example of such a statement would be
 140  
      * <p>
 141  
      * <pre>
 142  
      * if (y < 0)
 143  
      *     x = y;
 144  
      * </pre>
 145  
      * <p>
 146  
      * This leads to the following AST structure:
 147  
      * <p>
 148  
      * <pre>
 149  
      * LITERAL_IF
 150  
      *     LPAREN
 151  
      *     EXPR // test
 152  
      *     RPAREN
 153  
      *     EXPR // body
 154  
      *     SEMI
 155  
      * </pre>
 156  
      * <p>
 157  
      * We need to ensure that aAST is in the body and not in the test.
 158  
      *
 159  
      * @param aAST an assignment operator AST
 160  
      * @return whether aAST is in the body of a flow control statement
 161  
      */
 162  
     private static boolean isInNoBraceControlStatement(DetailAST aAST)
 163  
     {
 164  23
         if (!isInContext(aAST, CONTROL_CONTEXT)) {
 165  12
             return false;
 166  
         }
 167  11
         final DetailAST expr = aAST.getParent();
 168  11
         final AST exprNext = expr.getNextSibling();
 169  11
         return (exprNext != null) && (exprNext.getType() == TokenTypes.SEMI);
 170  
     }
 171  
 
 172  
     /**
 173  
      * Tests whether the given AST is used in the "assignment in while test"
 174  
      * idiom.
 175  
      * <p>
 176  
      * <pre>
 177  
      * while ((b = is.read()) != -1) {
 178  
      *   // work with b
 179  
      * }
 180  
      * <pre>
 181  
      * @param aAST assignment AST
 182  
      * @return whether the context of the assignemt AST indicates the idiom
 183  
      */
 184  
     private boolean isInWhileIdiom(DetailAST aAST)
 185  
     {
 186  17
         if (!isComparison(aAST.getParent())) {
 187  16
             return false;
 188  
         }
 189  1
         return isInContext(
 190  
                 aAST.getParent(), ALLOWED_ASSIGMENT_IN_COMPARISON_CONTEXT);
 191  
     }
 192  
 
 193  
     /**
 194  
      * Checks if an AST is a comparison operator.
 195  
      * @param aAST the AST to check
 196  
      * @return true iff aAST is a comparison operator.
 197  
      */
 198  
     private static boolean isComparison(DetailAST aAST)
 199  
     {
 200  17
         final int astType = aAST.getType();
 201  17
         return (Arrays.binarySearch(COMPARISON_TYPES, astType) >= 0);
 202  
     }
 203  
 
 204  
     /**
 205  
      * Tests whether the provided AST is in
 206  
      * one of the given contexts.
 207  
      *
 208  
      * @param aAST the AST from which to start walking towards root
 209  
      * @param aContextSet the contexts to test against.
 210  
      *
 211  
      * @return whether the parents nodes of aAST match
 212  
      * one of the allowed type paths
 213  
      */
 214  
     private static boolean isInContext(DetailAST aAST, int[][] aContextSet)
 215  
     {
 216  324
         for (int[] element : aContextSet) {
 217  289
             DetailAST current = aAST;
 218  289
             final int len = element.length;
 219  421
             for (int j = 0; j < len; j++) {
 220  421
                 current = current.getParent();
 221  421
                 final int expectedType = element[j];
 222  421
                 if ((current == null) || (current.getType() != expectedType)) {
 223  255
                     break;
 224  
                 }
 225  166
                 if (j == len - 1) {
 226  34
                     return true;
 227  
                 }
 228  
             }
 229  
         }
 230  35
         return false;
 231  
     }
 232  
 }