001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 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.ArrayDeque; 023import java.util.Deque; 024 025import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 026import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.TokenTypes; 029 030/** 031 * Restricts the number of statements per line to one. 032 * <p> 033 * Rationale: It's very difficult to read multiple statements on one line. 034 * </p> 035 * <p> 036 * In the Java programming language, statements are the fundamental unit of 037 * execution. All statements except blocks are terminated by a semicolon. 038 * Blocks are denoted by open and close curly braces. 039 * </p> 040 * <p> 041 * OneStatementPerLineCheck checks the following types of statements: 042 * variable declaration statements, empty statements, assignment statements, 043 * expression statements, increment statements, object creation statements, 044 * 'for loop' statements, 'break' statements, 'continue' statements, 045 * 'return' statements, import statements. 046 * </p> 047 * <p> 048 * The following examples will be flagged as a violation: 049 * </p> 050 * <pre> 051 * //Each line causes violation: 052 * int var1; int var2; 053 * var1 = 1; var2 = 2; 054 * int var1 = 1; int var2 = 2; 055 * var1++; var2++; 056 * Object obj1 = new Object(); Object obj2 = new Object(); 057 * import java.io.EOFException; import java.io.BufferedReader; 058 * ;; //two empty statements on the same line. 059 * 060 * //Multi-line statements: 061 * int var1 = 1 062 * ; var2 = 2; //violation here 063 * int o = 1, p = 2, 064 * r = 5; int t; //violation here 065 * </pre> 066 * 067 */ 068@FileStatefulCheck 069public final class OneStatementPerLineCheck extends AbstractCheck { 070 071 /** 072 * A key is pointing to the warning message text in "messages.properties" 073 * file. 074 */ 075 public static final String MSG_KEY = "multiple.statements.line"; 076 077 /** 078 * Counts number of semicolons in nested lambdas. 079 */ 080 private final Deque<Integer> countOfSemiInLambda = new ArrayDeque<>(); 081 082 /** 083 * Hold the line-number where the last statement ended. 084 */ 085 private int lastStatementEnd = -1; 086 087 /** 088 * Hold the line-number where the last 'for-loop' statement ended. 089 */ 090 private int forStatementEnd = -1; 091 092 /** 093 * The for-header usually has 3 statements on one line, but THIS IS OK. 094 */ 095 private boolean inForHeader; 096 097 /** 098 * Holds if current token is inside lambda. 099 */ 100 private boolean isInLambda; 101 102 /** 103 * Hold the line-number where the last lambda statement ended. 104 */ 105 private int lambdaStatementEnd = -1; 106 107 @Override 108 public int[] getDefaultTokens() { 109 return getRequiredTokens(); 110 } 111 112 @Override 113 public int[] getAcceptableTokens() { 114 return getRequiredTokens(); 115 } 116 117 @Override 118 public int[] getRequiredTokens() { 119 return new int[] { 120 TokenTypes.SEMI, 121 TokenTypes.FOR_INIT, 122 TokenTypes.FOR_ITERATOR, 123 TokenTypes.LAMBDA, 124 }; 125 } 126 127 @Override 128 public void beginTree(DetailAST rootAST) { 129 inForHeader = false; 130 lastStatementEnd = -1; 131 forStatementEnd = -1; 132 isInLambda = false; 133 } 134 135 @Override 136 public void visitToken(DetailAST ast) { 137 switch (ast.getType()) { 138 case TokenTypes.SEMI: 139 checkIfSemicolonIsInDifferentLineThanPrevious(ast); 140 break; 141 case TokenTypes.FOR_ITERATOR: 142 forStatementEnd = ast.getLineNo(); 143 break; 144 case TokenTypes.LAMBDA: 145 isInLambda = true; 146 countOfSemiInLambda.push(0); 147 break; 148 default: 149 inForHeader = true; 150 break; 151 } 152 } 153 154 @Override 155 public void leaveToken(DetailAST ast) { 156 switch (ast.getType()) { 157 case TokenTypes.SEMI: 158 lastStatementEnd = ast.getLineNo(); 159 forStatementEnd = -1; 160 lambdaStatementEnd = -1; 161 break; 162 case TokenTypes.FOR_ITERATOR: 163 inForHeader = false; 164 break; 165 case TokenTypes.LAMBDA: 166 countOfSemiInLambda.pop(); 167 if (countOfSemiInLambda.isEmpty()) { 168 isInLambda = false; 169 } 170 lambdaStatementEnd = ast.getLineNo(); 171 break; 172 default: 173 break; 174 } 175 } 176 177 /** 178 * Checks if given semicolon is in different line than previous. 179 * @param ast semicolon to check 180 */ 181 private void checkIfSemicolonIsInDifferentLineThanPrevious(DetailAST ast) { 182 DetailAST currentStatement = ast; 183 final boolean hasResourcesPrevSibling = 184 currentStatement.getPreviousSibling() != null 185 && currentStatement.getPreviousSibling().getType() == TokenTypes.RESOURCES; 186 if (!hasResourcesPrevSibling && isMultilineStatement(currentStatement)) { 187 currentStatement = ast.getPreviousSibling(); 188 } 189 if (isInLambda) { 190 int countOfSemiInCurrentLambda = countOfSemiInLambda.pop(); 191 countOfSemiInCurrentLambda++; 192 countOfSemiInLambda.push(countOfSemiInCurrentLambda); 193 if (!inForHeader && countOfSemiInCurrentLambda > 1 194 && isOnTheSameLine(currentStatement, 195 lastStatementEnd, forStatementEnd, 196 lambdaStatementEnd)) { 197 log(ast, MSG_KEY); 198 } 199 } 200 else if (!inForHeader && isOnTheSameLine(currentStatement, lastStatementEnd, 201 forStatementEnd, lambdaStatementEnd)) { 202 log(ast, MSG_KEY); 203 } 204 } 205 206 /** 207 * Checks whether two statements are on the same line. 208 * @param ast token for the current statement. 209 * @param lastStatementEnd the line-number where the last statement ended. 210 * @param forStatementEnd the line-number where the last 'for-loop' 211 * statement ended. 212 * @param lambdaStatementEnd the line-number where the last lambda 213 * statement ended. 214 * @return true if two statements are on the same line. 215 */ 216 private static boolean isOnTheSameLine(DetailAST ast, int lastStatementEnd, 217 int forStatementEnd, int lambdaStatementEnd) { 218 return lastStatementEnd == ast.getLineNo() && forStatementEnd != ast.getLineNo() 219 && lambdaStatementEnd != ast.getLineNo(); 220 } 221 222 /** 223 * Checks whether statement is multiline. 224 * @param ast token for the current statement. 225 * @return true if one statement is distributed over two or more lines. 226 */ 227 private static boolean isMultilineStatement(DetailAST ast) { 228 final boolean multiline; 229 if (ast.getPreviousSibling() == null) { 230 multiline = false; 231 } 232 else { 233 final DetailAST prevSibling = ast.getPreviousSibling(); 234 multiline = prevSibling.getLineNo() != ast.getLineNo() 235 && ast.getParent() != null; 236 } 237 return multiline; 238 } 239 240}