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