001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 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; 025import com.puppycrawl.tools.checkstyle.StatelessCheck; 026import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 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 */ 043@StatelessCheck 044public class InnerAssignmentCheck 045 extends AbstractCheck { 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 getRequiredTokens(); 109 } 110 111 @Override 112 public int[] getAcceptableTokens() { 113 return getRequiredTokens(); 114 } 115 116 @Override 117 public int[] getRequiredTokens() { 118 return new int[] { 119 TokenTypes.ASSIGN, // '=' 120 TokenTypes.DIV_ASSIGN, // "/=" 121 TokenTypes.PLUS_ASSIGN, // "+=" 122 TokenTypes.MINUS_ASSIGN, //"-=" 123 TokenTypes.STAR_ASSIGN, // "*=" 124 TokenTypes.MOD_ASSIGN, // "%=" 125 TokenTypes.SR_ASSIGN, // ">>=" 126 TokenTypes.BSR_ASSIGN, // ">>>=" 127 TokenTypes.SL_ASSIGN, // "<<=" 128 TokenTypes.BXOR_ASSIGN, // "^=" 129 TokenTypes.BOR_ASSIGN, // "|=" 130 TokenTypes.BAND_ASSIGN, // "&=" 131 }; 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, 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 boolean result = false; 174 if (isInContext(ast, CONTROL_CONTEXT)) { 175 final DetailAST expr = ast.getParent(); 176 final AST exprNext = expr.getNextSibling(); 177 result = exprNext.getType() == TokenTypes.SEMI; 178 } 179 return result; 180 } 181 182 /** 183 * Tests whether the given AST is used in the "assignment in while" idiom. 184 * <pre> 185 * String line; 186 * while ((line = bufferedReader.readLine()) != null) { 187 * // process the line 188 * } 189 * </pre> 190 * Assignment inside a condition is not a problem here, as the assignment is surrounded by an 191 * extra pair of parentheses. The comparison is {@code != null} and there is no chance that 192 * intention was to write {@code line == reader.readLine()}. 193 * 194 * @param ast assignment AST 195 * @return whether the context of the assignment AST indicates the idiom 196 */ 197 private static boolean isInWhileIdiom(DetailAST ast) { 198 boolean result = false; 199 if (isComparison(ast.getParent())) { 200 result = isInContext( 201 ast.getParent(), ALLOWED_ASSIGNMENT_IN_COMPARISON_CONTEXT); 202 } 203 return result; 204 } 205 206 /** 207 * Checks if an AST is a comparison operator. 208 * @param ast the AST to check 209 * @return true iff ast is a comparison operator. 210 */ 211 private static boolean isComparison(DetailAST ast) { 212 final int astType = ast.getType(); 213 return Arrays.binarySearch(COMPARISON_TYPES, astType) >= 0; 214 } 215 216 /** 217 * Tests whether the provided AST is in 218 * one of the given contexts. 219 * 220 * @param ast the AST from which to start walking towards root 221 * @param contextSet the contexts to test against. 222 * 223 * @return whether the parents nodes of ast match one of the allowed type paths. 224 */ 225 private static boolean isInContext(DetailAST ast, int[]... contextSet) { 226 boolean found = false; 227 for (int[] element : contextSet) { 228 DetailAST current = ast; 229 for (int anElement : element) { 230 current = current.getParent(); 231 if (current.getType() == anElement) { 232 found = true; 233 } 234 else { 235 found = false; 236 break; 237 } 238 } 239 240 if (found) { 241 break; 242 } 243 } 244 return found; 245 } 246 247}