001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.coding;
021
022import com.puppycrawl.tools.checkstyle.StatelessCheck;
023import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
024import com.puppycrawl.tools.checkstyle.api.DetailAST;
025import com.puppycrawl.tools.checkstyle.api.TokenTypes;
026import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
027import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
028
029/**
030 * <p>
031 * Checks that each variable declaration is in its own statement
032 * and on its own line.
033 * </p>
034 * <p>
035 * Rationale: <a
036 * href="https://checkstyle.org/styleguides/sun-code-conventions-19990420/CodeConventions.doc5.html#a2992">
037 * the Java code conventions chapter 6.1</a> recommends that
038 * declarations should be one per line/statement.
039 * </p>
040 * <p>
041 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
042 * </p>
043 * <p>
044 * Violation Message Keys:
045 * </p>
046 * <ul>
047 * <li>
048 * {@code multiple.variable.declarations}
049 * </li>
050 * <li>
051 * {@code multiple.variable.declarations.comma}
052 * </li>
053 * </ul>
054 *
055 * @since 3.4
056 */
057@StatelessCheck
058public class MultipleVariableDeclarationsCheck extends AbstractCheck {
059
060    /**
061     * A key is pointing to the warning message text in "messages.properties"
062     * file.
063     */
064    public static final String MSG_MULTIPLE = "multiple.variable.declarations";
065
066    /**
067     * A key is pointing to the warning message text in "messages.properties"
068     * file.
069     */
070    public static final String MSG_MULTIPLE_COMMA = "multiple.variable.declarations.comma";
071
072    @Override
073    public int[] getAcceptableTokens() {
074        return getRequiredTokens();
075    }
076
077    @Override
078    public int[] getDefaultTokens() {
079        return getRequiredTokens();
080    }
081
082    @Override
083    public int[] getRequiredTokens() {
084        return new int[] {TokenTypes.VARIABLE_DEF};
085    }
086
087    @Override
088    public void visitToken(DetailAST ast) {
089        DetailAST nextNode = ast.getNextSibling();
090
091        if (nextNode != null) {
092            final boolean isCommaSeparated = nextNode.getType() == TokenTypes.COMMA;
093
094            if (isCommaSeparated
095                || nextNode.getType() == TokenTypes.SEMI) {
096                nextNode = nextNode.getNextSibling();
097            }
098
099            if (nextNode != null
100                    && nextNode.getType() == TokenTypes.VARIABLE_DEF) {
101                final DetailAST firstNode = CheckUtil.getFirstNode(ast);
102                if (isCommaSeparated) {
103                    // Check if the multiple variable declarations are in a
104                    // for loop initializer. If they are, then no warning
105                    // should be displayed. Declaring multiple variables in
106                    // a for loop initializer is a good way to minimize
107                    // variable scope. Refer Feature Request Id - 2895985
108                    // for more details
109                    if (ast.getParent().getType() != TokenTypes.FOR_INIT) {
110                        log(firstNode, MSG_MULTIPLE_COMMA);
111                    }
112                }
113                else {
114                    final DetailAST lastNode = getLastNode(ast);
115                    final DetailAST firstNextNode = CheckUtil.getFirstNode(nextNode);
116
117                    if (TokenUtil.areOnSameLine(firstNextNode, lastNode)) {
118                        log(firstNode, MSG_MULTIPLE);
119                    }
120                }
121            }
122        }
123    }
124
125    /**
126     * Finds sub-node for given node maximum (line, column) pair.
127     *
128     * @param node the root of tree for search.
129     * @return sub-node with maximum (line, column) pair.
130     */
131    private static DetailAST getLastNode(final DetailAST node) {
132        DetailAST currentNode = node;
133        final DetailAST child = node.getLastChild();
134        if (child != null) {
135            currentNode = getLastNode(child);
136        }
137
138        return currentNode;
139    }
140
141}