1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle.checks.coding;
21
22 import java.util.ArrayDeque;
23 import java.util.Deque;
24 import java.util.Set;
25
26 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
27 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
28 import com.puppycrawl.tools.checkstyle.api.DetailAST;
29 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30 import com.puppycrawl.tools.checkstyle.utils.NullUtil;
31 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63 @FileStatefulCheck
64 public final class OneStatementPerLineCheck extends AbstractCheck {
65
66
67
68
69
70 public static final String MSG_KEY = "multiple.statements.line";
71
72
73 private static final Set<Integer> VALID_SEMI_PARENT = Set.of(
74 TokenTypes.VARIABLE_DEF,
75 TokenTypes.IMPORT,
76 TokenTypes.STATIC_IMPORT,
77 TokenTypes.MODULE_IMPORT,
78 TokenTypes.LITERAL_RETURN,
79 TokenTypes.LITERAL_BREAK,
80 TokenTypes.LITERAL_CONTINUE,
81 TokenTypes.PACKAGE_DEF,
82 TokenTypes.SUPER_CTOR_CALL,
83 TokenTypes.CTOR_CALL,
84 TokenTypes.LITERAL_ASSERT,
85 TokenTypes.LITERAL_YIELD,
86 TokenTypes.METHOD_DEF,
87 TokenTypes.ANNOTATION_FIELD_DEF
88 );
89
90
91
92
93
94 private final Deque<Integer> nestingScope = new ArrayDeque<>();
95
96
97
98
99 private int lastStatementEnd;
100
101
102
103
104 private int lastVariableResourceStatementEnd;
105
106
107
108
109 private boolean treatTryResourcesAsStatement;
110
111
112
113
114 private int lastStatementInCurrentScope;
115
116
117
118
119 private boolean isFirstStatementOfLambdaOrAnonymous;
120
121
122
123
124 public OneStatementPerLineCheck() {
125
126 }
127
128
129
130
131
132
133
134 public void setTreatTryResourcesAsStatement(boolean treatTryResourcesAsStatement) {
135 this.treatTryResourcesAsStatement = treatTryResourcesAsStatement;
136 }
137
138 @Override
139 public int[] getDefaultTokens() {
140 return getRequiredTokens();
141 }
142
143 @Override
144 public int[] getAcceptableTokens() {
145 return getRequiredTokens();
146 }
147
148 @Override
149 public int[] getRequiredTokens() {
150 return new int[] {
151 TokenTypes.SEMI,
152 TokenTypes.LAMBDA,
153 TokenTypes.OBJBLOCK,
154 TokenTypes.METHOD_CALL,
155 TokenTypes.CLASS_DEF,
156 TokenTypes.INTERFACE_DEF,
157 TokenTypes.ENUM_DEF,
158 TokenTypes.RECORD_DEF,
159 TokenTypes.ANNOTATION_DEF,
160 };
161 }
162
163 @Override
164 public void beginTree(DetailAST rootAST) {
165 lastStatementEnd = 0;
166 lastVariableResourceStatementEnd = lastStatementEnd;
167 lastStatementInCurrentScope = lastStatementEnd;
168 }
169
170 @Override
171 public void visitToken(DetailAST ast) {
172 switch (ast.getType()) {
173 case TokenTypes.SEMI ->
174 checkIfSemicolonIsInDifferentLineThanPrevious(ast);
175 case TokenTypes.LAMBDA, TokenTypes.OBJBLOCK -> {
176 if (ast.getType() == TokenTypes.LAMBDA
177 || ast.getParent().getType() == TokenTypes.LITERAL_NEW) {
178 nestingScope.push(lastStatementEnd);
179 isFirstStatementOfLambdaOrAnonymous = true;
180 }
181 }
182 case TokenTypes.METHOD_CALL -> nestingScope.push(lastStatementEnd);
183 default -> {
184
185 DetailAST previousNode = ast.getPreviousSibling();
186 if (previousNode != null) {
187 previousNode = getLastNestedLeafNode(previousNode);
188 if (TokenUtil.areOnSameLine(ast, previousNode)) {
189 logViolation(ast);
190 }
191 }
192 }
193 }
194 }
195
196 @Override
197 public void leaveToken(DetailAST ast) {
198 switch (ast.getType()) {
199 case TokenTypes.SEMI -> {
200 if (getStatementStart(ast).getType() != TokenTypes.SEMI) {
201 lastStatementEnd = ast.getLineNo();
202 }
203 isFirstStatementOfLambdaOrAnonymous = false;
204 lastStatementInCurrentScope = lastStatementEnd;
205 }
206 case TokenTypes.LAMBDA, TokenTypes.OBJBLOCK -> {
207 if (ast.getType() == TokenTypes.LAMBDA
208 || ast.getParent().getType() == TokenTypes.LITERAL_NEW) {
209 lastStatementInCurrentScope = nestingScope.pop();
210 isFirstStatementOfLambdaOrAnonymous = false;
211 }
212 }
213 case TokenTypes.METHOD_CALL -> lastStatementInCurrentScope = nestingScope.pop();
214 default -> {
215
216 }
217 }
218 }
219
220
221
222
223
224
225
226 private void checkIfSemicolonIsInDifferentLineThanPrevious(DetailAST ast) {
227 boolean validStatement = true;
228 DetailAST statementStart = getStatementStart(ast);
229 if (isResource(ast.getParent())) {
230 validStatement = checkResourceVariable(ast);
231 statementStart = NullUtil.notNull(ast.getNextSibling());
232 }
233 else if (!isFirstStatementOfLambdaOrAnonymous
234 && statementStart.getType() != TokenTypes.SEMI) {
235 validStatement = isValidStatement(statementStart);
236 }
237 if (!validStatement) {
238 logViolation(statementStart);
239 }
240 }
241
242
243
244
245
246
247 private void logViolation(DetailAST violationNode) {
248 log(violationNode, MSG_KEY);
249 }
250
251
252
253
254
255
256
257
258
259 private boolean isValidStatement(DetailAST statementStart) {
260 boolean blockStatementBefore = false;
261 DetailAST previousNode = statementStart.getPreviousSibling();
262 final boolean isBlockStatement = TokenUtil.isOfType(previousNode,
263 TokenTypes.CLASS_DEF,
264 TokenTypes.INTERFACE_DEF,
265 TokenTypes.ENUM_DEF,
266 TokenTypes.RECORD_DEF,
267 TokenTypes.ANNOTATION_DEF,
268 TokenTypes.METHOD_DEF);
269 if (isBlockStatement) {
270 previousNode = getLastNestedLeafNode(previousNode);
271 blockStatementBefore = TokenUtil.areOnSameLine(statementStart, previousNode);
272 }
273 return !blockStatementBefore && statementStart.getLineNo() != lastStatementInCurrentScope;
274 }
275
276
277
278
279
280
281
282
283 private static DetailAST getStatementStart(DetailAST ast) {
284 DetailAST statementStart = ast;
285 final DetailAST parent = ast.getParent();
286 final DetailAST previousSibling = ast.getPreviousSibling();
287 final boolean validPreviousSibling = previousSibling != null
288 && (previousSibling.getType() == TokenTypes.VARIABLE_DEF
289 || previousSibling.getType() == TokenTypes.EXPR
290 || previousSibling.getType() == TokenTypes.ENUM_CONSTANT_DEF)
291 && previousSibling.findFirstToken(TokenTypes.SEMI) == null;
292 if (VALID_SEMI_PARENT.contains(parent.getType())) {
293 statementStart = ast.getParent();
294 }
295 else if (validPreviousSibling) {
296 statementStart = getStartNodeInExpression(previousSibling);
297 }
298 return statementStart;
299 }
300
301
302
303
304
305
306
307
308
309 private boolean checkResourceVariable(DetailAST currentStatement) {
310 boolean result = true;
311 if (treatTryResourcesAsStatement) {
312 final DetailAST nextNode = currentStatement.getNextSibling();
313 if (currentStatement.getPreviousSibling().findFirstToken(TokenTypes.ASSIGN) != null) {
314 lastVariableResourceStatementEnd = currentStatement.getLineNo();
315 }
316 result = nextNode.findFirstToken(TokenTypes.ASSIGN) == null
317 || nextNode.getLineNo() != lastVariableResourceStatementEnd;
318 }
319 return result;
320 }
321
322
323
324
325
326
327
328 private static DetailAST getStartNodeInExpression(DetailAST expression) {
329 DetailAST child = expression;
330 while (child.hasChildren()) {
331 child = child.getFirstChild();
332 }
333 return child;
334 }
335
336
337
338
339
340
341
342 private static DetailAST getLastNestedLeafNode(DetailAST startToken) {
343 DetailAST current = startToken;
344 while (current.hasChildren()) {
345 current = current.getLastChild();
346 }
347 return current;
348 }
349
350
351
352
353
354
355
356 private static boolean isResource(DetailAST ast) {
357 return ast.getType() == TokenTypes.RESOURCES;
358 }
359
360 }