View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 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 java.util.ArrayDeque;
23  import java.util.Deque;
24  
25  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
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  
30  /**
31   * Restricts the number of statements per line to one.
32   * <p>
33   *     Rationale: It's very difficult to read multiple statements on one line.
34   * </p>
35   * <p>
36   *     In the Java programming language, statements are the fundamental unit of
37   *     execution. All statements except blocks are terminated by a semicolon.
38   *     Blocks are denoted by open and close curly braces.
39   * </p>
40   * <p>
41   *     OneStatementPerLineCheck checks the following types of statements:
42   *     variable declaration statements, empty statements, assignment statements,
43   *     expression statements, increment statements, object creation statements,
44   *     'for loop' statements, 'break' statements, 'continue' statements,
45   *     'return' statements, import statements.
46   * </p>
47   * <p>
48   *     The following examples will be flagged as a violation:
49   * </p>
50   * <pre>
51   *     //Each line causes violation:
52   *     int var1; int var2;
53   *     var1 = 1; var2 = 2;
54   *     int var1 = 1; int var2 = 2;
55   *     var1++; var2++;
56   *     Object obj1 = new Object(); Object obj2 = new Object();
57   *     import java.io.EOFException; import java.io.BufferedReader;
58   *     ;; //two empty statements on the same line.
59   *
60   *     //Multi-line statements:
61   *     int var1 = 1
62   *     ; var2 = 2; //violation here
63   *     int o = 1, p = 2,
64   *     r = 5; int t; //violation here
65   * </pre>
66   *
67   * @author Alexander Jesse
68   * @author Oliver Burn
69   * @author Andrei Selkin
70   */
71  @FileStatefulCheck
72  public final class OneStatementPerLineCheck extends AbstractCheck {
73  
74      /**
75       * A key is pointing to the warning message text in "messages.properties"
76       * file.
77       */
78      public static final String MSG_KEY = "multiple.statements.line";
79  
80      /**
81       * Counts number of semicolons in nested lambdas.
82       */
83      private final Deque<Integer> countOfSemiInLambda = new ArrayDeque<>();
84  
85      /**
86       * Hold the line-number where the last statement ended.
87       */
88      private int lastStatementEnd = -1;
89  
90      /**
91       * Hold the line-number where the last 'for-loop' statement ended.
92       */
93      private int forStatementEnd = -1;
94  
95      /**
96       * The for-header usually has 3 statements on one line, but THIS IS OK.
97       */
98      private boolean inForHeader;
99  
100     /**
101      * Holds if current token is inside lambda.
102      */
103     private boolean isInLambda;
104 
105     /**
106      * Hold the line-number where the last lambda statement ended.
107      */
108     private int lambdaStatementEnd = -1;
109 
110     @Override
111     public int[] getDefaultTokens() {
112         return getRequiredTokens();
113     }
114 
115     @Override
116     public int[] getAcceptableTokens() {
117         return getRequiredTokens();
118     }
119 
120     @Override
121     public int[] getRequiredTokens() {
122         return new int[] {
123             TokenTypes.SEMI,
124             TokenTypes.FOR_INIT,
125             TokenTypes.FOR_ITERATOR,
126             TokenTypes.LAMBDA,
127         };
128     }
129 
130     @Override
131     public void beginTree(DetailAST rootAST) {
132         inForHeader = false;
133         lastStatementEnd = -1;
134         forStatementEnd = -1;
135         isInLambda = false;
136     }
137 
138     @Override
139     public void visitToken(DetailAST ast) {
140         switch (ast.getType()) {
141             case TokenTypes.SEMI:
142                 checkIfSemicolonIsInDifferentLineThanPrevious(ast);
143                 break;
144             case TokenTypes.FOR_ITERATOR:
145                 forStatementEnd = ast.getLineNo();
146                 break;
147             case TokenTypes.LAMBDA:
148                 isInLambda = true;
149                 countOfSemiInLambda.push(0);
150                 break;
151             default:
152                 inForHeader = true;
153                 break;
154         }
155     }
156 
157     @Override
158     public void leaveToken(DetailAST ast) {
159         switch (ast.getType()) {
160             case TokenTypes.SEMI:
161                 lastStatementEnd = ast.getLineNo();
162                 forStatementEnd = -1;
163                 lambdaStatementEnd = -1;
164                 break;
165             case TokenTypes.FOR_ITERATOR:
166                 inForHeader = false;
167                 break;
168             case TokenTypes.LAMBDA:
169                 countOfSemiInLambda.pop();
170                 if (countOfSemiInLambda.isEmpty()) {
171                     isInLambda = false;
172                 }
173                 lambdaStatementEnd = ast.getLineNo();
174                 break;
175             default:
176                 break;
177         }
178     }
179 
180     /**
181      * Checks if given semicolon is in different line than previous.
182      * @param ast semicolon to check
183      */
184     private void checkIfSemicolonIsInDifferentLineThanPrevious(DetailAST ast) {
185         DetailAST currentStatement = ast;
186         final boolean hasResourcesPrevSibling =
187                 currentStatement.getPreviousSibling() != null
188                         && currentStatement.getPreviousSibling().getType() == TokenTypes.RESOURCES;
189         if (!hasResourcesPrevSibling && isMultilineStatement(currentStatement)) {
190             currentStatement = ast.getPreviousSibling();
191         }
192         if (isInLambda) {
193             int countOfSemiInCurrentLambda = countOfSemiInLambda.pop();
194             countOfSemiInCurrentLambda++;
195             countOfSemiInLambda.push(countOfSemiInCurrentLambda);
196             if (!inForHeader && countOfSemiInCurrentLambda > 1
197                     && isOnTheSameLine(currentStatement,
198                     lastStatementEnd, forStatementEnd,
199                     lambdaStatementEnd)) {
200                 log(ast, MSG_KEY);
201             }
202         }
203         else if (!inForHeader && isOnTheSameLine(currentStatement, lastStatementEnd,
204                 forStatementEnd, lambdaStatementEnd)) {
205             log(ast, MSG_KEY);
206         }
207     }
208 
209     /**
210      * Checks whether two statements are on the same line.
211      * @param ast token for the current statement.
212      * @param lastStatementEnd the line-number where the last statement ended.
213      * @param forStatementEnd the line-number where the last 'for-loop'
214      *                        statement ended.
215      * @param lambdaStatementEnd the line-number where the last lambda
216      *                        statement ended.
217      * @return true if two statements are on the same line.
218      */
219     private static boolean isOnTheSameLine(DetailAST ast, int lastStatementEnd,
220                                            int forStatementEnd, int lambdaStatementEnd) {
221         return lastStatementEnd == ast.getLineNo() && forStatementEnd != ast.getLineNo()
222                 && lambdaStatementEnd != ast.getLineNo();
223     }
224 
225     /**
226      * Checks whether statement is multiline.
227      * @param ast token for the current statement.
228      * @return true if one statement is distributed over two or more lines.
229      */
230     private static boolean isMultilineStatement(DetailAST ast) {
231         final boolean multiline;
232         if (ast.getPreviousSibling() == null) {
233             multiline = false;
234         }
235         else {
236             final DetailAST prevSibling = ast.getPreviousSibling();
237             multiline = prevSibling.getLineNo() != ast.getLineNo()
238                     && ast.getParent() != null;
239         }
240         return multiline;
241     }
242 }