1 ////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code for adherence to a set of rules.
3 // Copyright (C) 2001-2019 the original author or authors.
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ////////////////////////////////////////////////////////////////////////////////
19
20 package com.puppycrawl.tools.checkstyle.checks.coding;
21
22 import java.util.ArrayDeque;
23 import java.util.Deque;
24
25 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
26 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
27 import com.puppycrawl.tools.checkstyle.api.DetailAST;
28 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
29
30 /**
31 * Restricts the number of statements per line to one.
32 * <p>
33 * Rationale: It's very difficult to read multiple statements on one line.
34 * </p>
35 * <p>
36 * In the Java programming language, statements are the fundamental unit of
37 * execution. All statements except blocks are terminated by a semicolon.
38 * Blocks are denoted by open and close curly braces.
39 * </p>
40 * <p>
41 * OneStatementPerLineCheck checks the following types of statements:
42 * variable declaration statements, empty statements, assignment statements,
43 * expression statements, increment statements, object creation statements,
44 * 'for loop' statements, 'break' statements, 'continue' statements,
45 * 'return' statements, import statements.
46 * </p>
47 * <p>
48 * The following examples will be flagged as a violation:
49 * </p>
50 * <pre>
51 * //Each line causes violation:
52 * int var1; int var2;
53 * var1 = 1; var2 = 2;
54 * int var1 = 1; int var2 = 2;
55 * var1++; var2++;
56 * Object obj1 = new Object(); Object obj2 = new Object();
57 * import java.io.EOFException; import java.io.BufferedReader;
58 * ;; //two empty statements on the same line.
59 *
60 * //Multi-line statements:
61 * int var1 = 1
62 * ; var2 = 2; //violation here
63 * int o = 1, p = 2,
64 * r = 5; int t; //violation here
65 * </pre>
66 *
67 */
68 @FileStatefulCheck
69 public final class OneStatementPerLineCheck extends AbstractCheck {
70
71 /**
72 * A key is pointing to the warning message text in "messages.properties"
73 * file.
74 */
75 public static final String MSG_KEY = "multiple.statements.line";
76
77 /**
78 * Counts number of semicolons in nested lambdas.
79 */
80 private final Deque<Integer> countOfSemiInLambda = new ArrayDeque<>();
81
82 /**
83 * Hold the line-number where the last statement ended.
84 */
85 private int lastStatementEnd = -1;
86
87 /**
88 * Hold the line-number where the last 'for-loop' statement ended.
89 */
90 private int forStatementEnd = -1;
91
92 /**
93 * The for-header usually has 3 statements on one line, but THIS IS OK.
94 */
95 private boolean inForHeader;
96
97 /**
98 * Holds if current token is inside lambda.
99 */
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 }