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 }