Coverage Report - com.puppycrawl.tools.checkstyle.checks.coding.MagicNumberCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
MagicNumberCheck
98%
75/76
93%
58/62
4.9
 
 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  
 package com.puppycrawl.tools.checkstyle.checks.coding;
 20  
 
 21  
 import com.puppycrawl.tools.checkstyle.api.Check;
 22  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 23  
 import com.puppycrawl.tools.checkstyle.api.ScopeUtils;
 24  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 25  
 import com.puppycrawl.tools.checkstyle.checks.CheckUtils;
 26  
 import java.util.Arrays;
 27  
 
 28  
 /**
 29  
  * <p>
 30  
  * Checks for magic numbers.
 31  
  * </p>
 32  
  * <p>
 33  
  * An example of how to configure the check to ignore
 34  
  * numbers 0, 1, 1.5, 2:
 35  
  * </p>
 36  
  * <pre>
 37  
  * &lt;module name="MagicNumber"&gt;
 38  
  *    &lt;property name="ignoreNumbers" value="0, 1, 1.5, 2"/&gt;
 39  
  *    &lt;property name="ignoreHashCodeMethod" value="true"/&gt;
 40  
  * &lt;/module&gt;
 41  
  * </pre>
 42  
  * @author Rick Giles
 43  
  * @author Lars Kühne
 44  
  * @author Daniel Solano Gómez
 45  
  */
 46  6
 public class MagicNumberCheck extends Check
 47  
 {
 48  
     /**
 49  
      * The token types that are allowed in the AST path from the
 50  
      * number literal to the enclosing constant definition.
 51  
      */
 52  1
     private static final int[] ALLOWED_PATH_TOKENTYPES = {
 53  
         TokenTypes.ASSIGN,
 54  
         TokenTypes.ARRAY_INIT,
 55  
         TokenTypes.EXPR,
 56  
         TokenTypes.UNARY_PLUS,
 57  
         TokenTypes.UNARY_MINUS,
 58  
         TokenTypes.TYPECAST,
 59  
         TokenTypes.ELIST,
 60  
         TokenTypes.LITERAL_NEW,
 61  
         TokenTypes.METHOD_CALL,
 62  
         TokenTypes.STAR,
 63  
     };
 64  
 
 65  
     static {
 66  1
         Arrays.sort(ALLOWED_PATH_TOKENTYPES);
 67  1
     }
 68  
 
 69  
     /** the numbers to ignore in the check, sorted */
 70  6
     private double[] mIgnoreNumbers = {-1, 0, 1, 2};
 71  
     /** Whether to ignore magic numbers in a hash code method. */
 72  
     private boolean mIgnoreHashCodeMethod;
 73  
     /** Whether to ignore magic numbers in annotation. */
 74  
     private boolean mIgnoreAnnotation;
 75  
 
 76  
     @Override
 77  
     public int[] getDefaultTokens()
 78  
     {
 79  6
         return new int[] {
 80  
             TokenTypes.NUM_DOUBLE,
 81  
             TokenTypes.NUM_FLOAT,
 82  
             TokenTypes.NUM_INT,
 83  
             TokenTypes.NUM_LONG,
 84  
         };
 85  
     }
 86  
 
 87  
     @Override
 88  
     public void visitToken(DetailAST aAST)
 89  
     {
 90  510
         if (mIgnoreAnnotation && isInAnnotation(aAST)) {
 91  5
             return;
 92  
         }
 93  
 
 94  505
         if (inIgnoreList(aAST)
 95  
             || (mIgnoreHashCodeMethod && isInHashCodeMethod(aAST)))
 96  
         {
 97  138
             return;
 98  
         }
 99  
 
 100  367
         final DetailAST constantDefAST = findContainingConstantDef(aAST);
 101  
 
 102  367
         if (constantDefAST == null) {
 103  226
             reportMagicNumber(aAST);
 104  
         }
 105  
         else {
 106  141
             DetailAST ast = aAST.getParent();
 107  563
             while (ast != constantDefAST) {
 108  428
                 final int type = ast.getType();
 109  428
                 if (Arrays.binarySearch(ALLOWED_PATH_TOKENTYPES, type) < 0) {
 110  6
                     reportMagicNumber(aAST);
 111  6
                     break;
 112  
                 }
 113  
 
 114  422
                 ast = ast.getParent();
 115  422
             }
 116  
         }
 117  367
     }
 118  
 
 119  
     /**
 120  
      * Finds the constant definition that contains aAST.
 121  
      * @param aAST the AST
 122  
      * @return the constant def or null if aAST is not
 123  
      * contained in a constant definition
 124  
      */
 125  
     private DetailAST findContainingConstantDef(DetailAST aAST)
 126  
     {
 127  367
         DetailAST varDefAST = aAST;
 128  
         while ((varDefAST != null)
 129  
                 && (varDefAST.getType() != TokenTypes.VARIABLE_DEF)
 130  2041
                 && (varDefAST.getType() != TokenTypes.ENUM_CONSTANT_DEF))
 131  
         {
 132  1674
             varDefAST = varDefAST.getParent();
 133  
         }
 134  
 
 135  
         // no containing variable definition?
 136  367
         if (varDefAST == null) {
 137  68
             return null;
 138  
         }
 139  
 
 140  
         // implicit constant?
 141  299
         if (ScopeUtils.inInterfaceOrAnnotationBlock(varDefAST)
 142  
             || (varDefAST.getType() == TokenTypes.ENUM_CONSTANT_DEF))
 143  
         {
 144  23
             return varDefAST;
 145  
         }
 146  
 
 147  
         // explicit constant
 148  276
         final DetailAST modifiersAST =
 149  
                 varDefAST.findFirstToken(TokenTypes.MODIFIERS);
 150  276
         if (modifiersAST.branchContains(TokenTypes.FINAL)) {
 151  118
             return varDefAST;
 152  
         }
 153  
 
 154  158
         return null;
 155  
     }
 156  
 
 157  
     /**
 158  
      * Reports aAST as a magic number, includes unary operators as needed.
 159  
      * @param aAST the AST node that contains the number to report
 160  
      */
 161  
     private void reportMagicNumber(DetailAST aAST)
 162  
     {
 163  232
         String text = aAST.getText();
 164  232
         final DetailAST parent = aAST.getParent();
 165  232
         DetailAST reportAST = aAST;
 166  232
         if (parent.getType() == TokenTypes.UNARY_MINUS) {
 167  10
             reportAST = parent;
 168  10
             text = "-" + text;
 169  
         }
 170  222
         else if (parent.getType() == TokenTypes.UNARY_PLUS) {
 171  9
             reportAST = parent;
 172  9
             text = "+" + text;
 173  
         }
 174  232
         log(reportAST.getLineNo(),
 175  
                 reportAST.getColumnNo(),
 176  
                 "magic.number",
 177  
                 text);
 178  232
     }
 179  
 
 180  
     /**
 181  
      * Determines whether or not the given AST is in a valid hash code method.
 182  
      * A valid hash code method is considered to be a method of the signature
 183  
      * {@code public int hashCode()}.
 184  
      *
 185  
      * @param aAST the AST from which to search for an enclosing hash code
 186  
      * method definition
 187  
      *
 188  
      * @return {@code true} if {@code aAST} is in the scope of a valid hash
 189  
      * code method
 190  
      */
 191  
     private boolean isInHashCodeMethod(DetailAST aAST)
 192  
     {
 193  
         // if not in a code block, can't be in hashCode()
 194  62
         if (!ScopeUtils.inCodeBlock(aAST)) {
 195  27
             return false;
 196  
         }
 197  
 
 198  
         // find the method definition AST
 199  35
         DetailAST methodDefAST = aAST.getParent();
 200  
         while ((null != methodDefAST)
 201  189
                 && (TokenTypes.METHOD_DEF != methodDefAST.getType()))
 202  
         {
 203  154
             methodDefAST = methodDefAST.getParent();
 204  
         }
 205  
 
 206  35
         if (null == methodDefAST) {
 207  3
             return false;
 208  
         }
 209  
 
 210  
         // Check for 'hashCode' name.
 211  32
         final DetailAST identAST =
 212  
             methodDefAST.findFirstToken(TokenTypes.IDENT);
 213  32
         if (!"hashCode".equals(identAST.getText())) {
 214  30
             return false;
 215  
         }
 216  
 
 217  
         // Check for no arguments.
 218  2
         final DetailAST paramAST =
 219  
             methodDefAST.findFirstToken(TokenTypes.PARAMETERS);
 220  2
         if (0 != paramAST.getChildCount()) {
 221  1
             return false;
 222  
         }
 223  
 
 224  
         // we are in a 'public int hashCode()' method! The compiler will ensure
 225  
         // the method returns an 'int' and is public.
 226  1
         return true;
 227  
     }
 228  
 
 229  
     /**
 230  
      * Decides whether the number of an AST is in the ignore list of this
 231  
      * check.
 232  
      * @param aAST the AST to check
 233  
      * @return true if the number of aAST is in the ignore list of this
 234  
      * check.
 235  
      */
 236  
     private boolean inIgnoreList(DetailAST aAST)
 237  
     {
 238  505
         double value = CheckUtils.parseDouble(aAST.getText(), aAST.getType());
 239  505
         final DetailAST parent = aAST.getParent();
 240  505
         if (parent.getType() == TokenTypes.UNARY_MINUS) {
 241  16
             value = -1 * value;
 242  
         }
 243  505
         return (Arrays.binarySearch(mIgnoreNumbers, value) >= 0);
 244  
     }
 245  
 
 246  
     /**
 247  
      * Sets the numbers to ignore in the check.
 248  
      * BeanUtils converts numeric token list to double array automatically.
 249  
      * @param aList list of numbers to ignore.
 250  
      */
 251  
     public void setIgnoreNumbers(double[] aList)
 252  
     {
 253  3
         if ((aList == null) || (aList.length == 0)) {
 254  1
             mIgnoreNumbers = new double[0];
 255  
         }
 256  
         else {
 257  2
             mIgnoreNumbers = new double[aList.length];
 258  2
             System.arraycopy(aList, 0, mIgnoreNumbers, 0, aList.length);
 259  2
             Arrays.sort(mIgnoreNumbers);
 260  
         }
 261  3
     }
 262  
 
 263  
     /**
 264  
      * Set whether to ignore hashCode methods.
 265  
      * @param aIgnoreHashCodeMethod decide whether to ignore
 266  
      * hash code methods
 267  
      */
 268  
     public void setIgnoreHashCodeMethod(boolean aIgnoreHashCodeMethod)
 269  
     {
 270  1
         mIgnoreHashCodeMethod = aIgnoreHashCodeMethod;
 271  1
     }
 272  
 
 273  
     /**
 274  
      * Set whether to ignore Annotations.
 275  
      * @param aIgnoreAnnotation decide whether to ignore annotations
 276  
      */
 277  
     public void setIgnoreAnnotation(boolean aIgnoreAnnotation)
 278  
     {
 279  5
         mIgnoreAnnotation = aIgnoreAnnotation;
 280  5
     }
 281  
 
 282  
     /**
 283  
      * Determines if the column displays a token type of annotation or
 284  
      * annotation member
 285  
      *
 286  
      * @param aAST the AST from which to search for annotations
 287  
      *
 288  
      * @return {@code true} if the token type for this node is a annotation
 289  
      */
 290  
     private boolean isInAnnotation(DetailAST aAST)
 291  
     {
 292  420
         if ((null == aAST.getParent())
 293  
                 || (null == aAST.getParent().getParent()))
 294  
         {
 295  0
             return false;
 296  
         }
 297  
 
 298  420
         return (TokenTypes.ANNOTATION == aAST.getParent().getParent().getType())
 299  
                 || (TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR
 300  
                         == aAST.getParent().getParent().getType());
 301  
     }
 302  
 }