001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2015 the original author or authors. 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.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.TokenTypes; 029 030/** 031 * <p> 032 * Checks for assignments in subexpressions, such as in 033 * {@code String s = Integer.toString(i = 2);}. 034 * </p> 035 * <p> 036 * Rationale: With the exception of {@code for} iterators, all assignments 037 * should occur in their own top-level 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 * A key is pointing to the warning message text in "messages.properties" 049 * file. 050 */ 051 public static final String MSG_KEY = "assignment.inner.avoid"; 052 053 /** 054 * List of allowed AST types from an assignment AST node 055 * towards the root. 056 */ 057 private static final int[][] ALLOWED_ASSIGNMENT_CONTEXT = { 058 {TokenTypes.EXPR, TokenTypes.SLIST}, 059 {TokenTypes.VARIABLE_DEF}, 060 {TokenTypes.EXPR, TokenTypes.ELIST, TokenTypes.FOR_INIT}, 061 {TokenTypes.EXPR, TokenTypes.ELIST, TokenTypes.FOR_ITERATOR}, 062 {TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR}, { 063 TokenTypes.RESOURCE, 064 TokenTypes.RESOURCES, 065 TokenTypes.RESOURCE_SPECIFICATION, 066 }, 067 {TokenTypes.EXPR, TokenTypes.LAMBDA}, 068 }; 069 070 /** 071 * List of allowed AST types from an assignment AST node 072 * towards the root. 073 */ 074 private static final int[][] CONTROL_CONTEXT = { 075 {TokenTypes.EXPR, TokenTypes.LITERAL_DO}, 076 {TokenTypes.EXPR, TokenTypes.LITERAL_FOR}, 077 {TokenTypes.EXPR, TokenTypes.LITERAL_WHILE}, 078 {TokenTypes.EXPR, TokenTypes.LITERAL_IF}, 079 {TokenTypes.EXPR, TokenTypes.LITERAL_ELSE}, 080 }; 081 082 /** 083 * List of allowed AST types from a comparison node (above an assignment) 084 * towards the root. 085 */ 086 private static final int[][] ALLOWED_ASSIGNMENT_IN_COMPARISON_CONTEXT = { 087 {TokenTypes.EXPR, TokenTypes.LITERAL_WHILE, }, 088 }; 089 090 /** 091 * The token types that identify comparison operators. 092 */ 093 private static final int[] COMPARISON_TYPES = { 094 TokenTypes.EQUAL, 095 TokenTypes.GE, 096 TokenTypes.GT, 097 TokenTypes.LE, 098 TokenTypes.LT, 099 TokenTypes.NOT_EQUAL, 100 }; 101 102 static { 103 Arrays.sort(COMPARISON_TYPES); 104 } 105 106 @Override 107 public int[] getDefaultTokens() { 108 return getAcceptableTokens(); 109 } 110 111 @Override 112 public int[] getAcceptableTokens() { 113 return new int[] { 114 TokenTypes.ASSIGN, // '=' 115 TokenTypes.DIV_ASSIGN, // "/=" 116 TokenTypes.PLUS_ASSIGN, // "+=" 117 TokenTypes.MINUS_ASSIGN, //"-=" 118 TokenTypes.STAR_ASSIGN, // "*=" 119 TokenTypes.MOD_ASSIGN, // "%=" 120 TokenTypes.SR_ASSIGN, // ">>=" 121 TokenTypes.BSR_ASSIGN, // ">>>=" 122 TokenTypes.SL_ASSIGN, // "<<=" 123 TokenTypes.BXOR_ASSIGN, // "^=" 124 TokenTypes.BOR_ASSIGN, // "|=" 125 TokenTypes.BAND_ASSIGN, // "&=" 126 }; 127 } 128 129 @Override 130 public int[] getRequiredTokens() { 131 return getAcceptableTokens(); 132 } 133 134 @Override 135 public void visitToken(DetailAST ast) { 136 if (!isInContext(ast, ALLOWED_ASSIGNMENT_CONTEXT) 137 && !isInNoBraceControlStatement(ast) 138 && !isInWhileIdiom(ast)) { 139 log(ast.getLineNo(), ast.getColumnNo(), MSG_KEY); 140 } 141 } 142 143 /** 144 * Determines if ast is in the body of a flow control statement without 145 * braces. An example of such a statement would be 146 * <p> 147 * <pre> 148 * if (y < 0) 149 * x = y; 150 * </pre> 151 * </p> 152 * <p> 153 * This leads to the following AST structure: 154 * </p> 155 * <p> 156 * <pre> 157 * LITERAL_IF 158 * LPAREN 159 * EXPR // test 160 * RPAREN 161 * EXPR // body 162 * SEMI 163 * </pre> 164 * </p> 165 * <p> 166 * We need to ensure that ast is in the body and not in the test. 167 * </p> 168 * 169 * @param ast an assignment operator AST 170 * @return whether ast is in the body of a flow control statement 171 */ 172 private static boolean isInNoBraceControlStatement(DetailAST ast) { 173 if (!isInContext(ast, CONTROL_CONTEXT)) { 174 return false; 175 } 176 final DetailAST expr = ast.getParent(); 177 final AST exprNext = expr.getNextSibling(); 178 return exprNext.getType() == TokenTypes.SEMI; 179 } 180 181 /** 182 * Tests whether the given AST is used in the "assignment in while" idiom. 183 * <pre> 184 * String line; 185 * while ((line = bufferedReader.readLine()) != null) { 186 * // process the line 187 * } 188 * </pre> 189 * Assignment inside a condition is not a problem here, as the assignment is surrounded by an 190 * extra pair of parentheses. The comparison is {@code != null} and there is no chance that 191 * intention was to write {@code line == reader.readLine()}. 192 * 193 * @param ast assignment AST 194 * @return whether the context of the assignment AST indicates the idiom 195 */ 196 private static boolean isInWhileIdiom(DetailAST ast) { 197 if (!isComparison(ast.getParent())) { 198 return false; 199 } 200 return isInContext( 201 ast.getParent(), ALLOWED_ASSIGNMENT_IN_COMPARISON_CONTEXT); 202 } 203 204 /** 205 * Checks if an AST is a comparison operator. 206 * @param ast the AST to check 207 * @return true iff ast is a comparison operator. 208 */ 209 private static boolean isComparison(DetailAST ast) { 210 final int astType = ast.getType(); 211 return Arrays.binarySearch(COMPARISON_TYPES, astType) >= 0; 212 } 213 214 /** 215 * Tests whether the provided AST is in 216 * one of the given contexts. 217 * 218 * @param ast the AST from which to start walking towards root 219 * @param contextSet the contexts to test against. 220 * 221 * @return whether the parents nodes of ast match one of the allowed type paths. 222 */ 223 private static boolean isInContext(DetailAST ast, int[]... contextSet) { 224 boolean found = false; 225 for (int[] element : contextSet) { 226 DetailAST current = ast; 227 for (int anElement : element) { 228 current = current.getParent(); 229 if (current.getType() == anElement) { 230 found = true; 231 } 232 else { 233 found = false; 234 break; 235 } 236 } 237 238 if (found) { 239 break; 240 } 241 } 242 return found; 243 } 244}