Coverage Report - com.puppycrawl.tools.checkstyle.checks.coding.MagicNumberCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
MagicNumberCheck
100%
103/103
100%
72/72
2.941
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2017 the original author or authors.
 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 com.puppycrawl.tools.checkstyle.StatelessCheck;
 25  
 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
 26  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 27  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 28  
 import com.puppycrawl.tools.checkstyle.utils.CheckUtils;
 29  
 import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
 30  
 import com.puppycrawl.tools.checkstyle.utils.ScopeUtils;
 31  
 import com.puppycrawl.tools.checkstyle.utils.TokenUtils;
 32  
 
 33  
 /**
 34  
  * <p>
 35  
  * Checks that there are no <a href="https://en.wikipedia.org/wiki/Magic_number_%28programming%29">
 36  
  * &quot;magic numbers&quot;</a> where a magic
 37  
  * number is a numeric literal that is not defined as a constant.
 38  
  * By default, -1, 0, 1, and 2 are not considered to be magic numbers.
 39  
  * </p>
 40  
  *
 41  
  * <p>Constant definition is any variable/field that has 'final' modifier.
 42  
  * It is fine to have one constant defining multiple numeric literals within one expression:
 43  
  * <pre>
 44  
  * {@code static final int SECONDS_PER_DAY = 24 * 60 * 60;
 45  
  * static final double SPECIAL_RATIO = 4.0 / 3.0;
 46  
  * static final double SPECIAL_SUM = 1 + Math.E;
 47  
  * static final double SPECIAL_DIFFERENCE = 4 - Math.PI;
 48  
  * static final Border STANDARD_BORDER = BorderFactory.createEmptyBorder(3, 3, 3, 3);
 49  
  * static final Integer ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE = new Integer(42);}
 50  
  * </pre>
 51  
  *
 52  
  * <p>Check have following options:
 53  
  * ignoreHashCodeMethod - ignore magic numbers in hashCode methods;
 54  
  * ignoreAnnotation - ignore magic numbers in annotation declarations;
 55  
  * ignoreFieldDeclaration - ignore magic numbers in field declarations.
 56  
  * <p>
 57  
  * To configure the check with default configuration:
 58  
  * </p>
 59  
  * <pre>
 60  
  * &lt;module name=&quot;MagicNumber&quot;/&gt;
 61  
  * </pre>
 62  
  * <p>
 63  
  * results is following violations:
 64  
  * </p>
 65  
  * <pre>
 66  
  * {@code
 67  
  *   {@literal @}MyAnnotation(6) // violation
 68  
  *   class MyClass {
 69  
  *       private field = 7; // violation
 70  
  *
 71  
  *       void foo() {
 72  
  *          int i = i + 1; // no violation
 73  
  *          int j = j + 8; // violation
 74  
  *       }
 75  
  *   }
 76  
  * }
 77  
  * </pre>
 78  
  * <p>
 79  
  * To configure the check so that it checks floating-point numbers
 80  
  * that are not 0, 0.5, or 1:
 81  
  * </p>
 82  
  * <pre>
 83  
  *   &lt;module name=&quot;MagicNumber&quot;&gt;
 84  
  *       &lt;property name=&quot;tokens&quot; value=&quot;NUM_DOUBLE, NUM_FLOAT&quot;/&gt;
 85  
  *       &lt;property name=&quot;ignoreNumbers&quot; value=&quot;0, 0.5, 1&quot;/&gt;
 86  
  *       &lt;property name=&quot;ignoreFieldDeclaration&quot; value=&quot;true&quot;/&gt;
 87  
  *       &lt;property name=&quot;ignoreAnnotation&quot; value=&quot;true&quot;/&gt;
 88  
  *   &lt;/module&gt;
 89  
  * </pre>
 90  
  * <p>
 91  
  * results is following violations:
 92  
  * </p>
 93  
  * <pre>
 94  
  * {@code
 95  
  *   {@literal @}MyAnnotation(6) // no violation
 96  
  *   class MyClass {
 97  
  *       private field = 7; // no violation
 98  
  *
 99  
  *       void foo() {
 100  
  *          int i = i + 1; // no violation
 101  
  *          int j = j + (int)0.5; // no violation
 102  
  *       }
 103  
  *   }
 104  
  * }
 105  
  * </pre>
 106  
  * <p>
 107  
  * Config example of constantWaiverParentToken option:
 108  
  * </p>
 109  
  * <pre>
 110  
  *   &lt;module name=&quot;MagicNumber&quot;&gt;
 111  
  *       &lt;property name=&quot;constantWaiverParentToken&quot; value=&quot;ASSIGN,ARRAY_INIT,EXPR,
 112  
  *       UNARY_PLUS, UNARY_MINUS, TYPECAST, ELIST, DIV, PLUS &quot;/&gt;
 113  
  *   &lt;/module&gt;
 114  
  * </pre>
 115  
  * <p>
 116  
  * result is following violation:
 117  
  * </p>
 118  
  * <pre>
 119  
  * {@code
 120  
  * class TestMethodCall {
 121  
  *     public void method2() {
 122  
  *         final TestMethodCall dummyObject = new TestMethodCall(62);    //violation
 123  
  *         final int a = 3;        // ok as waiver is ASSIGN
 124  
  *         final int [] b = {4, 5} // ok as waiver is ARRAY_INIT
 125  
  *         final int c = -3;       // ok as waiver is UNARY_MINUS
 126  
  *         final int d = +4;       // ok as waiver is UNARY_PLUS
 127  
  *         final int e = method(1, 2) // ELIST is there but violation due to METHOD_CALL
 128  
  *         final int x = 3 * 4;    // violation
 129  
  *         final int y = 3 / 4;    // ok as waiver is DIV
 130  
  *         final int z = 3 + 4;    // ok as waiver is PLUS
 131  
  *         final int w = 3 - 4;    // violation
 132  
  *         final int x = (int)(3.4);    //ok as waiver is TYPECAST
 133  
  *     }
 134  
  * }
 135  
  * }
 136  
  * </pre>
 137  
  * @author Rick Giles
 138  
  * @author Lars Kühne
 139  
  * @author Daniel Solano Gómez
 140  
  */
 141  
 @StatelessCheck
 142  
 public class MagicNumberCheck extends AbstractCheck {
 143  
 
 144  
     /**
 145  
      * A key is pointing to the warning message text in "messages.properties"
 146  
      * file.
 147  
      */
 148  
     public static final String MSG_KEY = "magic.number";
 149  
 
 150  
     /**
 151  
      * The token types that are allowed in the AST path from the
 152  
      * number literal to the enclosing constant definition.
 153  
      */
 154  17
     private int[] constantWaiverParentToken = {
 155  
         TokenTypes.ASSIGN,
 156  
         TokenTypes.ARRAY_INIT,
 157  
         TokenTypes.EXPR,
 158  
         TokenTypes.UNARY_PLUS,
 159  
         TokenTypes.UNARY_MINUS,
 160  
         TokenTypes.TYPECAST,
 161  
         TokenTypes.ELIST,
 162  
         TokenTypes.LITERAL_NEW,
 163  
         TokenTypes.METHOD_CALL,
 164  
         TokenTypes.STAR,
 165  
         TokenTypes.DIV,
 166  
         TokenTypes.PLUS,
 167  
         TokenTypes.MINUS,
 168  
     };
 169  
 
 170  
     /** The numbers to ignore in the check, sorted. */
 171  17
     private double[] ignoreNumbers = {-1, 0, 1, 2};
 172  
 
 173  
     /** Whether to ignore magic numbers in a hash code method. */
 174  
     private boolean ignoreHashCodeMethod;
 175  
 
 176  
     /** Whether to ignore magic numbers in annotation. */
 177  
     private boolean ignoreAnnotation;
 178  
 
 179  
     /** Whether to ignore magic numbers in field declaration. */
 180  
     private boolean ignoreFieldDeclaration;
 181  
 
 182  
     /**
 183  
      * Constructor for MagicNumber Check.
 184  
      * Sort the allowedTokensBetweenMagicNumberAndConstDef array for binary search.
 185  
      */
 186  17
     public MagicNumberCheck() {
 187  17
         Arrays.sort(constantWaiverParentToken);
 188  17
     }
 189  
 
 190  
     @Override
 191  
     public int[] getDefaultTokens() {
 192  14
         return getAcceptableTokens();
 193  
     }
 194  
 
 195  
     @Override
 196  
     public int[] getAcceptableTokens() {
 197  22
         return new int[] {
 198  
             TokenTypes.NUM_DOUBLE,
 199  
             TokenTypes.NUM_FLOAT,
 200  
             TokenTypes.NUM_INT,
 201  
             TokenTypes.NUM_LONG,
 202  
         };
 203  
     }
 204  
 
 205  
     @Override
 206  
     public int[] getRequiredTokens() {
 207  21
         return CommonUtils.EMPTY_INT_ARRAY;
 208  
     }
 209  
 
 210  
     @Override
 211  
     public void visitToken(DetailAST ast) {
 212  760
         if ((!ignoreAnnotation || !isChildOf(ast, TokenTypes.ANNOTATION))
 213  740
                 && !isInIgnoreList(ast)
 214  67
                 && (!ignoreHashCodeMethod || !isInHashCodeMethod(ast))) {
 215  539
             final DetailAST constantDefAST = findContainingConstantDef(ast);
 216  
 
 217  539
             if (constantDefAST == null) {
 218  308
                 if (!ignoreFieldDeclaration || !isFieldDeclaration(ast)) {
 219  298
                     reportMagicNumber(ast);
 220  
                 }
 221  
             }
 222  
             else {
 223  231
                 final boolean found = isMagicNumberExists(ast, constantDefAST);
 224  231
                 if (found) {
 225  14
                     reportMagicNumber(ast);
 226  
                 }
 227  
             }
 228  
         }
 229  760
     }
 230  
 
 231  
     /**
 232  
      * Is magic number some where at ast tree.
 233  
      * @param ast ast token
 234  
      * @param constantDefAST constant ast
 235  
      * @return true if magic number is present
 236  
      */
 237  
     private boolean isMagicNumberExists(DetailAST ast, DetailAST constantDefAST) {
 238  231
         boolean found = false;
 239  231
         DetailAST astNode = ast.getParent();
 240  919
         while (astNode != constantDefAST) {
 241  702
             final int type = astNode.getType();
 242  702
             if (Arrays.binarySearch(constantWaiverParentToken, type) < 0) {
 243  14
                 found = true;
 244  14
                 break;
 245  
             }
 246  688
             astNode = astNode.getParent();
 247  688
         }
 248  231
         return found;
 249  
     }
 250  
 
 251  
     /**
 252  
      * Finds the constant definition that contains aAST.
 253  
      * @param ast the AST
 254  
      * @return the constant def or null if ast is not contained in a constant definition.
 255  
      */
 256  
     private static DetailAST findContainingConstantDef(DetailAST ast) {
 257  539
         DetailAST varDefAST = ast;
 258  3020
         while (varDefAST != null
 259  2921
                 && varDefAST.getType() != TokenTypes.VARIABLE_DEF
 260  2496
                 && varDefAST.getType() != TokenTypes.ENUM_CONSTANT_DEF) {
 261  2481
             varDefAST = varDefAST.getParent();
 262  
         }
 263  539
         DetailAST constantDef = null;
 264  
 
 265  
         // no containing variable definition?
 266  539
         if (varDefAST != null) {
 267  
             // implicit constant?
 268  440
             if (ScopeUtils.isInInterfaceOrAnnotationBlock(varDefAST)
 269  424
                     || varDefAST.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
 270  31
                 constantDef = varDefAST;
 271  
             }
 272  
             else {
 273  
                 // explicit constant
 274  409
                 final DetailAST modifiersAST = varDefAST.findFirstToken(TokenTypes.MODIFIERS);
 275  
 
 276  409
                 if (modifiersAST.findFirstToken(TokenTypes.FINAL) != null) {
 277  200
                     constantDef = varDefAST;
 278  
                 }
 279  
             }
 280  
         }
 281  539
         return constantDef;
 282  
     }
 283  
 
 284  
     /**
 285  
      * Reports aAST as a magic number, includes unary operators as needed.
 286  
      * @param ast the AST node that contains the number to report
 287  
      */
 288  
     private void reportMagicNumber(DetailAST ast) {
 289  312
         String text = ast.getText();
 290  312
         final DetailAST parent = ast.getParent();
 291  312
         DetailAST reportAST = ast;
 292  312
         if (parent.getType() == TokenTypes.UNARY_MINUS) {
 293  17
             reportAST = parent;
 294  17
             text = "-" + text;
 295  
         }
 296  295
         else if (parent.getType() == TokenTypes.UNARY_PLUS) {
 297  11
             reportAST = parent;
 298  11
             text = "+" + text;
 299  
         }
 300  624
         log(reportAST.getLineNo(),
 301  312
                 reportAST.getColumnNo(),
 302  
                 MSG_KEY,
 303  
                 text);
 304  312
     }
 305  
 
 306  
     /**
 307  
      * Determines whether or not the given AST is in a valid hash code method.
 308  
      * A valid hash code method is considered to be a method of the signature
 309  
      * {@code public int hashCode()}.
 310  
      *
 311  
      * @param ast the AST from which to search for an enclosing hash code
 312  
      *     method definition
 313  
      *
 314  
      * @return {@code true} if {@code ast} is in the scope of a valid hash code method.
 315  
      */
 316  
     private static boolean isInHashCodeMethod(DetailAST ast) {
 317  67
         boolean inHashCodeMethod = false;
 318  
 
 319  
         // if not in a code block, can't be in hashCode()
 320  67
         if (ScopeUtils.isInCodeBlock(ast)) {
 321  
             // find the method definition AST
 322  36
             DetailAST methodDefAST = ast.getParent();
 323  197
             while (methodDefAST != null
 324  194
                     && methodDefAST.getType() != TokenTypes.METHOD_DEF) {
 325  161
                 methodDefAST = methodDefAST.getParent();
 326  
             }
 327  
 
 328  36
             if (methodDefAST != null) {
 329  
                 // Check for 'hashCode' name.
 330  33
                 final DetailAST identAST = methodDefAST.findFirstToken(TokenTypes.IDENT);
 331  
 
 332  33
                 if ("hashCode".equals(identAST.getText())) {
 333  
                     // Check for no arguments.
 334  2
                     final DetailAST paramAST = methodDefAST.findFirstToken(TokenTypes.PARAMETERS);
 335  
                     // we are in a 'public int hashCode()' method! The compiler will ensure
 336  
                     // the method returns an 'int' and is public.
 337  2
                     inHashCodeMethod = paramAST.getChildCount() == 0;
 338  
                 }
 339  
             }
 340  
         }
 341  67
         return inHashCodeMethod;
 342  
     }
 343  
 
 344  
     /**
 345  
      * Decides whether the number of an AST is in the ignore list of this
 346  
      * check.
 347  
      * @param ast the AST to check
 348  
      * @return true if the number of ast is in the ignore list of this check.
 349  
      */
 350  
     private boolean isInIgnoreList(DetailAST ast) {
 351  740
         double value = CheckUtils.parseDouble(ast.getText(), ast.getType());
 352  740
         final DetailAST parent = ast.getParent();
 353  740
         if (parent.getType() == TokenTypes.UNARY_MINUS) {
 354  28
             value = -1 * value;
 355  
         }
 356  740
         return Arrays.binarySearch(ignoreNumbers, value) >= 0;
 357  
     }
 358  
 
 359  
     /**
 360  
      * Determines whether or not the given AST is field declaration.
 361  
      *
 362  
      * @param ast AST from which to search for an enclosing field declaration
 363  
      *
 364  
      * @return {@code true} if {@code ast} is in the scope of field declaration
 365  
      */
 366  
     private static boolean isFieldDeclaration(DetailAST ast) {
 367  40
         DetailAST varDefAST = ast;
 368  239
         while (varDefAST != null
 369  225
                 && varDefAST.getType() != TokenTypes.VARIABLE_DEF) {
 370  199
             varDefAST = varDefAST.getParent();
 371  
         }
 372  
 
 373  
         // contains variable declaration
 374  
         // and it is directly inside class declaration
 375  80
         return varDefAST != null
 376  26
                 && varDefAST.getParent().getParent().getType() == TokenTypes.CLASS_DEF;
 377  
     }
 378  
 
 379  
     /**
 380  
      * Sets the tokens which are allowed between Magic Number and defined Object.
 381  
      * @param tokens The string representation of the tokens interested in
 382  
      */
 383  
     public void setConstantWaiverParentToken(String... tokens) {
 384  2
         constantWaiverParentToken = new int[tokens.length];
 385  22
         for (int i = 0; i < tokens.length; i++) {
 386  20
             constantWaiverParentToken[i] = TokenUtils.getTokenId(tokens[i]);
 387  
         }
 388  2
         Arrays.sort(constantWaiverParentToken);
 389  2
     }
 390  
 
 391  
     /**
 392  
      * Sets the numbers to ignore in the check.
 393  
      * BeanUtils converts numeric token list to double array automatically.
 394  
      * @param list list of numbers to ignore.
 395  
      */
 396  
     public void setIgnoreNumbers(double... list) {
 397  4
         if (list.length == 0) {
 398  1
             ignoreNumbers = CommonUtils.EMPTY_DOUBLE_ARRAY;
 399  
         }
 400  
         else {
 401  3
             ignoreNumbers = new double[list.length];
 402  3
             System.arraycopy(list, 0, ignoreNumbers, 0, list.length);
 403  3
             Arrays.sort(ignoreNumbers);
 404  
         }
 405  4
     }
 406  
 
 407  
     /**
 408  
      * Set whether to ignore hashCode methods.
 409  
      * @param ignoreHashCodeMethod decide whether to ignore
 410  
      *     hash code methods
 411  
      */
 412  
     public void setIgnoreHashCodeMethod(boolean ignoreHashCodeMethod) {
 413  1
         this.ignoreHashCodeMethod = ignoreHashCodeMethod;
 414  1
     }
 415  
 
 416  
     /**
 417  
      * Set whether to ignore Annotations.
 418  
      * @param ignoreAnnotation decide whether to ignore annotations
 419  
      */
 420  
     public void setIgnoreAnnotation(boolean ignoreAnnotation) {
 421  6
         this.ignoreAnnotation = ignoreAnnotation;
 422  6
     }
 423  
 
 424  
     /**
 425  
      * Set whether to ignore magic numbers in field declaration.
 426  
      * @param ignoreFieldDeclaration decide whether to ignore magic numbers
 427  
      *     in field declaration
 428  
      */
 429  
     public void setIgnoreFieldDeclaration(boolean ignoreFieldDeclaration) {
 430  2
         this.ignoreFieldDeclaration = ignoreFieldDeclaration;
 431  2
     }
 432  
 
 433  
     /**
 434  
      * Determines if the given AST node has a parent node with given token type code.
 435  
      *
 436  
      * @param ast the AST from which to search for annotations
 437  
      * @param type the type code of parent token
 438  
      *
 439  
      * @return {@code true} if the AST node has a parent with given token type.
 440  
      */
 441  
     private static boolean isChildOf(DetailAST ast, int type) {
 442  463
         boolean result = false;
 443  463
         DetailAST node = ast;
 444  
         do {
 445  3542
             if (node.getType() == type) {
 446  20
                 result = true;
 447  20
                 break;
 448  
             }
 449  3522
             node = node.getParent();
 450  3522
         } while (node != null);
 451  
 
 452  463
         return result;
 453  
     }
 454  
 }