1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 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 TokenTypes.LAMBDA, // "->"
145 };
146 }
147
148 @Override
149 public int[] getRequiredTokens() {
150 return CommonUtil.EMPTY_INT_ARRAY;
151 }
152
153 @Override
154 public void visitToken(DetailAST ast) {
155 if (isTargetNode(ast)) {
156 if (option == WrapOption.NL && isNewLineModeViolation(ast)) {
157 log(ast, MSG_LINE_NEW, ast.getText());
158 }
159 else if (option == WrapOption.EOL && isEndOfLineModeViolation(ast)) {
160 log(ast, MSG_LINE_PREVIOUS, ast.getText());
161 }
162 }
163 }
164
165 /**
166 * Filters some false tokens that this check should ignore.
167 *
168 * @param node the node to check
169 * @return {@code true} for all nodes this check should validate
170 */
171 private static boolean isTargetNode(DetailAST node) {
172 final boolean result;
173 if (node.getType() == TokenTypes.COLON) {
174 result = !isColonFromLabel(node);
175 }
176 else if (node.getType() == TokenTypes.STAR) {
177 // Unlike the import statement, the multiply operator always has children
178 result = node.hasChildren();
179 }
180 else {
181 result = true;
182 }
183 return result;
184 }
185
186 /**
187 * Checks whether operator violates {@link WrapOption#NL} mode.
188 *
189 * @param ast the DetailAst of an operator
190 * @return {@code true} if mode does not match
191 */
192 private static boolean isNewLineModeViolation(DetailAST ast) {
193 return TokenUtil.areOnSameLine(ast, getLeftNode(ast))
194 && !TokenUtil.areOnSameLine(ast, getRightNode(ast));
195 }
196
197 /**
198 * Checks whether operator violates {@link WrapOption#EOL} mode.
199 *
200 * @param ast the DetailAst of an operator
201 * @return {@code true} if mode does not match
202 */
203 private static boolean isEndOfLineModeViolation(DetailAST ast) {
204 return !TokenUtil.areOnSameLine(ast, getLeftNode(ast));
205 }
206
207 /**
208 * Checks if a node is {@link TokenTypes#COLON} from a label, switch case of default.
209 *
210 * @param node the node to check
211 * @return {@code true} if node matches
212 */
213 private static boolean isColonFromLabel(DetailAST node) {
214 return TokenUtil.isOfType(node.getParent(), TokenTypes.LABELED_STAT,
215 TokenTypes.LITERAL_CASE, TokenTypes.LITERAL_DEFAULT);
216 }
217
218 /**
219 * Checks if a node is {@link TokenTypes#ASSIGN} to a variable or resource.
220 *
221 * @param node the node to check
222 * @return {@code true} if node matches
223 */
224 private static boolean isAssignToVariable(DetailAST node) {
225 return TokenUtil.isOfType(node.getParent(), TokenTypes.VARIABLE_DEF, TokenTypes.RESOURCE);
226 }
227
228 /**
229 * Returns the left neighbour of a binary operator. This is the rightmost
230 * grandchild of the left child or sibling. For the assign operator the return value is
231 * the variable name.
232 *
233 * @param node the binary operator
234 * @return nearest node from left
235 */
236 private static DetailAST getLeftNode(DetailAST node) {
237 DetailAST result;
238 if (node.getFirstChild() == null || isAssignToVariable(node)) {
239 result = node.getPreviousSibling();
240 }
241 else {
242 result = adjustParens(node.getFirstChild(), DetailAST::getNextSibling);
243 }
244 while (result.getLastChild() != null) {
245 result = result.getLastChild();
246 }
247 return result;
248 }
249
250 /**
251 * Returns the right neighbour of a binary operator. This is the leftmost
252 * grandchild of the right child or sibling. For the ternary operator this
253 * is the node between {@code ?} and {@code :} .
254 *
255 * @param node the binary operator
256 * @return nearest node from right
257 */
258 private static DetailAST getRightNode(DetailAST node) {
259 DetailAST result;
260 if (node.getLastChild() == null) {
261 result = node.getNextSibling();
262 }
263 else {
264 final DetailAST rightNode;
265 if (node.getType() == TokenTypes.QUESTION) {
266 rightNode = node.findFirstToken(TokenTypes.COLON).getPreviousSibling();
267 }
268 else {
269 rightNode = node.getLastChild();
270 }
271 result = adjustParens(rightNode, DetailAST::getPreviousSibling);
272 }
273
274 if (!TokenUtil.isOfType(result, TokenTypes.ARRAY_INIT, TokenTypes.ANNOTATION_ARRAY_INIT)) {
275 while (result.getFirstChild() != null) {
276 result = result.getFirstChild();
277 }
278 }
279 return result;
280 }
281
282 /**
283 * Finds matching parentheses among siblings. If the given node is not
284 * {@link TokenTypes#LPAREN} nor {@link TokenTypes#RPAREN}, the method adjusts nothing.
285 * This method is for handling case like {@code
286 * (condition && (condition
287 * || condition2 || condition3) && condition4
288 * && condition3)
289 * }
290 *
291 * @param node the node to adjust
292 * @param step the node transformer, should be {@link DetailAST#getPreviousSibling}
293 * or {@link DetailAST#getNextSibling}
294 * @return adjusted node
295 */
296 private static DetailAST adjustParens(DetailAST node, UnaryOperator<DetailAST> step) {
297 DetailAST result = node;
298 int accumulator = 0;
299 while (true) {
300 if (result.getType() == TokenTypes.LPAREN) {
301 accumulator--;
302 }
303 else if (result.getType() == TokenTypes.RPAREN) {
304 accumulator++;
305 }
306 if (accumulator == 0) {
307 break;
308 }
309 result = step.apply(result);
310 }
311 return result;
312 }
313
314 }