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.whitespace;
21
22 import java.util.ArrayList;
23 import java.util.List;
24
25 import com.puppycrawl.tools.checkstyle.StatelessCheck;
26 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
27 import com.puppycrawl.tools.checkstyle.api.DetailAST;
28 import com.puppycrawl.tools.checkstyle.api.FileContents;
29 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
31 import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191 @StatelessCheck
192 public class EmptyLineSeparatorCheck extends AbstractCheck {
193
194
195
196
197
198 public static final String MSG_SHOULD_BE_SEPARATED = "empty.line.separator";
199
200
201
202
203
204
205 public static final String MSG_MULTIPLE_LINES = "empty.line.separator.multiple.lines";
206
207
208
209
210
211 public static final String MSG_MULTIPLE_LINES_AFTER =
212 "empty.line.separator.multiple.lines.after";
213
214
215
216
217
218 public static final String MSG_MULTIPLE_LINES_INSIDE =
219 "empty.line.separator.multiple.lines.inside";
220
221
222 private boolean allowNoEmptyLineBetweenFields;
223
224
225 private boolean allowMultipleEmptyLines = true;
226
227
228 private boolean allowMultipleEmptyLinesInsideClassMembers = true;
229
230
231
232
233
234
235 public final void setAllowNoEmptyLineBetweenFields(boolean allow) {
236 allowNoEmptyLineBetweenFields = allow;
237 }
238
239
240
241
242
243 public void setAllowMultipleEmptyLines(boolean allow) {
244 allowMultipleEmptyLines = allow;
245 }
246
247
248
249
250
251 public void setAllowMultipleEmptyLinesInsideClassMembers(boolean allow) {
252 allowMultipleEmptyLinesInsideClassMembers = allow;
253 }
254
255 @Override
256 public boolean isCommentNodesRequired() {
257 return true;
258 }
259
260 @Override
261 public int[] getDefaultTokens() {
262 return getAcceptableTokens();
263 }
264
265 @Override
266 public int[] getAcceptableTokens() {
267 return new int[] {
268 TokenTypes.PACKAGE_DEF,
269 TokenTypes.IMPORT,
270 TokenTypes.STATIC_IMPORT,
271 TokenTypes.CLASS_DEF,
272 TokenTypes.INTERFACE_DEF,
273 TokenTypes.ENUM_DEF,
274 TokenTypes.STATIC_INIT,
275 TokenTypes.INSTANCE_INIT,
276 TokenTypes.METHOD_DEF,
277 TokenTypes.CTOR_DEF,
278 TokenTypes.VARIABLE_DEF,
279 };
280 }
281
282 @Override
283 public int[] getRequiredTokens() {
284 return CommonUtil.EMPTY_INT_ARRAY;
285 }
286
287 @Override
288 public void visitToken(DetailAST ast) {
289 if (hasMultipleLinesBefore(ast)) {
290 log(ast.getLineNo(), MSG_MULTIPLE_LINES, ast.getText());
291 }
292 if (!allowMultipleEmptyLinesInsideClassMembers) {
293 processMultipleLinesInside(ast);
294 }
295
296 DetailAST nextToken = ast.getNextSibling();
297 while (nextToken != null && isComment(nextToken)) {
298 nextToken = nextToken.getNextSibling();
299 }
300 if (nextToken != null) {
301 final int astType = ast.getType();
302 switch (astType) {
303 case TokenTypes.VARIABLE_DEF:
304 processVariableDef(ast, nextToken);
305 break;
306 case TokenTypes.IMPORT:
307 case TokenTypes.STATIC_IMPORT:
308 processImport(ast, nextToken);
309 break;
310 case TokenTypes.PACKAGE_DEF:
311 processPackage(ast, nextToken);
312 break;
313 default:
314 if (nextToken.getType() == TokenTypes.RCURLY) {
315 if (hasNotAllowedTwoEmptyLinesBefore(nextToken)) {
316 log(ast.getLineNo(), MSG_MULTIPLE_LINES_AFTER, ast.getText());
317 }
318 }
319 else if (!hasEmptyLineAfter(ast)) {
320 log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED,
321 nextToken.getText());
322 }
323 }
324 }
325 }
326
327
328
329
330
331
332 private void processMultipleLinesInside(DetailAST ast) {
333 final int astType = ast.getType();
334 if (isClassMemberBlock(astType)) {
335 final List<Integer> emptyLines = getEmptyLines(ast);
336 final List<Integer> emptyLinesToLog = getEmptyLinesToLog(emptyLines);
337
338 for (Integer lineNo : emptyLinesToLog) {
339
340 log(lineNo + 1, MSG_MULTIPLE_LINES_INSIDE);
341 }
342 }
343 }
344
345
346
347
348
349
350 private static boolean isClassMemberBlock(int astType) {
351 return astType == TokenTypes.STATIC_INIT
352 || astType == TokenTypes.INSTANCE_INIT
353 || astType == TokenTypes.METHOD_DEF
354 || astType == TokenTypes.CTOR_DEF;
355 }
356
357
358
359
360
361
362 private List<Integer> getEmptyLines(DetailAST ast) {
363 final DetailAST lastToken = ast.getLastChild().getLastChild();
364 int lastTokenLineNo = 0;
365 if (lastToken != null) {
366
367
368 lastTokenLineNo = lastToken.getLineNo() - 2;
369 }
370 final List<Integer> emptyLines = new ArrayList<>();
371 final FileContents fileContents = getFileContents();
372
373 for (int lineNo = ast.getLineNo(); lineNo <= lastTokenLineNo; lineNo++) {
374 if (fileContents.lineIsBlank(lineNo)) {
375 emptyLines.add(lineNo);
376 }
377 }
378 return emptyLines;
379 }
380
381
382
383
384
385
386 private static List<Integer> getEmptyLinesToLog(List<Integer> emptyLines) {
387 final List<Integer> emptyLinesToLog = new ArrayList<>();
388 if (emptyLines.size() >= 2) {
389 int previousEmptyLineNo = emptyLines.get(0);
390 for (int emptyLineNo : emptyLines) {
391 if (previousEmptyLineNo + 1 == emptyLineNo) {
392 emptyLinesToLog.add(emptyLineNo);
393 }
394 previousEmptyLineNo = emptyLineNo;
395 }
396 }
397 return emptyLinesToLog;
398 }
399
400
401
402
403
404
405 private boolean hasMultipleLinesBefore(DetailAST ast) {
406 boolean result = false;
407 if ((ast.getType() != TokenTypes.VARIABLE_DEF
408 || isTypeField(ast))
409 && hasNotAllowedTwoEmptyLinesBefore(ast)) {
410 result = true;
411 }
412 return result;
413 }
414
415
416
417
418
419
420 private void processPackage(DetailAST ast, DetailAST nextToken) {
421 if (ast.getLineNo() > 1 && !hasEmptyLineBefore(ast)) {
422 if (getFileContents().getFileName().endsWith("package-info.java")) {
423 if (ast.getFirstChild().getChildCount() == 0 && !isPrecededByJavadoc(ast)) {
424 log(ast.getLineNo(), MSG_SHOULD_BE_SEPARATED, ast.getText());
425 }
426 }
427 else {
428 log(ast.getLineNo(), MSG_SHOULD_BE_SEPARATED, ast.getText());
429 }
430 }
431 if (!hasEmptyLineAfter(ast)) {
432 log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, nextToken.getText());
433 }
434 }
435
436
437
438
439
440
441 private void processImport(DetailAST ast, DetailAST nextToken) {
442 if (nextToken.getType() != TokenTypes.IMPORT
443 && nextToken.getType() != TokenTypes.STATIC_IMPORT && !hasEmptyLineAfter(ast)) {
444 log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED, nextToken.getText());
445 }
446 }
447
448
449
450
451
452
453 private void processVariableDef(DetailAST ast, DetailAST nextToken) {
454 if (isTypeField(ast) && !hasEmptyLineAfter(ast)
455 && isViolatingEmptyLineBetweenFieldsPolicy(nextToken)) {
456 log(nextToken.getLineNo(), MSG_SHOULD_BE_SEPARATED,
457 nextToken.getText());
458 }
459 }
460
461
462
463
464
465
466 private boolean isViolatingEmptyLineBetweenFieldsPolicy(DetailAST detailAST) {
467 return detailAST.getType() != TokenTypes.RCURLY
468 && (!allowNoEmptyLineBetweenFields
469 || detailAST.getType() != TokenTypes.VARIABLE_DEF);
470 }
471
472
473
474
475
476
477 private boolean hasNotAllowedTwoEmptyLinesBefore(DetailAST token) {
478 return !allowMultipleEmptyLines && hasEmptyLineBefore(token)
479 && isPrePreviousLineEmpty(token);
480 }
481
482
483
484
485
486
487 private boolean isPrePreviousLineEmpty(DetailAST token) {
488 boolean result = false;
489 final int lineNo = token.getLineNo();
490
491 final int number = 3;
492 if (lineNo >= number) {
493 final String prePreviousLine = getLines()[lineNo - number];
494 result = CommonUtil.isBlank(prePreviousLine);
495 }
496 return result;
497 }
498
499
500
501
502
503
504 private boolean hasEmptyLineAfter(DetailAST token) {
505 DetailAST lastToken = token.getLastChild().getLastChild();
506 if (lastToken == null) {
507 lastToken = token.getLastChild();
508 }
509 DetailAST nextToken = token.getNextSibling();
510 if (isComment(nextToken)) {
511 nextToken = nextToken.getNextSibling();
512 }
513
514 final int nextBegin = nextToken.getLineNo();
515
516 final int currentEnd = lastToken.getLineNo();
517 return hasEmptyLine(currentEnd + 1, nextBegin - 1);
518 }
519
520
521
522
523
524
525
526
527
528 private boolean hasEmptyLine(int startLine, int endLine) {
529
530 boolean result = false;
531 final FileContents fileContents = getFileContents();
532 for (int line = startLine; line <= endLine; line++) {
533
534 if (fileContents.lineIsBlank(line - 1)) {
535 result = true;
536 break;
537 }
538 }
539 return result;
540 }
541
542
543
544
545
546
547 private boolean hasEmptyLineBefore(DetailAST token) {
548 boolean result = false;
549 final int lineNo = token.getLineNo();
550 if (lineNo != 1) {
551
552 final String lineBefore = getLines()[lineNo - 2];
553 result = CommonUtil.isBlank(lineBefore);
554 }
555 return result;
556 }
557
558
559
560
561
562
563 private static boolean isPrecededByJavadoc(DetailAST token) {
564 boolean result = false;
565 final DetailAST previous = token.getPreviousSibling();
566 if (previous.getType() == TokenTypes.BLOCK_COMMENT_BEGIN
567 && JavadocUtil.isJavadocComment(previous.getFirstChild().getText())) {
568 result = true;
569 }
570 return result;
571 }
572
573
574
575
576
577
578 private static boolean isComment(DetailAST ast) {
579 return ast.getType() == TokenTypes.SINGLE_LINE_COMMENT
580 || ast.getType() == TokenTypes.BLOCK_COMMENT_BEGIN;
581 }
582
583
584
585
586
587
588 private static boolean isTypeField(DetailAST variableDef) {
589 final int parentType = variableDef.getParent().getParent().getType();
590 return parentType == TokenTypes.CLASS_DEF;
591 }
592
593 }