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