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.coding;
21
22 import com.puppycrawl.tools.checkstyle.StatelessCheck;
23 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
24 import com.puppycrawl.tools.checkstyle.api.DetailAST;
25 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
26 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
27
28 /**
29 * <div>
30 * Checks that expression lambdas are used instead of single-line block lambdas
31 * where possible.
32 * </div>
33 *
34 * <p>
35 * Rationale: According to the OpenJDK Java Style Guidelines (and general
36 * modern Java conventions), expression lambdas are preferred over single-line
37 * block lambdas for readability and conciseness.
38 * </p>
39 *
40 * <p>
41 * A single-line block lambda is a lambda whose body is a block ({@code {...}})
42 * that fits on a single line and contains only one statement that could be
43 * written as an expression lambda.
44 * </p>
45 *
46 * @since 14.1.0
47 */
48 @StatelessCheck
49 public class ExpressionOverBlockLambdaCheck extends AbstractCheck {
50
51 /**
52 * A key is pointing to the warning message text in "messages.properties"
53 * file.
54 */
55 public static final String MSG_KEY = "expression.over.block.lambda";
56
57 /**
58 * Creates a new {@code ExpressionOverBlockLambdaCheck} instance.
59 */
60 public ExpressionOverBlockLambdaCheck() {
61 // no code by default
62 }
63
64 @Override
65 public int[] getDefaultTokens() {
66 return getRequiredTokens();
67 }
68
69 @Override
70 public int[] getAcceptableTokens() {
71 return getRequiredTokens();
72 }
73
74 @Override
75 public int[] getRequiredTokens() {
76 return new int[] {TokenTypes.LAMBDA};
77 }
78
79 @Override
80 public void visitToken(DetailAST ast) {
81 if (!isSwitchRuleLambda(ast)
82 && isSingleLineLambda(ast)) {
83 final DetailAST body = ast.getLastChild();
84 final DetailAST statement =
85 findSingleStatement(body);
86 if (statement != null
87 && isConvertibleToExpressionLambda(
88 statement)) {
89 log(ast, MSG_KEY);
90 }
91 }
92 }
93
94 /**
95 * Checks if the lambda is a switch rule lambda.
96 *
97 * @param lambda the lambda AST node
98 * @return true if the lambda is part of a switch rule
99 */
100 private static boolean isSwitchRuleLambda(DetailAST lambda) {
101 return lambda.getParent().getType() == TokenTypes.SWITCH_RULE;
102 }
103
104 /**
105 * Checks if a lambda is single-line.
106 *
107 * @param lambda the lambda AST node
108 * @return true if the lambda fits on a single line
109 */
110 private static boolean isSingleLineLambda(DetailAST lambda) {
111 final DetailAST lastLambdaToken = getLastLambdaToken(lambda);
112 return TokenUtil.areOnSameLine(lambda, lastLambdaToken);
113 }
114
115 /**
116 * Gets the last token in a lambda.
117 *
118 * @param lambda the lambda AST node
119 * @return the last token in the lambda
120 */
121 private static DetailAST getLastLambdaToken(DetailAST lambda) {
122 DetailAST node = lambda;
123 do {
124 node = node.getLastChild();
125 } while (node.getLastChild() != null);
126 return node;
127 }
128
129 /**
130 * Finds the single statement in a block lambda body, or returns null
131 * if there are zero or multiple statements.
132 *
133 * @param slist the SLIST node (lambda body)
134 * @return the single statement, or null if not exactly one
135 */
136 private static DetailAST findSingleStatement(DetailAST slist) {
137 DetailAST singleStatement = null;
138 int count = 0;
139 for (DetailAST child = slist.getFirstChild(); child != null;
140 child = child.getNextSibling()) {
141 final int type = child.getType();
142 if (type != TokenTypes.RCURLY
143 && type != TokenTypes.SEMI) {
144 singleStatement = child;
145 count++;
146 }
147 }
148 DetailAST result = null;
149 if (count == 1) {
150 result = singleStatement;
151 }
152 return result;
153 }
154
155 /**
156 * Checks if the statement in a block lambda can be rewritten
157 * as an expression lambda.
158 *
159 * @param statement the statement node
160 * @return true if the statement can be converted to an expression lambda
161 */
162 private static boolean isConvertibleToExpressionLambda(DetailAST statement) {
163 boolean convertible = false;
164 if (statement.getType() == TokenTypes.EXPR) {
165 convertible = true;
166 }
167 else if (statement.getType() == TokenTypes.LITERAL_RETURN) {
168 convertible = hasReturnExpression(statement);
169 }
170 return convertible;
171 }
172
173 /**
174 * Checks if a return statement has an expression (not bare return).
175 *
176 * @param literalReturn the LITERAL_RETURN node
177 * @return true if the return statement has an expression
178 */
179 private static boolean hasReturnExpression(DetailAST literalReturn) {
180 return literalReturn.findFirstToken(TokenTypes.EXPR) != null;
181 }
182
183 }