View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 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   *
44   * @since 3.0
45   */
46  @StatelessCheck
47  public class OperatorWrapCheck
48      extends AbstractCheck {
49  
50      /**
51       * A key is pointing to the warning message text in "messages.properties"
52       * file.
53       */
54      public static final String MSG_LINE_NEW = "line.new";
55  
56      /**
57       * A key is pointing to the warning message text in "messages.properties"
58       * file.
59       */
60      public static final String MSG_LINE_PREVIOUS = "line.previous";
61  
62      /** Specify policy on how to wrap lines. */
63      private WrapOption option = WrapOption.NL;
64  
65      /**
66       * Setter to specify policy on how to wrap lines.
67       *
68       * @param optionStr string to decode option from
69       * @throws IllegalArgumentException if unable to decode
70       * @since 3.0
71       */
72      public void setOption(String optionStr) {
73          option = WrapOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
74      }
75  
76      @Override
77      public int[] getDefaultTokens() {
78          return new int[] {
79              TokenTypes.QUESTION,          // '?'
80              TokenTypes.COLON,             // ':' (not reported for a case)
81              TokenTypes.EQUAL,             // "=="
82              TokenTypes.NOT_EQUAL,         // "!="
83              TokenTypes.DIV,               // '/'
84              TokenTypes.PLUS,              // '+' (unary plus is UNARY_PLUS)
85              TokenTypes.MINUS,             // '-' (unary minus is UNARY_MINUS)
86              TokenTypes.STAR,              // '*'
87              TokenTypes.MOD,               // '%'
88              TokenTypes.SR,                // ">>"
89              TokenTypes.BSR,               // ">>>"
90              TokenTypes.GE,                // ">="
91              TokenTypes.GT,                // ">"
92              TokenTypes.SL,                // "<<"
93              TokenTypes.LE,                // "<="
94              TokenTypes.LT,                // '<'
95              TokenTypes.BXOR,              // '^'
96              TokenTypes.BOR,               // '|'
97              TokenTypes.LOR,               // "||"
98              TokenTypes.BAND,              // '&'
99              TokenTypes.LAND,              // "&&"
100             TokenTypes.TYPE_EXTENSION_AND,
101             TokenTypes.LITERAL_INSTANCEOF,
102         };
103     }
104 
105     @Override
106     public int[] getAcceptableTokens() {
107         return new int[] {
108             TokenTypes.QUESTION,          // '?'
109             TokenTypes.COLON,             // ':' (not reported for a case)
110             TokenTypes.EQUAL,             // "=="
111             TokenTypes.NOT_EQUAL,         // "!="
112             TokenTypes.DIV,               // '/'
113             TokenTypes.PLUS,              // '+' (unary plus is UNARY_PLUS)
114             TokenTypes.MINUS,             // '-' (unary minus is UNARY_MINUS)
115             TokenTypes.STAR,              // '*'
116             TokenTypes.MOD,               // '%'
117             TokenTypes.SR,                // ">>"
118             TokenTypes.BSR,               // ">>>"
119             TokenTypes.GE,                // ">="
120             TokenTypes.GT,                // ">"
121             TokenTypes.SL,                // "<<"
122             TokenTypes.LE,                // "<="
123             TokenTypes.LT,                // '<'
124             TokenTypes.BXOR,              // '^'
125             TokenTypes.BOR,               // '|'
126             TokenTypes.LOR,               // "||"
127             TokenTypes.BAND,              // '&'
128             TokenTypes.LAND,              // "&&"
129             TokenTypes.LITERAL_INSTANCEOF,
130             TokenTypes.TYPE_EXTENSION_AND,
131             TokenTypes.ASSIGN,            // '='
132             TokenTypes.DIV_ASSIGN,        // "/="
133             TokenTypes.PLUS_ASSIGN,       // "+="
134             TokenTypes.MINUS_ASSIGN,      // "-="
135             TokenTypes.STAR_ASSIGN,       // "*="
136             TokenTypes.MOD_ASSIGN,        // "%="
137             TokenTypes.SR_ASSIGN,         // ">>="
138             TokenTypes.BSR_ASSIGN,        // ">>>="
139             TokenTypes.SL_ASSIGN,         // "<<="
140             TokenTypes.BXOR_ASSIGN,       // "^="
141             TokenTypes.BOR_ASSIGN,        // "|="
142             TokenTypes.BAND_ASSIGN,       // "&="
143             TokenTypes.METHOD_REF,        // "::"
144         };
145     }
146 
147     @Override
148     public int[] getRequiredTokens() {
149         return CommonUtil.EMPTY_INT_ARRAY;
150     }
151 
152     @Override
153     public void visitToken(DetailAST ast) {
154         if (isTargetNode(ast)) {
155             if (option == WrapOption.NL && isNewLineModeViolation(ast)) {
156                 log(ast, MSG_LINE_NEW, ast.getText());
157             }
158             else if (option == WrapOption.EOL && isEndOfLineModeViolation(ast)) {
159                 log(ast, MSG_LINE_PREVIOUS, ast.getText());
160             }
161         }
162     }
163 
164     /**
165      * Filters some false tokens that this check should ignore.
166      *
167      * @param node the node to check
168      * @return {@code true} for all nodes this check should validate
169      */
170     private static boolean isTargetNode(DetailAST node) {
171         final boolean result;
172         if (node.getType() == TokenTypes.COLON) {
173             result = !isColonFromLabel(node);
174         }
175         else if (node.getType() == TokenTypes.STAR) {
176             // Unlike the import statement, the multiply operator always has children
177             result = node.hasChildren();
178         }
179         else {
180             result = true;
181         }
182         return result;
183     }
184 
185     /**
186      * Checks whether operator violates {@link WrapOption#NL} mode.
187      *
188      * @param ast the DetailAst of an operator
189      * @return {@code true} if mode does not match
190      */
191     private static boolean isNewLineModeViolation(DetailAST ast) {
192         return TokenUtil.areOnSameLine(ast, getLeftNode(ast))
193                 && !TokenUtil.areOnSameLine(ast, getRightNode(ast));
194     }
195 
196     /**
197      * Checks whether operator violates {@link WrapOption#EOL} mode.
198      *
199      * @param ast the DetailAst of an operator
200      * @return {@code true} if mode does not match
201      */
202     private static boolean isEndOfLineModeViolation(DetailAST ast) {
203         return !TokenUtil.areOnSameLine(ast, getLeftNode(ast));
204     }
205 
206     /**
207      * Checks if a node is {@link TokenTypes#COLON} from a label, switch case of default.
208      *
209      * @param node the node to check
210      * @return {@code true} if node matches
211      */
212     private static boolean isColonFromLabel(DetailAST node) {
213         return TokenUtil.isOfType(node.getParent(), TokenTypes.LABELED_STAT,
214             TokenTypes.LITERAL_CASE, TokenTypes.LITERAL_DEFAULT);
215     }
216 
217     /**
218      * Checks if a node is {@link TokenTypes#ASSIGN} to a variable or resource.
219      *
220      * @param node the node to check
221      * @return {@code true} if node matches
222      */
223     private static boolean isAssignToVariable(DetailAST node) {
224         return TokenUtil.isOfType(node.getParent(), TokenTypes.VARIABLE_DEF, TokenTypes.RESOURCE);
225     }
226 
227     /**
228      * Returns the left neighbour of a binary operator. This is the rightmost
229      * grandchild of the left child or sibling. For the assign operator the return value is
230      * the variable name.
231      *
232      * @param node the binary operator
233      * @return nearest node from left
234      */
235     private static DetailAST getLeftNode(DetailAST node) {
236         DetailAST result;
237         if (node.getFirstChild() == null || isAssignToVariable(node)) {
238             result = node.getPreviousSibling();
239         }
240         else if (isInPatternDefinition(node)) {
241             result = node.getFirstChild();
242         }
243         else {
244             result = adjustParens(node.getFirstChild(), DetailAST::getNextSibling);
245         }
246         while (result.getLastChild() != null) {
247             result = result.getLastChild();
248         }
249         return result;
250     }
251 
252     /**
253      * Ascends AST to determine if given node is part of a pattern
254      * definition.
255      *
256      * @param node the node to check
257      * @return true if node is in pattern definition
258      */
259     private static boolean isInPatternDefinition(DetailAST node) {
260         DetailAST parent = node;
261         final int[] tokensToStopOn = {
262             // token we are looking for
263             TokenTypes.PATTERN_DEF,
264             // tokens that mean we can stop looking
265             TokenTypes.EXPR,
266             TokenTypes.RESOURCE,
267             TokenTypes.COMPILATION_UNIT,
268         };
269 
270         do {
271             parent = parent.getParent();
272         } while (!TokenUtil.isOfType(parent, tokensToStopOn));
273         return parent.getType() == TokenTypes.PATTERN_DEF;
274     }
275 
276     /**
277      * Returns the right neighbour of a binary operator. This is the leftmost
278      * grandchild of the right child or sibling. For the ternary operator this
279      * is the node between {@code ?} and {@code :} .
280      *
281      * @param node the binary operator
282      * @return nearest node from right
283      */
284     private static DetailAST getRightNode(DetailAST node) {
285         DetailAST result;
286         if (node.getLastChild() == null) {
287             result = node.getNextSibling();
288         }
289         else {
290             final DetailAST rightNode;
291             if (node.getType() == TokenTypes.QUESTION) {
292                 rightNode = node.findFirstToken(TokenTypes.COLON).getPreviousSibling();
293             }
294             else {
295                 rightNode = node.getLastChild();
296             }
297             result = adjustParens(rightNode, DetailAST::getPreviousSibling);
298         }
299 
300         if (!TokenUtil.isOfType(result, TokenTypes.ARRAY_INIT, TokenTypes.ANNOTATION_ARRAY_INIT)) {
301             while (result.getFirstChild() != null) {
302                 result = result.getFirstChild();
303             }
304         }
305         return result;
306     }
307 
308     /**
309      * Finds matching parentheses among siblings. If the given node is not
310      * {@link TokenTypes#LPAREN} nor {@link TokenTypes#RPAREN}, the method adjusts nothing.
311      * This method is for handling case like {@code
312      *   (condition && (condition
313      *     || condition2 || condition3) && condition4
314      *     && condition3)
315      * }
316      *
317      * @param node the node to adjust
318      * @param step the node transformer, should be {@link DetailAST#getPreviousSibling}
319      *             or {@link DetailAST#getNextSibling}
320      * @return adjusted node
321      */
322     private static DetailAST adjustParens(DetailAST node, UnaryOperator<DetailAST> step) {
323         DetailAST result = node;
324         int accumulator = 0;
325         while (true) {
326             if (result.getType() == TokenTypes.LPAREN) {
327                 accumulator--;
328             }
329             else if (result.getType() == TokenTypes.RPAREN) {
330                 accumulator++;
331             }
332             if (accumulator == 0) {
333                 break;
334             }
335             result = step.apply(result);
336         }
337         return result;
338     }
339 
340 }