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