1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 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.whitespace;
21
22 import java.util.Locale;
23
24 import com.puppycrawl.tools.checkstyle.StatelessCheck;
25 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
26 import com.puppycrawl.tools.checkstyle.api.DetailAST;
27 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
28 import com.puppycrawl.tools.checkstyle.utils.CodePointUtil;
29 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
30
31 /**
32 * <div>
33 * Checks the padding between the identifier of a method definition,
34 * constructor definition, method call, constructor invocation, record, or record pattern;
35 * and the left parenthesis of the parameter list.
36 * That is, if the identifier and left parenthesis are on the same line,
37 * checks whether a space is required immediately after the identifier or
38 * such a space is forbidden.
39 * If they are not on the same line, reports a violation, unless configured to
40 * allow line breaks. To allow linebreaks after the identifier, set property
41 * {@code allowLineBreaks} to {@code true}.
42 * </div>
43 *
44 * @since 3.4
45 */
46
47 @StatelessCheck
48 public class MethodParamPadCheck
49 extends AbstractCheck {
50
51 /**
52 * A key is pointing to the warning message text in "messages.properties"
53 * file.
54 */
55 public static final String MSG_LINE_PREVIOUS = "line.previous";
56
57 /**
58 * A key is pointing to the warning message text in "messages.properties"
59 * file.
60 */
61 public static final String MSG_WS_PRECEDED = "ws.preceded";
62
63 /**
64 * A key is pointing to the warning message text in "messages.properties"
65 * file.
66 */
67 public static final String MSG_WS_NOT_PRECEDED = "ws.notPreceded";
68
69 /**
70 * Allow a line break between the identifier and left parenthesis.
71 */
72 private boolean allowLineBreaks;
73
74 /** Specify policy on how to pad method parameter. */
75 private PadOption option = PadOption.NOSPACE;
76
77 /**
78 * Creates a new {@code MethodParamPadCheck} instance.
79 */
80 public MethodParamPadCheck() {
81 // no code by default
82 }
83
84 @Override
85 public int[] getDefaultTokens() {
86 return getAcceptableTokens();
87 }
88
89 @Override
90 public int[] getAcceptableTokens() {
91 return new int[] {
92 TokenTypes.CTOR_DEF,
93 TokenTypes.CTOR_CALL,
94 TokenTypes.LITERAL_NEW,
95 TokenTypes.METHOD_CALL,
96 TokenTypes.METHOD_DEF,
97 TokenTypes.SUPER_CTOR_CALL,
98 TokenTypes.ENUM_CONSTANT_DEF,
99 TokenTypes.RECORD_DEF,
100 TokenTypes.RECORD_PATTERN_DEF,
101 };
102 }
103
104 @Override
105 public int[] getRequiredTokens() {
106 return CommonUtil.EMPTY_INT_ARRAY;
107 }
108
109 @Override
110 public void visitToken(DetailAST ast) {
111 final DetailAST parenAST;
112 if (ast.getType() == TokenTypes.METHOD_CALL) {
113 parenAST = ast;
114 }
115 else {
116 parenAST = ast.findFirstToken(TokenTypes.LPAREN);
117 // array construction => parenAST == null
118 }
119
120 if (parenAST != null) {
121 final int[] line = getLineCodePoints(parenAST.getLineNo() - 1);
122 if (CodePointUtil.hasWhitespaceBefore(parenAST.getColumnNo(), line)) {
123 if (!allowLineBreaks) {
124 log(parenAST, MSG_LINE_PREVIOUS, parenAST.getText());
125 }
126 }
127 else {
128 final int before = parenAST.getColumnNo() - 1;
129 if (option == PadOption.NOSPACE
130 && CommonUtil.isCodePointWhitespace(line, before)) {
131 log(parenAST, MSG_WS_PRECEDED, parenAST.getText());
132 }
133 else if (option == PadOption.SPACE
134 && !CommonUtil.isCodePointWhitespace(line, before)) {
135 log(parenAST, MSG_WS_NOT_PRECEDED, parenAST.getText());
136 }
137 }
138 }
139 }
140
141 /**
142 * Setter to allow a line break between the identifier and left parenthesis.
143 *
144 * @param allowLineBreaks whether whitespace should be
145 * flagged at line breaks.
146 * @since 3.4
147 */
148 public void setAllowLineBreaks(boolean allowLineBreaks) {
149 this.allowLineBreaks = allowLineBreaks;
150 }
151
152 /**
153 * Setter to specify policy on how to pad method parameter.
154 *
155 * @param optionStr string to decode option from
156 * @throws IllegalArgumentException if unable to decode
157 * @since 3.4
158 */
159 public void setOption(String optionStr) {
160 option = PadOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
161 }
162
163 }