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 * <module name="ArrayTrailingComma"/> 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 * <module name="ArrayTrailingComma"> 146 * <property name="alwaysDemandTrailingComma" value="true"/> 147 * </module> 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}