1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 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.CheckUtil;
27  
28  /**
29   * <p>
30   * Checks that each variable declaration is in its own statement
31   * and on its own line.
32   * </p>
33   * <p>
34   * Rationale: <a
35   * href="https://checkstyle.org/styleguides/sun-code-conventions-19990420/CodeConventions.doc5.html#a2992">
36   * the SUN Code conventions chapter 6.1</a> recommends that
37   * declarations should be one per line.
38   * </p>
39   * <p>
40   * An example of how to configure the check is:
41   * </p>
42   * <pre>
43   * &lt;module name="MultipleVariableDeclarations"/&gt;
44   * </pre>
45   */
46  @StatelessCheck
47  public class MultipleVariableDeclarationsCheck extends AbstractCheck {
48  
49      /**
50       * A key is pointing to the warning message text in "messages.properties"
51       * file.
52       */
53      public static final String MSG_MULTIPLE = "multiple.variable.declarations";
54  
55      /**
56       * A key is pointing to the warning message text in "messages.properties"
57       * file.
58       */
59      public static final String MSG_MULTIPLE_COMMA = "multiple.variable.declarations.comma";
60  
61      @Override
62      public int[] getAcceptableTokens() {
63          return getRequiredTokens();
64      }
65  
66      @Override
67      public int[] getDefaultTokens() {
68          return getRequiredTokens();
69      }
70  
71      @Override
72      public int[] getRequiredTokens() {
73          return new int[] {TokenTypes.VARIABLE_DEF};
74      }
75  
76      @Override
77      public void visitToken(DetailAST ast) {
78          DetailAST nextNode = ast.getNextSibling();
79  
80          if (nextNode != null) {
81              final boolean isCommaSeparated = nextNode.getType() == TokenTypes.COMMA;
82  
83              if (isCommaSeparated
84                  || nextNode.getType() == TokenTypes.SEMI) {
85                  nextNode = nextNode.getNextSibling();
86              }
87  
88              if (nextNode != null
89                      && nextNode.getType() == TokenTypes.VARIABLE_DEF) {
90                  final DetailAST firstNode = CheckUtil.getFirstNode(ast);
91                  if (isCommaSeparated) {
92                      // Check if the multiple variable declarations are in a
93                      // for loop initializer. If they are, then no warning
94                      // should be displayed. Declaring multiple variables in
95                      // a for loop initializer is a good way to minimize
96                      // variable scope. Refer Feature Request Id - 2895985
97                      // for more details
98                      if (ast.getParent().getType() != TokenTypes.FOR_INIT) {
99                          log(firstNode, MSG_MULTIPLE_COMMA);
100                     }
101                 }
102                 else {
103                     final DetailAST lastNode = getLastNode(ast);
104                     final DetailAST firstNextNode = CheckUtil.getFirstNode(nextNode);
105 
106                     if (firstNextNode.getLineNo() == lastNode.getLineNo()) {
107                         log(firstNode, MSG_MULTIPLE);
108                     }
109                 }
110             }
111         }
112     }
113 
114     /**
115      * Finds sub-node for given node maximum (line, column) pair.
116      * @param node the root of tree for search.
117      * @return sub-node with maximum (line, column) pair.
118      */
119     private static DetailAST getLastNode(final DetailAST node) {
120         DetailAST currentNode = node;
121         DetailAST child = node.getFirstChild();
122         while (child != null) {
123             final DetailAST newNode = getLastNode(child);
124             if (newNode.getLineNo() > currentNode.getLineNo()
125                 || newNode.getLineNo() == currentNode.getLineNo()
126                     && newNode.getColumnNo() > currentNode.getColumnNo()) {
127                 currentNode = newNode;
128             }
129             child = child.getNextSibling();
130         }
131 
132         return currentNode;
133     }
134 
135 }