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.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 * @author Alexander Jesse 068 * @author Oliver Burn 069 * @author Andrei Selkin 070 */ 071@FileStatefulCheck 072public final class OneStatementPerLineCheck extends AbstractCheck { 073 074 /** 075 * A key is pointing to the warning message text in "messages.properties" 076 * file. 077 */ 078 public static final String MSG_KEY = "multiple.statements.line"; 079 080 /** 081 * Counts number of semicolons in nested lambdas. 082 */ 083 private final Deque<Integer> countOfSemiInLambda = new ArrayDeque<>(); 084 085 /** 086 * Hold the line-number where the last statement ended. 087 */ 088 private int lastStatementEnd = -1; 089 090 /** 091 * Hold the line-number where the last 'for-loop' statement ended. 092 */ 093 private int forStatementEnd = -1; 094 095 /** 096 * The for-header usually has 3 statements on one line, but THIS IS OK. 097 */ 098 private boolean inForHeader; 099 100 /** 101 * Holds if current token is inside lambda. 102 */ 103 private boolean isInLambda; 104 105 /** 106 * Hold the line-number where the last lambda statement ended. 107 */ 108 private int lambdaStatementEnd = -1; 109 110 @Override 111 public int[] getDefaultTokens() { 112 return getRequiredTokens(); 113 } 114 115 @Override 116 public int[] getAcceptableTokens() { 117 return getRequiredTokens(); 118 } 119 120 @Override 121 public int[] getRequiredTokens() { 122 return new int[] { 123 TokenTypes.SEMI, 124 TokenTypes.FOR_INIT, 125 TokenTypes.FOR_ITERATOR, 126 TokenTypes.LAMBDA, 127 }; 128 } 129 130 @Override 131 public void beginTree(DetailAST rootAST) { 132 inForHeader = false; 133 lastStatementEnd = -1; 134 forStatementEnd = -1; 135 isInLambda = false; 136 } 137 138 @Override 139 public void visitToken(DetailAST ast) { 140 switch (ast.getType()) { 141 case TokenTypes.SEMI: 142 checkIfSemicolonIsInDifferentLineThanPrevious(ast); 143 break; 144 case TokenTypes.FOR_ITERATOR: 145 forStatementEnd = ast.getLineNo(); 146 break; 147 case TokenTypes.LAMBDA: 148 isInLambda = true; 149 countOfSemiInLambda.push(0); 150 break; 151 default: 152 inForHeader = true; 153 break; 154 } 155 } 156 157 @Override 158 public void leaveToken(DetailAST ast) { 159 switch (ast.getType()) { 160 case TokenTypes.SEMI: 161 lastStatementEnd = ast.getLineNo(); 162 forStatementEnd = -1; 163 lambdaStatementEnd = -1; 164 break; 165 case TokenTypes.FOR_ITERATOR: 166 inForHeader = false; 167 break; 168 case TokenTypes.LAMBDA: 169 countOfSemiInLambda.pop(); 170 if (countOfSemiInLambda.isEmpty()) { 171 isInLambda = false; 172 } 173 lambdaStatementEnd = ast.getLineNo(); 174 break; 175 default: 176 break; 177 } 178 } 179 180 /** 181 * Checks if given semicolon is in different line than previous. 182 * @param ast semicolon to check 183 */ 184 private void checkIfSemicolonIsInDifferentLineThanPrevious(DetailAST ast) { 185 DetailAST currentStatement = ast; 186 final boolean hasResourcesPrevSibling = 187 currentStatement.getPreviousSibling() != null 188 && currentStatement.getPreviousSibling().getType() == TokenTypes.RESOURCES; 189 if (!hasResourcesPrevSibling && isMultilineStatement(currentStatement)) { 190 currentStatement = ast.getPreviousSibling(); 191 } 192 if (isInLambda) { 193 int countOfSemiInCurrentLambda = countOfSemiInLambda.pop(); 194 countOfSemiInCurrentLambda++; 195 countOfSemiInLambda.push(countOfSemiInCurrentLambda); 196 if (!inForHeader && countOfSemiInCurrentLambda > 1 197 && isOnTheSameLine(currentStatement, 198 lastStatementEnd, forStatementEnd, 199 lambdaStatementEnd)) { 200 log(ast, MSG_KEY); 201 } 202 } 203 else if (!inForHeader && isOnTheSameLine(currentStatement, lastStatementEnd, 204 forStatementEnd, lambdaStatementEnd)) { 205 log(ast, MSG_KEY); 206 } 207 } 208 209 /** 210 * Checks whether two statements are on the same line. 211 * @param ast token for the current statement. 212 * @param lastStatementEnd the line-number where the last statement ended. 213 * @param forStatementEnd the line-number where the last 'for-loop' 214 * statement ended. 215 * @param lambdaStatementEnd the line-number where the last lambda 216 * statement ended. 217 * @return true if two statements are on the same line. 218 */ 219 private static boolean isOnTheSameLine(DetailAST ast, int lastStatementEnd, 220 int forStatementEnd, int lambdaStatementEnd) { 221 return lastStatementEnd == ast.getLineNo() && forStatementEnd != ast.getLineNo() 222 && lambdaStatementEnd != ast.getLineNo(); 223 } 224 225 /** 226 * Checks whether statement is multiline. 227 * @param ast token for the current statement. 228 * @return true if one statement is distributed over two or more lines. 229 */ 230 private static boolean isMultilineStatement(DetailAST ast) { 231 final boolean multiline; 232 if (ast.getPreviousSibling() == null) { 233 multiline = false; 234 } 235 else { 236 final DetailAST prevSibling = ast.getPreviousSibling(); 237 multiline = prevSibling.getLineNo() != ast.getLineNo() 238 && ast.getParent() != null; 239 } 240 return multiline; 241 } 242 243}