001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2014 Oliver Burn 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.coding; 021 022import java.util.Arrays; 023 024import antlr.collections.AST; 025 026import com.puppycrawl.tools.checkstyle.api.Check; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028import com.puppycrawl.tools.checkstyle.api.DetailAST; 029 030/** 031 * <p> 032 * Checks for assignments in subexpressions, such as in 033 * <code>String s = Integer.toString(i = 2);</code>. 034 * </p> 035 * <p> 036 * Rationale: With the exception of <code>for</code> iterators, all assignments 037 * should occur in their own toplevel statement to increase readability. 038 * With inner assignments like the above it is difficult to see all places 039 * where a variable is set. 040 * </p> 041 * 042 * @author lkuehne 043 */ 044public class InnerAssignmentCheck 045 extends Check 046{ 047 /** 048 * list of allowed AST types from an assignement AST node 049 * towards the root. 050 */ 051 private static final int[][] ALLOWED_ASSIGMENT_CONTEXT = { 052 {TokenTypes.EXPR, TokenTypes.SLIST}, 053 {TokenTypes.VARIABLE_DEF}, 054 {TokenTypes.EXPR, TokenTypes.ELIST, TokenTypes.FOR_INIT}, 055 {TokenTypes.EXPR, TokenTypes.ELIST, TokenTypes.FOR_ITERATOR}, 056 {TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR}, 057 { 058 TokenTypes.RESOURCE, 059 TokenTypes.RESOURCES, 060 TokenTypes.RESOURCE_SPECIFICATION, 061 }, 062 {TokenTypes.EXPR, TokenTypes.LAMBDA}, 063 }; 064 065 /** 066 * list of allowed AST types from an assignement AST node 067 * towards the root. 068 */ 069 private static final int[][] CONTROL_CONTEXT = { 070 {TokenTypes.EXPR, TokenTypes.LITERAL_DO}, 071 {TokenTypes.EXPR, TokenTypes.LITERAL_FOR}, 072 {TokenTypes.EXPR, TokenTypes.LITERAL_WHILE}, 073 {TokenTypes.EXPR, TokenTypes.LITERAL_IF}, 074 {TokenTypes.EXPR, TokenTypes.LITERAL_ELSE}, 075 }; 076 077 /** 078 * list of allowed AST types from a comparison node (above an assignement) 079 * towards the root. 080 */ 081 private static final int[][] ALLOWED_ASSIGMENT_IN_COMPARISON_CONTEXT = { 082 {TokenTypes.EXPR, TokenTypes.LITERAL_WHILE, }, 083 }; 084 085 /** 086 * The token types that identify comparison operators. 087 */ 088 private static final int[] COMPARISON_TYPES = { 089 TokenTypes.EQUAL, 090 TokenTypes.GE, 091 TokenTypes.GT, 092 TokenTypes.LE, 093 TokenTypes.LT, 094 TokenTypes.NOT_EQUAL, 095 }; 096 097 static { 098 Arrays.sort(COMPARISON_TYPES); 099 } 100 101 @Override 102 public int[] getDefaultTokens() 103 { 104 return new int[] { 105 TokenTypes.ASSIGN, // '=' 106 TokenTypes.DIV_ASSIGN, // "/=" 107 TokenTypes.PLUS_ASSIGN, // "+=" 108 TokenTypes.MINUS_ASSIGN, //"-=" 109 TokenTypes.STAR_ASSIGN, // "*=" 110 TokenTypes.MOD_ASSIGN, // "%=" 111 TokenTypes.SR_ASSIGN, // ">>=" 112 TokenTypes.BSR_ASSIGN, // ">>>=" 113 TokenTypes.SL_ASSIGN, // "<<=" 114 TokenTypes.BXOR_ASSIGN, // "^=" 115 TokenTypes.BOR_ASSIGN, // "|=" 116 TokenTypes.BAND_ASSIGN, // "&=" 117 }; 118 } 119 120 @Override 121 public void visitToken(DetailAST ast) 122 { 123 if (isInContext(ast, ALLOWED_ASSIGMENT_CONTEXT)) { 124 return; 125 } 126 127 if (isInNoBraceControlStatement(ast)) { 128 return; 129 } 130 131 if (isInWhileIdiom(ast)) { 132 return; 133 } 134 135 log(ast.getLineNo(), ast.getColumnNo(), "assignment.inner.avoid"); 136 } 137 138 /** 139 * Determines if ast is in the body of a flow control statement without 140 * braces. An example of such a statement would be 141 * <p> 142 * <pre> 143 * if (y < 0) 144 * x = y; 145 * </pre> 146 * </p> 147 * <p> 148 * This leads to the following AST structure: 149 * </p> 150 * <p> 151 * <pre> 152 * LITERAL_IF 153 * LPAREN 154 * EXPR // test 155 * RPAREN 156 * EXPR // body 157 * SEMI 158 * </pre> 159 * </p> 160 * <p> 161 * We need to ensure that ast is in the body and not in the test. 162 * </p> 163 * 164 * @param ast an assignment operator AST 165 * @return whether ast is in the body of a flow control statement 166 */ 167 private static boolean isInNoBraceControlStatement(DetailAST ast) 168 { 169 if (!isInContext(ast, CONTROL_CONTEXT)) { 170 return false; 171 } 172 final DetailAST expr = ast.getParent(); 173 final AST exprNext = expr.getNextSibling(); 174 return (exprNext != null) && (exprNext.getType() == TokenTypes.SEMI); 175 } 176 177 /** 178 * Tests whether the given AST is used in the "assignment in while test" 179 * idiom. 180 * <p> 181 * <pre> 182 * while ((b = is.read()) != -1) { 183 * // work with b 184 * } 185 * </pre> 186 * </p> 187 * 188 * @param ast assignment AST 189 * @return whether the context of the assignemt AST indicates the idiom 190 */ 191 private boolean isInWhileIdiom(DetailAST ast) 192 { 193 if (!isComparison(ast.getParent())) { 194 return false; 195 } 196 return isInContext( 197 ast.getParent(), ALLOWED_ASSIGMENT_IN_COMPARISON_CONTEXT); 198 } 199 200 /** 201 * Checks if an AST is a comparison operator. 202 * @param ast the AST to check 203 * @return true iff ast is a comparison operator. 204 */ 205 private static boolean isComparison(DetailAST ast) 206 { 207 final int astType = ast.getType(); 208 return (Arrays.binarySearch(COMPARISON_TYPES, astType) >= 0); 209 } 210 211 /** 212 * Tests whether the provided AST is in 213 * one of the given contexts. 214 * 215 * @param ast the AST from which to start walking towards root 216 * @param contextSet the contexts to test against. 217 * 218 * @return whether the parents nodes of ast match 219 * one of the allowed type paths 220 */ 221 private static boolean isInContext(DetailAST ast, int[][] contextSet) 222 { 223 for (int[] element : contextSet) { 224 DetailAST current = ast; 225 final int len = element.length; 226 for (int j = 0; j < len; j++) { 227 current = current.getParent(); 228 final int expectedType = element[j]; 229 if ((current == null) || (current.getType() != expectedType)) { 230 break; 231 } 232 if (j == len - 1) { 233 return true; 234 } 235 } 236 } 237 return false; 238 } 239}