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