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.whitespace;
021
022import java.util.Locale;
023
024import com.puppycrawl.tools.checkstyle.StatelessCheck;
025import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
026import com.puppycrawl.tools.checkstyle.api.DetailAST;
027import com.puppycrawl.tools.checkstyle.api.TokenTypes;
028import com.puppycrawl.tools.checkstyle.utils.CodePointUtil;
029import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
030
031/**
032 * <p>
033 * Checks the padding between the identifier of a method definition,
034 * constructor definition, method call, or constructor invocation;
035 * and the left parenthesis of the parameter list.
036 * That is, if the identifier and left parenthesis are on the same line,
037 * checks whether a space is required immediately after the identifier or
038 * such a space is forbidden.
039 * If they are not on the same line, reports a violation, unless configured to
040 * allow line breaks. To allow linebreaks after the identifier, set property
041 * {@code allowLineBreaks} to {@code true}.
042 * </p>
043 * <ul>
044 * <li>
045 * Property {@code allowLineBreaks} - Allow a line break between the identifier
046 * and left parenthesis.
047 * Type is {@code boolean}.
048 * Default value is {@code false}.
049 * </li>
050 * <li>
051 * Property {@code option} - Specify policy on how to pad method parameter.
052 * Type is {@code com.puppycrawl.tools.checkstyle.checks.whitespace.PadOption}.
053 * Default value is {@code nospace}.
054 * </li>
055 * <li>
056 * Property {@code tokens} - tokens to check
057 * Type is {@code java.lang.String[]}.
058 * Validation type is {@code tokenSet}.
059 * Default value is:
060 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF">
061 * CTOR_DEF</a>,
062 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_NEW">
063 * LITERAL_NEW</a>,
064 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_CALL">
065 * METHOD_CALL</a>,
066 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
067 * METHOD_DEF</a>,
068 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SUPER_CTOR_CALL">
069 * SUPER_CTOR_CALL</a>,
070 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF">
071 * ENUM_CONSTANT_DEF</a>,
072 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF">
073 * RECORD_DEF</a>.
074 * </li>
075 * </ul>
076 * <p>
077 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
078 * </p>
079 * <p>
080 * Violation Message Keys:
081 * </p>
082 * <ul>
083 * <li>
084 * {@code line.previous}
085 * </li>
086 * <li>
087 * {@code ws.notPreceded}
088 * </li>
089 * <li>
090 * {@code ws.preceded}
091 * </li>
092 * </ul>
093 *
094 * @since 3.4
095 */
096
097@StatelessCheck
098public class MethodParamPadCheck
099    extends AbstractCheck {
100
101    /**
102     * A key is pointing to the warning message text in "messages.properties"
103     * file.
104     */
105    public static final String MSG_LINE_PREVIOUS = "line.previous";
106
107    /**
108     * A key is pointing to the warning message text in "messages.properties"
109     * file.
110     */
111    public static final String MSG_WS_PRECEDED = "ws.preceded";
112
113    /**
114     * A key is pointing to the warning message text in "messages.properties"
115     * file.
116     */
117    public static final String MSG_WS_NOT_PRECEDED = "ws.notPreceded";
118
119    /**
120     * Allow a line break between the identifier and left parenthesis.
121     */
122    private boolean allowLineBreaks;
123
124    /** Specify policy on how to pad method parameter. */
125    private PadOption option = PadOption.NOSPACE;
126
127    @Override
128    public int[] getDefaultTokens() {
129        return getAcceptableTokens();
130    }
131
132    @Override
133    public int[] getAcceptableTokens() {
134        return new int[] {
135            TokenTypes.CTOR_DEF,
136            TokenTypes.LITERAL_NEW,
137            TokenTypes.METHOD_CALL,
138            TokenTypes.METHOD_DEF,
139            TokenTypes.SUPER_CTOR_CALL,
140            TokenTypes.ENUM_CONSTANT_DEF,
141            TokenTypes.RECORD_DEF,
142        };
143    }
144
145    @Override
146    public int[] getRequiredTokens() {
147        return CommonUtil.EMPTY_INT_ARRAY;
148    }
149
150    @Override
151    public void visitToken(DetailAST ast) {
152        final DetailAST parenAST;
153        if (ast.getType() == TokenTypes.METHOD_CALL) {
154            parenAST = ast;
155        }
156        else {
157            parenAST = ast.findFirstToken(TokenTypes.LPAREN);
158            // array construction => parenAST == null
159        }
160
161        if (parenAST != null) {
162            final int[] line = getLineCodePoints(parenAST.getLineNo() - 1);
163            if (CodePointUtil.hasWhitespaceBefore(parenAST.getColumnNo(), line)) {
164                if (!allowLineBreaks) {
165                    log(parenAST, MSG_LINE_PREVIOUS, parenAST.getText());
166                }
167            }
168            else {
169                final int before = parenAST.getColumnNo() - 1;
170                if (option == PadOption.NOSPACE
171                    && CommonUtil.isCodePointWhitespace(line, before)) {
172                    log(parenAST, MSG_WS_PRECEDED, parenAST.getText());
173                }
174                else if (option == PadOption.SPACE
175                         && !CommonUtil.isCodePointWhitespace(line, before)) {
176                    log(parenAST, MSG_WS_NOT_PRECEDED, parenAST.getText());
177                }
178            }
179        }
180    }
181
182    /**
183     * Setter to allow a line break between the identifier and left parenthesis.
184     *
185     * @param allowLineBreaks whether whitespace should be
186     *     flagged at line breaks.
187     * @since 3.4
188     */
189    public void setAllowLineBreaks(boolean allowLineBreaks) {
190        this.allowLineBreaks = allowLineBreaks;
191    }
192
193    /**
194     * Setter to specify policy on how to pad method parameter.
195     *
196     * @param optionStr string to decode option from
197     * @throws IllegalArgumentException if unable to decode
198     * @since 3.4
199     */
200    public void setOption(String optionStr) {
201        option = PadOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
202    }
203
204}