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.indentation;
21
22 import java.util.Collection;
23 import java.util.Iterator;
24 import java.util.NavigableMap;
25 import java.util.TreeMap;
26
27 import com.puppycrawl.tools.checkstyle.api.DetailAST;
28 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
29 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
30 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
31
32
33
34
35
36
37
38 public class LineWrappingHandler {
39
40
41
42
43 public enum LineWrappingOptions {
44
45
46
47
48 IGNORE_FIRST_LINE,
49
50
51
52 NONE
53
54 }
55
56
57
58
59
60
61
62
63
64
65
66 private static final int[] IGNORED_LIST = {
67 TokenTypes.LCURLY,
68 TokenTypes.RCURLY,
69 TokenTypes.LITERAL_NEW,
70 TokenTypes.LITERAL_YIELD,
71 TokenTypes.ARRAY_INIT,
72 TokenTypes.LITERAL_DEFAULT,
73 TokenTypes.LITERAL_CASE,
74 };
75
76
77
78
79
80
81 private final IndentationCheck indentCheck;
82
83
84
85
86
87
88
89 public LineWrappingHandler(IndentationCheck instance) {
90 indentCheck = instance;
91 }
92
93
94
95
96
97
98
99
100 public void checkIndentation(DetailAST firstNode, DetailAST lastNode) {
101 checkIndentation(firstNode, lastNode, indentCheck.getLineWrappingIndentation());
102 }
103
104
105
106
107
108
109
110
111 private void checkIndentation(DetailAST firstNode, DetailAST lastNode, int indentLevel) {
112 checkIndentation(firstNode, lastNode, indentLevel,
113 -1, LineWrappingOptions.IGNORE_FIRST_LINE);
114 }
115
116
117
118
119
120
121
122
123
124
125 public void checkIndentation(DetailAST firstNode, DetailAST lastNode, int indentLevel,
126 int startIndent, LineWrappingOptions ignoreFirstLine) {
127 final NavigableMap<Integer, DetailAST> firstNodesOnLines = collectFirstNodes(firstNode,
128 lastNode);
129
130 final DetailAST firstLineNode = firstNodesOnLines.get(firstNodesOnLines.firstKey());
131 if (firstLineNode.getType() == TokenTypes.AT) {
132 checkForAnnotationIndentation(firstNodesOnLines, indentLevel);
133 }
134
135 if (ignoreFirstLine == LineWrappingOptions.IGNORE_FIRST_LINE) {
136
137 firstNodesOnLines.remove(firstNodesOnLines.firstKey());
138 }
139
140 final int firstNodeIndent;
141 if (startIndent == -1) {
142 firstNodeIndent = getLineStart(firstLineNode);
143 }
144 else {
145 firstNodeIndent = startIndent;
146 }
147 final int currentIndent = firstNodeIndent + indentLevel;
148
149 for (DetailAST node : firstNodesOnLines.values()) {
150 final int currentType = node.getType();
151 if (checkForNullParameterChild(node) || checkForMethodLparenNewLine(node)
152 || !shouldProcessTextBlockLiteral(node)) {
153 continue;
154 }
155 if (currentType == TokenTypes.RPAREN) {
156 logWarningMessage(node, firstNodeIndent);
157 }
158 else if (!TokenUtil.isOfType(currentType, IGNORED_LIST)) {
159 logWarningMessage(node, currentIndent);
160 }
161 }
162 }
163
164
165
166
167
168
169
170 public void checkForAnnotationIndentation(
171 NavigableMap<Integer, DetailAST> firstNodesOnLines, int indentLevel) {
172 final DetailAST firstLineNode = firstNodesOnLines.get(firstNodesOnLines.firstKey());
173 DetailAST node = firstLineNode.getParent();
174 while (node != null) {
175 if (node.getType() == TokenTypes.ANNOTATION) {
176 final DetailAST atNode = node.getFirstChild();
177 final NavigableMap<Integer, DetailAST> annotationLines =
178 firstNodesOnLines.subMap(
179 node.getLineNo(),
180 true,
181 getNextNodeLine(firstNodesOnLines, node),
182 true
183 );
184 checkAnnotationIndentation(atNode, annotationLines, indentLevel);
185 }
186 node = node.getNextSibling();
187 }
188 }
189
190
191
192
193
194
195
196 public static boolean checkForNullParameterChild(DetailAST node) {
197 return node.getFirstChild() == null && node.getType() == TokenTypes.PARAMETERS;
198 }
199
200
201
202
203
204
205
206 public static boolean checkForMethodLparenNewLine(DetailAST node) {
207 final int parentType = node.getParent().getType();
208 return parentType == TokenTypes.METHOD_DEF && node.getType() == TokenTypes.LPAREN;
209 }
210
211
212
213
214
215
216
217
218
219 private static Integer getNextNodeLine(
220 NavigableMap<Integer, DetailAST> firstNodesOnLines, DetailAST node) {
221 Integer nextNodeLine = firstNodesOnLines.higherKey(node.getLastChild().getLineNo());
222 if (nextNodeLine == null) {
223 nextNodeLine = firstNodesOnLines.lastKey();
224 }
225 return nextNodeLine;
226 }
227
228
229
230
231
232
233
234
235
236 private NavigableMap<Integer, DetailAST> collectFirstNodes(DetailAST firstNode,
237 DetailAST lastNode) {
238 final NavigableMap<Integer, DetailAST> result = new TreeMap<>();
239
240 result.put(firstNode.getLineNo(), firstNode);
241 DetailAST curNode = firstNode.getFirstChild();
242
243 while (curNode != lastNode) {
244 if (curNode.getType() == TokenTypes.OBJBLOCK
245 || curNode.getType() == TokenTypes.SLIST) {
246 curNode = curNode.getLastChild();
247 }
248
249 final DetailAST firstTokenOnLine = result.get(curNode.getLineNo());
250
251 if (firstTokenOnLine == null
252 || expandedTabsColumnNo(firstTokenOnLine) >= expandedTabsColumnNo(curNode)) {
253 result.put(curNode.getLineNo(), curNode);
254 }
255 curNode = getNextCurNode(curNode);
256 }
257 return result;
258 }
259
260
261
262
263
264
265
266
267 private boolean shouldProcessTextBlockLiteral(DetailAST node) {
268 return node.getType() != TokenTypes.TEXT_BLOCK_LITERAL_END
269 || expandedTabsColumnNo(node) == getLineStart(node);
270 }
271
272
273
274
275
276
277
278 private static DetailAST getNextCurNode(DetailAST curNode) {
279 DetailAST nodeToVisit = curNode.getFirstChild();
280 DetailAST currentNode = curNode;
281
282 while (nodeToVisit == null) {
283 nodeToVisit = currentNode.getNextSibling();
284 if (nodeToVisit == null) {
285 currentNode = currentNode.getParent();
286 }
287 }
288 return nodeToVisit;
289 }
290
291
292
293
294
295
296
297
298
299 private void checkAnnotationIndentation(DetailAST atNode,
300 NavigableMap<Integer, DetailAST> firstNodesOnLines, int indentLevel) {
301 final int firstNodeIndent = getLineStart(atNode);
302 final int currentIndent = firstNodeIndent + indentLevel;
303 final Collection<DetailAST> values = firstNodesOnLines.values();
304 final DetailAST lastAnnotationNode = atNode.getParent().getLastChild();
305 final int lastAnnotationLine = lastAnnotationNode.getLineNo();
306
307 final Iterator<DetailAST> itr = values.iterator();
308 while (firstNodesOnLines.size() > 1) {
309 final DetailAST node = itr.next();
310
311 final DetailAST parentNode = node.getParent();
312 final boolean isArrayInitPresentInAncestors =
313 isParentContainsTokenType(node, TokenTypes.ANNOTATION_ARRAY_INIT);
314 final boolean isCurrentNodeCloseAnnotationAloneInLine =
315 node.getLineNo() == lastAnnotationLine
316 && isEndOfScope(lastAnnotationNode, node);
317 if (!isArrayInitPresentInAncestors
318 && (isCurrentNodeCloseAnnotationAloneInLine
319 || node.getType() == TokenTypes.AT
320 && (parentNode.getParent().getType() == TokenTypes.MODIFIERS
321 || parentNode.getParent().getType() == TokenTypes.ANNOTATIONS)
322 || TokenUtil.areOnSameLine(node, atNode))) {
323 logWarningMessage(node, firstNodeIndent);
324 }
325 else if (!isArrayInitPresentInAncestors) {
326 logWarningMessage(node, currentIndent);
327 }
328 itr.remove();
329 }
330 }
331
332
333
334
335
336
337
338
339
340
341 private static boolean isEndOfScope(final DetailAST lastAnnotationNode, final DetailAST node) {
342 DetailAST checkNode = node;
343 boolean endOfScope = true;
344 while (endOfScope && !checkNode.equals(lastAnnotationNode)) {
345 switch (checkNode.getType()) {
346 case TokenTypes.RCURLY, TokenTypes.RBRACK -> {
347 while (checkNode.getNextSibling() == null) {
348 checkNode = checkNode.getParent();
349 }
350 checkNode = checkNode.getNextSibling();
351 }
352 default -> endOfScope = false;
353 }
354 }
355
356 return endOfScope;
357 }
358
359
360
361
362
363
364
365
366 private static boolean isParentContainsTokenType(final DetailAST node, int type) {
367 boolean returnValue = false;
368 for (DetailAST ast = node.getParent(); ast != null; ast = ast.getParent()) {
369 if (ast.getType() == type) {
370 returnValue = true;
371 break;
372 }
373 }
374 return returnValue;
375 }
376
377
378
379
380
381
382
383
384
385 private int expandedTabsColumnNo(DetailAST ast) {
386 final String line =
387 indentCheck.getLine(ast.getLineNo() - 1);
388
389 return CommonUtil.lengthExpandedTabs(line, ast.getColumnNo(),
390 indentCheck.getIndentationTabWidth());
391 }
392
393
394
395
396
397
398
399
400 private int getLineStart(DetailAST ast) {
401 final String line = indentCheck.getLine(ast.getLineNo() - 1);
402 return getLineStart(line);
403 }
404
405
406
407
408
409
410
411 private int getLineStart(String line) {
412 int index = 0;
413 while (Character.isWhitespace(line.charAt(index))) {
414 index++;
415 }
416 return CommonUtil.lengthExpandedTabs(line, index, indentCheck.getIndentationTabWidth());
417 }
418
419
420
421
422
423
424
425
426
427 private void logWarningMessage(DetailAST currentNode, int currentIndent) {
428 if (indentCheck.isForceStrictCondition()) {
429 if (expandedTabsColumnNo(currentNode) != currentIndent) {
430 indentCheck.indentationLog(currentNode,
431 IndentationCheck.MSG_ERROR, currentNode.getText(),
432 expandedTabsColumnNo(currentNode), currentIndent);
433 }
434 }
435 else {
436 if (expandedTabsColumnNo(currentNode) < currentIndent) {
437 indentCheck.indentationLog(currentNode,
438 IndentationCheck.MSG_ERROR, currentNode.getText(),
439 expandedTabsColumnNo(currentNode), currentIndent);
440 }
441 }
442 }
443
444 }