001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2023 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.coding;
021
022import com.puppycrawl.tools.checkstyle.StatelessCheck;
023import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
024import com.puppycrawl.tools.checkstyle.api.DetailAST;
025import com.puppycrawl.tools.checkstyle.api.TokenTypes;
026import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
027
028/**
029 * <p>
030 * Checks that array initialization contains a trailing comma.
031 * </p>
032 * <pre>
033 * int[] a = new int[]
034 * {
035 *   1,
036 *   2,
037 *   3,
038 * };
039 * </pre>
040 * <p>
041 * By default, the check demands a comma at the end if neither left nor right curly braces
042 * are on the same line as the last element of the array.
043 * </p>
044 * <pre>
045 * return new int[] { 0 };
046 * return new int[] { 0
047 *   };
048 * return new int[] {
049 *   0 };
050 * </pre>
051 * <p>
052 * Rationale: Putting this comma in makes it easier to change the
053 * order of the elements or add new elements on the end. Main benefit of a trailing
054 * comma is that when you add new entry to an array, no surrounding lines are changed.
055 * </p>
056 * <pre>
057 * {
058 *   100000000000000000000,
059 *   200000000000000000000, // OK
060 * }
061 *
062 * {
063 *   100000000000000000000,
064 *   200000000000000000000,
065 *   300000000000000000000,  // Just this line added, no other changes
066 * }
067 * </pre>
068 * <p>
069 * If closing brace is on the same line as trailing comma, this benefit is gone
070 * (as the check does not demand a certain location of curly braces the following
071 * two cases will not produce a violation):
072 * </p>
073 * <pre>
074 * {100000000000000000000,
075 *  200000000000000000000,} // Trailing comma not needed, line needs to be modified anyway
076 *
077 * {100000000000000000000,
078 *  200000000000000000000, // Modified line
079 *  300000000000000000000,} // Added line
080 * </pre>
081 * <p>
082 * If opening brace is on the same line as trailing comma there's also (more arguable) problem:
083 * </p>
084 * <pre>
085 * {100000000000000000000, // Line cannot be just duplicated to slightly modify entry
086 * }
087 *
088 * {100000000000000000000,
089 *  100000000000000000001, // More work needed to duplicate
090 * }
091 * </pre>
092 * <ul>
093 * <li>
094 * Property {@code alwaysDemandTrailingComma} - Control whether to always check for a trailing
095 * comma, even when an array is inline.
096 * Type is {@code boolean}.
097 * Default value is {@code false}.
098 * </li>
099 * </ul>
100 * <p>
101 * To configure the check:
102 * </p>
103 * <pre>
104 * &lt;module name=&quot;ArrayTrailingComma&quot;/&gt;
105 * </pre>
106 * <p>
107 * Which results in the following violations:
108 * </p>
109 * <pre>
110 * int[] numbers = {1, 2, 3};        //no violation
111 * boolean[] bools = {
112 * true,
113 * true,
114 * false
115 * };        //violation
116 *
117 * String[][] text = {{},{},};        //no violation
118 *
119 * double[][] decimals = {
120 * {0.5, 2.3, 1.1,},        //no violation
121 * {1.7, 1.9, 0.6},
122 * {0.8, 7.4, 6.5}
123 * };        // violation as previous line misses a comma
124 *
125 * char[] chars = {'a', 'b', 'c'
126 *   };        / /no violation
127 *
128 * String[] letters = {
129 *   "a", "b", "c"};        // no violation
130 *
131 * int[] a1 = new int[]{
132 *   1,
133 *   2
134 *   ,
135 * };        // no violation
136 *
137 * int[] a2 = new int[]{
138 *   1,
139 *   2
140 *   ,};        // no violation
141 * </pre>
142 *
143 * <p>To configure check to always validate trailing comma:</p>
144 * <pre>
145 * &lt;module name="ArrayTrailingComma"&gt;
146 *   &lt;property name="alwaysDemandTrailingComma" value="true"/&gt;
147 * &lt;/module&gt;
148 * </pre>
149 * <p>Example:</p>
150 * <pre>
151 * int[] numbers = {1, 2, 3}; // violation
152 * boolean[] bools = {
153 * true,
154 * true,
155 * false // violation
156 * };
157 *
158 * String[][] text = {{},{},}; // OK
159 *
160 * double[][] decimals = {
161 * {0.5, 2.3, 1.1,}, // OK
162 * {1.7, 1.9, 0.6}, // violation
163 * {0.8, 7.4, 6.5} // violation
164 * }; // violation, previous line misses a comma
165 *
166 * char[] chars = {'a', 'b', 'c'  // violation
167 *   };
168 *
169 * String[] letters = {
170 *   "a", "b", "c"}; // violation
171 *
172 * int[] a1 = new int[]{
173 *   1,
174 *   2
175 *   ,
176 * }; // OK
177 *
178 * int[] a2 = new int[]{
179 *   1,
180 *   2
181 *   ,}; // OK
182 * </pre>
183 * <p>
184 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
185 * </p>
186 * <p>
187 * Violation Message Keys:
188 * </p>
189 * <ul>
190 * <li>
191 * {@code array.trailing.comma}
192 * </li>
193 * </ul>
194 *
195 * @since 3.2
196 */
197@StatelessCheck
198public class ArrayTrailingCommaCheck extends AbstractCheck {
199
200    /**
201     * A key is pointing to the warning message text in "messages.properties"
202     * file.
203     */
204    public static final String MSG_KEY = "array.trailing.comma";
205
206    /**
207     * Control whether to always check for a trailing comma, even when an array is inline.
208     */
209    private boolean alwaysDemandTrailingComma;
210
211    /**
212     * Setter to control whether to always check for a trailing comma, even when an array is inline.
213     *
214     * @param alwaysDemandTrailingComma whether to always check for a trailing comma.
215     */
216    public void setAlwaysDemandTrailingComma(boolean alwaysDemandTrailingComma) {
217        this.alwaysDemandTrailingComma = alwaysDemandTrailingComma;
218    }
219
220    @Override
221    public int[] getDefaultTokens() {
222        return getRequiredTokens();
223    }
224
225    @Override
226    public int[] getAcceptableTokens() {
227        return getRequiredTokens();
228    }
229
230    @Override
231    public int[] getRequiredTokens() {
232        return new int[] {TokenTypes.ARRAY_INIT};
233    }
234
235    @Override
236    public void visitToken(DetailAST arrayInit) {
237        final DetailAST rcurly = arrayInit.findFirstToken(TokenTypes.RCURLY);
238        final DetailAST previousSibling = rcurly.getPreviousSibling();
239
240        if (arrayInit.getChildCount() != 1
241                && (alwaysDemandTrailingComma
242                    || !TokenUtil.areOnSameLine(rcurly, previousSibling)
243                        && !TokenUtil.areOnSameLine(arrayInit, previousSibling))
244                && previousSibling.getType() != TokenTypes.COMMA) {
245            log(previousSibling, MSG_KEY);
246        }
247    }
248
249}