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