001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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 * Default value is {@code false}. 097 * </li> 098 * </ul> 099 * <p> 100 * To configure the check: 101 * </p> 102 * <pre> 103 * <module name="ArrayTrailingComma"/> 104 * </pre> 105 * <p> 106 * Which results in the following violations: 107 * </p> 108 * <pre> 109 * int[] numbers = {1, 2, 3}; //no violation 110 * boolean[] bools = { 111 * true, 112 * true, 113 * false 114 * }; //violation 115 * 116 * String[][] text = {{},{},}; //no violation 117 * 118 * double[][] decimals = { 119 * {0.5, 2.3, 1.1,}, //no violation 120 * {1.7, 1.9, 0.6}, 121 * {0.8, 7.4, 6.5} 122 * }; // violation as previous line misses a comma 123 * 124 * char[] chars = {'a', 'b', 'c' 125 * }; / /no violation 126 * 127 * String[] letters = { 128 * "a", "b", "c"}; // no violation 129 * 130 * int[] a1 = new int[]{ 131 * 1, 132 * 2 133 * , 134 * }; // no violation 135 * 136 * int[] a2 = new int[]{ 137 * 1, 138 * 2 139 * ,}; // no violation 140 * </pre> 141 * 142 * <p>To configure check to always validate trailing comma:</p> 143 * <pre> 144 * <module name="ArrayTrailingComma"> 145 * <property name="alwaysDemandTrailingComma" value="true"/> 146 * </module> 147 * </pre> 148 * <p>Example:</p> 149 * <pre> 150 * int[] numbers = {1, 2, 3}; // violation 151 * boolean[] bools = { 152 * true, 153 * true, 154 * false // violation 155 * }; 156 * 157 * String[][] text = {{},{},}; // OK 158 * 159 * double[][] decimals = { 160 * {0.5, 2.3, 1.1,}, // OK 161 * {1.7, 1.9, 0.6}, // violation 162 * {0.8, 7.4, 6.5} // violation 163 * }; // violation, previous line misses a comma 164 * 165 * char[] chars = {'a', 'b', 'c' // violation 166 * }; 167 * 168 * String[] letters = { 169 * "a", "b", "c"}; // violation 170 * 171 * int[] a1 = new int[]{ 172 * 1, 173 * 2 174 * , 175 * }; // OK 176 * 177 * int[] a2 = new int[]{ 178 * 1, 179 * 2 180 * ,}; // OK 181 * </pre> 182 * 183 * @since 3.2 184 */ 185@StatelessCheck 186public class ArrayTrailingCommaCheck extends AbstractCheck { 187 188 /** 189 * A key is pointing to the warning message text in "messages.properties" 190 * file. 191 */ 192 public static final String MSG_KEY = "array.trailing.comma"; 193 194 /** 195 * Control whether to always check for a trailing comma, even when an array is inline. 196 */ 197 private boolean alwaysDemandTrailingComma; 198 199 /** 200 * Setter to control whether to always check for a trailing comma, even when an array is inline. 201 * 202 * @param alwaysDemandTrailingComma whether to always check for a trailing comma. 203 */ 204 public void setAlwaysDemandTrailingComma(boolean alwaysDemandTrailingComma) { 205 this.alwaysDemandTrailingComma = alwaysDemandTrailingComma; 206 } 207 208 @Override 209 public int[] getDefaultTokens() { 210 return getRequiredTokens(); 211 } 212 213 @Override 214 public int[] getAcceptableTokens() { 215 return getRequiredTokens(); 216 } 217 218 @Override 219 public int[] getRequiredTokens() { 220 return new int[] {TokenTypes.ARRAY_INIT}; 221 } 222 223 @Override 224 public void visitToken(DetailAST arrayInit) { 225 final DetailAST rcurly = arrayInit.findFirstToken(TokenTypes.RCURLY); 226 final DetailAST previousSibling = rcurly.getPreviousSibling(); 227 228 if (arrayInit.getChildCount() != 1 229 && (alwaysDemandTrailingComma 230 || !TokenUtil.areOnSameLine(rcurly, previousSibling) 231 && !TokenUtil.areOnSameLine(arrayInit, previousSibling)) 232 && previousSibling.getType() != TokenTypes.COMMA) { 233 log(previousSibling, MSG_KEY); 234 } 235 } 236 237}