View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.whitespace;
21  
22  import java.util.Locale;
23  import java.util.function.UnaryOperator;
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.TokenTypes;
29  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
30  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
31  
32  /**
33   * <p>
34   * Checks the policy on how to wrap lines on operators.
35   * </p>
36   * <ul>
37   * <li>
38   * Property {@code option} - Specify policy on how to wrap lines.
39   * Type is {@code com.puppycrawl.tools.checkstyle.checks.whitespace.WrapOption}.
40   * Default value is {@code nl}.
41   * </li>
42   * <li>
43   * Property {@code tokens} - tokens to check
44   * Type is {@code java.lang.String[]}.
45   * Validation type is {@code tokenSet}.
46   * Default value is:
47   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#QUESTION">
48   * QUESTION</a>,
49   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COLON">
50   * COLON</a>,
51   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EQUAL">
52   * EQUAL</a>,
53   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NOT_EQUAL">
54   * NOT_EQUAL</a>,
55   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DIV">
56   * DIV</a>,
57   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PLUS">
58   * PLUS</a>,
59   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MINUS">
60   * MINUS</a>,
61   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STAR">
62   * STAR</a>,
63   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MOD">
64   * MOD</a>,
65   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SR">
66   * SR</a>,
67   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BSR">
68   * BSR</a>,
69   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#GE">
70   * GE</a>,
71   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#GT">
72   * GT</a>,
73   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SL">
74   * SL</a>,
75   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LE">
76   * LE</a>,
77   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LT">
78   * LT</a>,
79   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BXOR">
80   * BXOR</a>,
81   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BOR">
82   * BOR</a>,
83   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LOR">
84   * LOR</a>,
85   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BAND">
86   * BAND</a>,
87   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAND">
88   * LAND</a>,
89   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#TYPE_EXTENSION_AND">
90   * TYPE_EXTENSION_AND</a>,
91   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_INSTANCEOF">
92   * LITERAL_INSTANCEOF</a>.
93   * </li>
94   * </ul>
95   * <p>
96   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
97   * </p>
98   * <p>
99   * Violation Message Keys:
100  * </p>
101  * <ul>
102  * <li>
103  * {@code line.new}
104  * </li>
105  * <li>
106  * {@code line.previous}
107  * </li>
108  * </ul>
109  *
110  * @since 3.0
111  */
112 @StatelessCheck
113 public class OperatorWrapCheck
114     extends AbstractCheck {
115 
116     /**
117      * A key is pointing to the warning message text in "messages.properties"
118      * file.
119      */
120     public static final String MSG_LINE_NEW = "line.new";
121 
122     /**
123      * A key is pointing to the warning message text in "messages.properties"
124      * file.
125      */
126     public static final String MSG_LINE_PREVIOUS = "line.previous";
127 
128     /** Specify policy on how to wrap lines. */
129     private WrapOption option = WrapOption.NL;
130 
131     /**
132      * Setter to specify policy on how to wrap lines.
133      *
134      * @param optionStr string to decode option from
135      * @throws IllegalArgumentException if unable to decode
136      * @since 3.0
137      */
138     public void setOption(String optionStr) {
139         option = WrapOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
140     }
141 
142     @Override
143     public int[] getDefaultTokens() {
144         return new int[] {
145             TokenTypes.QUESTION,          // '?'
146             TokenTypes.COLON,             // ':' (not reported for a case)
147             TokenTypes.EQUAL,             // "=="
148             TokenTypes.NOT_EQUAL,         // "!="
149             TokenTypes.DIV,               // '/'
150             TokenTypes.PLUS,              // '+' (unary plus is UNARY_PLUS)
151             TokenTypes.MINUS,             // '-' (unary minus is UNARY_MINUS)
152             TokenTypes.STAR,              // '*'
153             TokenTypes.MOD,               // '%'
154             TokenTypes.SR,                // ">>"
155             TokenTypes.BSR,               // ">>>"
156             TokenTypes.GE,                // ">="
157             TokenTypes.GT,                // ">"
158             TokenTypes.SL,                // "<<"
159             TokenTypes.LE,                // "<="
160             TokenTypes.LT,                // '<'
161             TokenTypes.BXOR,              // '^'
162             TokenTypes.BOR,               // '|'
163             TokenTypes.LOR,               // "||"
164             TokenTypes.BAND,              // '&'
165             TokenTypes.LAND,              // "&&"
166             TokenTypes.TYPE_EXTENSION_AND,
167             TokenTypes.LITERAL_INSTANCEOF,
168         };
169     }
170 
171     @Override
172     public int[] getAcceptableTokens() {
173         return new int[] {
174             TokenTypes.QUESTION,          // '?'
175             TokenTypes.COLON,             // ':' (not reported for a case)
176             TokenTypes.EQUAL,             // "=="
177             TokenTypes.NOT_EQUAL,         // "!="
178             TokenTypes.DIV,               // '/'
179             TokenTypes.PLUS,              // '+' (unary plus is UNARY_PLUS)
180             TokenTypes.MINUS,             // '-' (unary minus is UNARY_MINUS)
181             TokenTypes.STAR,              // '*'
182             TokenTypes.MOD,               // '%'
183             TokenTypes.SR,                // ">>"
184             TokenTypes.BSR,               // ">>>"
185             TokenTypes.GE,                // ">="
186             TokenTypes.GT,                // ">"
187             TokenTypes.SL,                // "<<"
188             TokenTypes.LE,                // "<="
189             TokenTypes.LT,                // '<'
190             TokenTypes.BXOR,              // '^'
191             TokenTypes.BOR,               // '|'
192             TokenTypes.LOR,               // "||"
193             TokenTypes.BAND,              // '&'
194             TokenTypes.LAND,              // "&&"
195             TokenTypes.LITERAL_INSTANCEOF,
196             TokenTypes.TYPE_EXTENSION_AND,
197             TokenTypes.ASSIGN,            // '='
198             TokenTypes.DIV_ASSIGN,        // "/="
199             TokenTypes.PLUS_ASSIGN,       // "+="
200             TokenTypes.MINUS_ASSIGN,      // "-="
201             TokenTypes.STAR_ASSIGN,       // "*="
202             TokenTypes.MOD_ASSIGN,        // "%="
203             TokenTypes.SR_ASSIGN,         // ">>="
204             TokenTypes.BSR_ASSIGN,        // ">>>="
205             TokenTypes.SL_ASSIGN,         // "<<="
206             TokenTypes.BXOR_ASSIGN,       // "^="
207             TokenTypes.BOR_ASSIGN,        // "|="
208             TokenTypes.BAND_ASSIGN,       // "&="
209             TokenTypes.METHOD_REF,        // "::"
210         };
211     }
212 
213     @Override
214     public int[] getRequiredTokens() {
215         return CommonUtil.EMPTY_INT_ARRAY;
216     }
217 
218     @Override
219     public void visitToken(DetailAST ast) {
220         if (isTargetNode(ast)) {
221             if (option == WrapOption.NL && isNewLineModeViolation(ast)) {
222                 log(ast, MSG_LINE_NEW, ast.getText());
223             }
224             else if (option == WrapOption.EOL && isEndOfLineModeViolation(ast)) {
225                 log(ast, MSG_LINE_PREVIOUS, ast.getText());
226             }
227         }
228     }
229 
230     /**
231      * Filters some false tokens that this check should ignore.
232      *
233      * @param node the node to check
234      * @return {@code true} for all nodes this check should validate
235      */
236     private static boolean isTargetNode(DetailAST node) {
237         final boolean result;
238         if (node.getType() == TokenTypes.COLON) {
239             result = !isColonFromLabel(node);
240         }
241         else if (node.getType() == TokenTypes.STAR) {
242             // Unlike the import statement, the multiply operator always has children
243             result = node.hasChildren();
244         }
245         else {
246             result = true;
247         }
248         return result;
249     }
250 
251     /**
252      * Checks whether operator violates {@link WrapOption#NL} mode.
253      *
254      * @param ast the DetailAst of an operator
255      * @return {@code true} if mode does not match
256      */
257     private static boolean isNewLineModeViolation(DetailAST ast) {
258         return TokenUtil.areOnSameLine(ast, getLeftNode(ast))
259                 && !TokenUtil.areOnSameLine(ast, getRightNode(ast));
260     }
261 
262     /**
263      * Checks whether operator violates {@link WrapOption#EOL} mode.
264      *
265      * @param ast the DetailAst of an operator
266      * @return {@code true} if mode does not match
267      */
268     private static boolean isEndOfLineModeViolation(DetailAST ast) {
269         return !TokenUtil.areOnSameLine(ast, getLeftNode(ast));
270     }
271 
272     /**
273      * Checks if a node is {@link TokenTypes#COLON} from a label, switch case of default.
274      *
275      * @param node the node to check
276      * @return {@code true} if node matches
277      */
278     private static boolean isColonFromLabel(DetailAST node) {
279         return TokenUtil.isOfType(node.getParent(), TokenTypes.LABELED_STAT,
280             TokenTypes.LITERAL_CASE, TokenTypes.LITERAL_DEFAULT);
281     }
282 
283     /**
284      * Checks if a node is {@link TokenTypes#ASSIGN} to a variable or resource.
285      *
286      * @param node the node to check
287      * @return {@code true} if node matches
288      */
289     private static boolean isAssignToVariable(DetailAST node) {
290         return TokenUtil.isOfType(node.getParent(), TokenTypes.VARIABLE_DEF, TokenTypes.RESOURCE);
291     }
292 
293     /**
294      * Returns the left neighbour of a binary operator. This is the rightmost
295      * grandchild of the left child or sibling. For the assign operator the return value is
296      * the variable name.
297      *
298      * @param node the binary operator
299      * @return nearest node from left
300      */
301     private static DetailAST getLeftNode(DetailAST node) {
302         DetailAST result;
303         if (node.getFirstChild() == null || isAssignToVariable(node)) {
304             result = node.getPreviousSibling();
305         }
306         else if (isInPatternDefinition(node)) {
307             result = node.getFirstChild();
308         }
309         else {
310             result = adjustParens(node.getFirstChild(), DetailAST::getNextSibling);
311         }
312         while (result.getLastChild() != null) {
313             result = result.getLastChild();
314         }
315         return result;
316     }
317 
318     /**
319      * Ascends AST to determine if given node is part of a pattern
320      * definition.
321      *
322      * @param node the node to check
323      * @return true if node is in pattern definition
324      */
325     private static boolean isInPatternDefinition(DetailAST node) {
326         DetailAST parent = node;
327         final int[] tokensToStopOn = {
328             // token we are looking for
329             TokenTypes.PATTERN_DEF,
330             // tokens that mean we can stop looking
331             TokenTypes.EXPR,
332             TokenTypes.RESOURCE,
333             TokenTypes.COMPILATION_UNIT,
334         };
335 
336         do {
337             parent = parent.getParent();
338         } while (!TokenUtil.isOfType(parent, tokensToStopOn));
339         return parent.getType() == TokenTypes.PATTERN_DEF;
340     }
341 
342     /**
343      * Returns the right neighbour of a binary operator. This is the leftmost
344      * grandchild of the right child or sibling. For the ternary operator this
345      * is the node between {@code ?} and {@code :} .
346      *
347      * @param node the binary operator
348      * @return nearest node from right
349      */
350     private static DetailAST getRightNode(DetailAST node) {
351         DetailAST result;
352         if (node.getLastChild() == null) {
353             result = node.getNextSibling();
354         }
355         else {
356             final DetailAST rightNode;
357             if (node.getType() == TokenTypes.QUESTION) {
358                 rightNode = node.findFirstToken(TokenTypes.COLON).getPreviousSibling();
359             }
360             else {
361                 rightNode = node.getLastChild();
362             }
363             result = adjustParens(rightNode, DetailAST::getPreviousSibling);
364         }
365 
366         if (!TokenUtil.isOfType(result, TokenTypes.ARRAY_INIT, TokenTypes.ANNOTATION_ARRAY_INIT)) {
367             while (result.getFirstChild() != null) {
368                 result = result.getFirstChild();
369             }
370         }
371         return result;
372     }
373 
374     /**
375      * Finds matching parentheses among siblings. If the given node is not
376      * {@link TokenTypes#LPAREN} nor {@link TokenTypes#RPAREN}, the method adjusts nothing.
377      * This method is for handling case like {@code
378      *   (condition && (condition
379      *     || condition2 || condition3) && condition4
380      *     && condition3)
381      * }
382      *
383      * @param node the node to adjust
384      * @param step the node transformer, should be {@link DetailAST#getPreviousSibling}
385      *             or {@link DetailAST#getNextSibling}
386      * @return adjusted node
387      */
388     private static DetailAST adjustParens(DetailAST node, UnaryOperator<DetailAST> step) {
389         DetailAST result = node;
390         int accumulator = 0;
391         while (true) {
392             if (result.getType() == TokenTypes.LPAREN) {
393                 accumulator--;
394             }
395             else if (result.getType() == TokenTypes.RPAREN) {
396                 accumulator++;
397             }
398             if (accumulator == 0) {
399                 break;
400             }
401             result = step.apply(result);
402         }
403         return result;
404     }
405 
406 }