1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2025 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 @Override
78 public int[] getDefaultTokens() {
79 return getAcceptableTokens();
80 }
81
82 @Override
83 public int[] getAcceptableTokens() {
84 return new int[] {
85 TokenTypes.CTOR_DEF,
86 TokenTypes.CTOR_CALL,
87 TokenTypes.LITERAL_NEW,
88 TokenTypes.METHOD_CALL,
89 TokenTypes.METHOD_DEF,
90 TokenTypes.SUPER_CTOR_CALL,
91 TokenTypes.ENUM_CONSTANT_DEF,
92 TokenTypes.RECORD_DEF,
93 TokenTypes.RECORD_PATTERN_DEF,
94 };
95 }
96
97 @Override
98 public int[] getRequiredTokens() {
99 return CommonUtil.EMPTY_INT_ARRAY;
100 }
101
102 @Override
103 public void visitToken(DetailAST ast) {
104 final DetailAST parenAST;
105 if (ast.getType() == TokenTypes.METHOD_CALL) {
106 parenAST = ast;
107 }
108 else {
109 parenAST = ast.findFirstToken(TokenTypes.LPAREN);
110 // array construction => parenAST == null
111 }
112
113 if (parenAST != null) {
114 final int[] line = getLineCodePoints(parenAST.getLineNo() - 1);
115 if (CodePointUtil.hasWhitespaceBefore(parenAST.getColumnNo(), line)) {
116 if (!allowLineBreaks) {
117 log(parenAST, MSG_LINE_PREVIOUS, parenAST.getText());
118 }
119 }
120 else {
121 final int before = parenAST.getColumnNo() - 1;
122 if (option == PadOption.NOSPACE
123 && CommonUtil.isCodePointWhitespace(line, before)) {
124 log(parenAST, MSG_WS_PRECEDED, parenAST.getText());
125 }
126 else if (option == PadOption.SPACE
127 && !CommonUtil.isCodePointWhitespace(line, before)) {
128 log(parenAST, MSG_WS_NOT_PRECEDED, parenAST.getText());
129 }
130 }
131 }
132 }
133
134 /**
135 * Setter to allow a line break between the identifier and left parenthesis.
136 *
137 * @param allowLineBreaks whether whitespace should be
138 * flagged at line breaks.
139 * @since 3.4
140 */
141 public void setAllowLineBreaks(boolean allowLineBreaks) {
142 this.allowLineBreaks = allowLineBreaks;
143 }
144
145 /**
146 * Setter to specify policy on how to pad method parameter.
147 *
148 * @param optionStr string to decode option from
149 * @throws IllegalArgumentException if unable to decode
150 * @since 3.4
151 */
152 public void setOption(String optionStr) {
153 option = PadOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
154 }
155
156 }