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 com.puppycrawl.tools.checkstyle.api.Check; 023import com.puppycrawl.tools.checkstyle.api.DetailAST; 024import com.puppycrawl.tools.checkstyle.api.TokenTypes; 025 026/** 027 * Restricts the number of statements per line to one. 028 * <p> 029 * Rationale: It's very difficult to read multiple statements on one line. 030 * </p> 031 * <p> 032 * In the Java programming language, statements are the fundamental unit of 033 * execution. All statements except blocks are terminated by a semicolon. 034 * Blocks are denoted by open and close curly braces. 035 * </p> 036 * <p> 037 * OneStatementPerLineCheck checks the following types of statements: 038 * variable declaration statements, empty statements, assignment statements, 039 * expression statements, increment statements, object creation statements, 040 * 'for loop' statements, 'break' statements, 'continue' statements, 041 * 'return' statements, import statements. 042 * </p> 043 * <p> 044 * The following examples will be flagged as a violation: 045 * </p> 046 * <pre> 047 * //Each line causes violation: 048 * int var1; int var2; 049 * var1 = 1; var2 = 2; 050 * int var1 = 1; int var2 = 2; 051 * var1++; var2++; 052 * Object obj1 = new Object(); Object obj2 = new Object(); 053 * import java.io.EOFException; import java.io.BufferedReader; 054 * ;; //two empty statements on the same line. 055 * 056 * //Multi-line statements: 057 * int var1 = 1 058 * ; var2 = 2; //violation here 059 * int o = 1, p = 2, 060 * r = 5; int t; //violation here 061 * </pre> 062 * 063 * @author Alexander Jesse 064 * @author Oliver Burn 065 * @author Andrei Selkin 066 */ 067public final class OneStatementPerLineCheck extends Check { 068 069 /** 070 * A key is pointing to the warning message text in "messages.properties" 071 * file. 072 */ 073 public static final String MSG_KEY = "multiple.statements.line"; 074 075 /** 076 * Hold the line-number where the last statement ended. 077 */ 078 private int lastStatementEnd = -1; 079 080 /** 081 * Hold the line-number where the last 'for-loop' statement ended. 082 */ 083 private int forStatementEnd = -1; 084 085 /** 086 * The for-header usually has 3 statements on one line, but THIS IS OK. 087 */ 088 private boolean inForHeader; 089 090 @Override 091 public int[] getDefaultTokens() { 092 return getAcceptableTokens(); 093 } 094 095 @Override 096 public int[] getAcceptableTokens() { 097 return new int[]{ 098 TokenTypes.SEMI, TokenTypes.FOR_INIT, 099 TokenTypes.FOR_ITERATOR, 100 }; 101 } 102 103 @Override 104 public int[] getRequiredTokens() { 105 return getAcceptableTokens(); 106 } 107 108 @Override 109 public void beginTree(DetailAST rootAST) { 110 inForHeader = false; 111 lastStatementEnd = -1; 112 forStatementEnd = -1; 113 } 114 115 @Override 116 public void visitToken(DetailAST ast) { 117 switch (ast.getType()) { 118 case TokenTypes.SEMI: 119 DetailAST currentStatement = ast; 120 if (isMultilineStatement(currentStatement)) { 121 currentStatement = ast.getPreviousSibling(); 122 } 123 if (isOnTheSameLine(currentStatement, lastStatementEnd, 124 forStatementEnd) && !inForHeader) { 125 log(ast, MSG_KEY); 126 } 127 break; 128 case TokenTypes.FOR_ITERATOR: 129 forStatementEnd = ast.getLineNo(); 130 break; 131 default: 132 inForHeader = true; 133 break; 134 } 135 } 136 137 @Override 138 public void leaveToken(DetailAST ast) { 139 switch (ast.getType()) { 140 case TokenTypes.SEMI: 141 lastStatementEnd = ast.getLineNo(); 142 forStatementEnd = -1; 143 break; 144 case TokenTypes.FOR_ITERATOR: 145 inForHeader = false; 146 break; 147 default: 148 break; 149 } 150 } 151 152 /** 153 * Checks whether two statements are on the same line. 154 * @param ast token for the current statement. 155 * @param lastStatementEnd the line-number where the last statement ended. 156 * @param forStatementEnd the line-number where the last 'for-loop' 157 * statement ended. 158 * @return true if two statements are on the same line. 159 */ 160 private static boolean isOnTheSameLine(DetailAST ast, int lastStatementEnd, 161 int forStatementEnd) { 162 return lastStatementEnd == ast.getLineNo() && forStatementEnd != ast.getLineNo(); 163 } 164 165 /** 166 * Checks whether statement is multiline. 167 * @param ast token for the current statement. 168 * @return true if one statement is distributed over two or more lines. 169 */ 170 private static boolean isMultilineStatement(DetailAST ast) { 171 final boolean multiline; 172 if (ast.getPreviousSibling() == null) { 173 multiline = false; 174 } 175 else { 176 final DetailAST prevSibling = ast.getPreviousSibling(); 177 multiline = prevSibling.getLineNo() != ast.getLineNo() 178 && ast.getParent() != null; 179 } 180 return multiline; 181 } 182}