View Javadoc
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 }