001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2022 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 * To configure the check:
078 * </p>
079 * <pre>
080 * &lt;module name="MethodParamPad"/&gt;
081 * </pre>
082 * <pre>
083 * public class Test {
084 *  public Test() { // OK
085 *     super(); // OK
086 *   }
087 *
088 *   public Test (int aParam) { // Violation - '(' is preceded with whitespace
089 *     super (); // Violation - '(' is preceded with whitespace
090 *   }
091 *
092 *   public void method() {} // OK
093 *
094 *   public void methodWithVeryLongName
095 *     () {} // Violation - '(' is preceded with whitespace
096 *
097 * }
098 * </pre>
099 * <p>
100 * To configure the check to require a space
101 * after the identifier of a method definition, except if the left
102 * parenthesis occurs on a new line:
103 * </p>
104 * <pre>
105 * &lt;module name="MethodParamPad"&gt;
106 *   &lt;property name="tokens" value="METHOD_DEF"/&gt;
107 *   &lt;property name="option" value="space"/&gt;
108 *   &lt;property name="allowLineBreaks" value="true"/&gt;
109 * &lt;/module&gt;
110 * </pre>
111 * <pre>
112 * public class Test {
113 *   public Test() { // OK
114 *     super(); // OK
115 *   }
116 *
117 *   public Test (int aParam) { // OK
118 *     super (); // OK
119 *   }
120 *
121 *   public void method() {} // Violation - '(' is NOT preceded with whitespace
122 *
123 *   public void methodWithVeryLongName
124 *     () {} // OK, because allowLineBreaks is true
125 *
126 * }
127 * </pre>
128 * <p>
129 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
130 * </p>
131 * <p>
132 * Violation Message Keys:
133 * </p>
134 * <ul>
135 * <li>
136 * {@code line.previous}
137 * </li>
138 * <li>
139 * {@code ws.notPreceded}
140 * </li>
141 * <li>
142 * {@code ws.preceded}
143 * </li>
144 * </ul>
145 *
146 * @since 3.4
147 */
148
149@StatelessCheck
150public class MethodParamPadCheck
151    extends AbstractCheck {
152
153    /**
154     * A key is pointing to the warning message text in "messages.properties"
155     * file.
156     */
157    public static final String MSG_LINE_PREVIOUS = "line.previous";
158
159    /**
160     * A key is pointing to the warning message text in "messages.properties"
161     * file.
162     */
163    public static final String MSG_WS_PRECEDED = "ws.preceded";
164
165    /**
166     * A key is pointing to the warning message text in "messages.properties"
167     * file.
168     */
169    public static final String MSG_WS_NOT_PRECEDED = "ws.notPreceded";
170
171    /**
172     * Allow a line break between the identifier and left parenthesis.
173     */
174    private boolean allowLineBreaks;
175
176    /** Specify policy on how to pad method parameter. */
177    private PadOption option = PadOption.NOSPACE;
178
179    @Override
180    public int[] getDefaultTokens() {
181        return getAcceptableTokens();
182    }
183
184    @Override
185    public int[] getAcceptableTokens() {
186        return new int[] {
187            TokenTypes.CTOR_DEF,
188            TokenTypes.LITERAL_NEW,
189            TokenTypes.METHOD_CALL,
190            TokenTypes.METHOD_DEF,
191            TokenTypes.SUPER_CTOR_CALL,
192            TokenTypes.ENUM_CONSTANT_DEF,
193            TokenTypes.RECORD_DEF,
194        };
195    }
196
197    @Override
198    public int[] getRequiredTokens() {
199        return CommonUtil.EMPTY_INT_ARRAY;
200    }
201
202    @Override
203    public void visitToken(DetailAST ast) {
204        final DetailAST parenAST;
205        if (ast.getType() == TokenTypes.METHOD_CALL) {
206            parenAST = ast;
207        }
208        else {
209            parenAST = ast.findFirstToken(TokenTypes.LPAREN);
210            // array construction => parenAST == null
211        }
212
213        if (parenAST != null) {
214            final int[] line = getLineCodePoints(parenAST.getLineNo() - 1);
215            if (CodePointUtil.hasWhitespaceBefore(parenAST.getColumnNo(), line)) {
216                if (!allowLineBreaks) {
217                    log(parenAST, MSG_LINE_PREVIOUS, parenAST.getText());
218                }
219            }
220            else {
221                final int before = parenAST.getColumnNo() - 1;
222                if (option == PadOption.NOSPACE
223                    && CommonUtil.isCodePointWhitespace(line, before)) {
224                    log(parenAST, MSG_WS_PRECEDED, parenAST.getText());
225                }
226                else if (option == PadOption.SPACE
227                         && !CommonUtil.isCodePointWhitespace(line, before)) {
228                    log(parenAST, MSG_WS_NOT_PRECEDED, parenAST.getText());
229                }
230            }
231        }
232    }
233
234    /**
235     * Setter to allow a line break between the identifier and left parenthesis.
236     *
237     * @param allowLineBreaks whether whitespace should be
238     *     flagged at line breaks.
239     */
240    public void setAllowLineBreaks(boolean allowLineBreaks) {
241        this.allowLineBreaks = allowLineBreaks;
242    }
243
244    /**
245     * Setter to specify policy on how to pad method parameter.
246     *
247     * @param optionStr string to decode option from
248     * @throws IllegalArgumentException if unable to decode
249     */
250    public void setOption(String optionStr) {
251        option = PadOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
252    }
253
254}