1 //////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code for adherence to a set of rules. 3 // Copyright (C) 2001-2019 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.indentation; 21 22 import java.util.ArrayDeque; 23 import java.util.Deque; 24 import java.util.HashSet; 25 import java.util.Set; 26 27 import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 28 import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 29 import com.puppycrawl.tools.checkstyle.api.DetailAST; 30 31 /** 32 * Checks correct indentation of Java Code. 33 * 34 * <p> 35 * The basic idea behind this is that while 36 * pretty printers are sometimes convenient for reformatting of 37 * legacy code, they often either aren't configurable enough or 38 * just can't anticipate how format should be done. Sometimes this is 39 * personal preference, other times it is practical experience. In any 40 * case, this check should just ensure that a minimal set of indentation 41 * rules are followed. 42 * </p> 43 * 44 * <p> 45 * Implementation -- 46 * Basically, this check requests visitation for all handled token 47 * types (those tokens registered in the HandlerFactory). When visitToken 48 * is called, a new ExpressionHandler is created for the AST and pushed 49 * onto the handlers stack. The new handler then checks the indentation 50 * for the currently visiting AST. When leaveToken is called, the 51 * ExpressionHandler is popped from the stack. 52 * </p> 53 * 54 * <p> 55 * While on the stack the ExpressionHandler can be queried for the 56 * indentation level it suggests for children as well as for other 57 * values. 58 * </p> 59 * 60 * <p> 61 * While an ExpressionHandler checks the indentation level of its own 62 * AST, it typically also checks surrounding ASTs. For instance, a 63 * while loop handler checks the while loop as well as the braces 64 * and immediate children. 65 * </p> 66 * <pre> 67 * - handler class -to-> ID mapping kept in Map 68 * - parent passed in during construction 69 * - suggest child indent level 70 * - allows for some tokens to be on same line (ie inner classes OBJBLOCK) 71 * and not increase indentation level 72 * - looked at using double dispatch for getSuggestedChildIndent(), but it 73 * doesn't seem worthwhile, at least now 74 * - both tabs and spaces are considered whitespace in front of the line... 75 * tabs are converted to spaces 76 * - block parents with parens -- for, while, if, etc... -- are checked that 77 * they match the level of the parent 78 * </pre> 79 * 80 * @noinspection ThisEscapedInObjectConstruction 81 */ 82 @FileStatefulCheck 83 public class IndentationCheck extends AbstractCheck { 84 85 /** 86 * A key is pointing to the warning message text in "messages.properties" 87 * file. 88 */ 89 public static final String MSG_ERROR = "indentation.error"; 90 91 /** 92 * A key is pointing to the warning message text in "messages.properties" 93 * file. 94 */ 95 public static final String MSG_ERROR_MULTI = "indentation.error.multi"; 96 97 /** 98 * A key is pointing to the warning message text in "messages.properties" 99 * file. 100 */ 101 public static final String MSG_CHILD_ERROR = "indentation.child.error"; 102 103 /** 104 * A key is pointing to the warning message text in "messages.properties" 105 * file. 106 */ 107 public static final String MSG_CHILD_ERROR_MULTI = "indentation.child.error.multi"; 108 109 /** Default indentation amount - based on Sun. */ 110 private static final int DEFAULT_INDENTATION = 4; 111 112 /** Handlers currently in use. */ 113 private final Deque<AbstractExpressionHandler> handlers = new ArrayDeque<>(); 114 115 /** Instance of line wrapping handler to use. */ 116 private final LineWrappingHandler lineWrappingHandler = new LineWrappingHandler(this); 117 118 /** Factory from which handlers are distributed. */ 119 private final HandlerFactory handlerFactory = new HandlerFactory(); 120 121 /** Lines logged as having incorrect indentation. */ 122 private Set<Integer> incorrectIndentationLines; 123 124 /** How many tabs or spaces to use. */ 125 private int basicOffset = DEFAULT_INDENTATION; 126 127 /** How much to indent a case label. */ 128 private int caseIndent = DEFAULT_INDENTATION; 129 130 /** How far brace should be indented when on next line. */ 131 private int braceAdjustment; 132 133 /** How far throws should be indented when on next line. */ 134 private int throwsIndent = DEFAULT_INDENTATION; 135 136 /** How much to indent an array initialization when on next line. */ 137 private int arrayInitIndent = DEFAULT_INDENTATION; 138 139 /** How far continuation line should be indented when line-wrapping is present. */ 140 private int lineWrappingIndentation = DEFAULT_INDENTATION; 141 142 /** 143 * Force strict condition in line wrapping case. If value is true, line wrap indent 144 * have to be same as lineWrappingIndentation parameter, if value is false, line wrap indent 145 * have to be not less than lineWrappingIndentation parameter. 146 */ 147 private boolean forceStrictCondition; 148 149 /** 150 * Get forcing strict condition. 151 * @return forceStrictCondition value. 152 */ 153 public boolean isForceStrictCondition() { 154 return forceStrictCondition; 155 } 156 157 /** 158 * Set forcing strict condition. 159 * @param value user's value of forceStrictCondition. 160 */ 161 public void setForceStrictCondition(boolean value) { 162 forceStrictCondition = value; 163 } 164 165 /** 166 * Set the basic offset. 167 * 168 * @param basicOffset the number of tabs or spaces to indent 169 */ 170 public void setBasicOffset(int basicOffset) { 171 this.basicOffset = basicOffset; 172 } 173 174 /** 175 * Get the basic offset. 176 * 177 * @return the number of tabs or spaces to indent 178 */ 179 public int getBasicOffset() { 180 return basicOffset; 181 } 182 183 /** 184 * Adjusts brace indentation (positive offset). 185 * 186 * @param adjustmentAmount the brace offset 187 */ 188 public void setBraceAdjustment(int adjustmentAmount) { 189 braceAdjustment = adjustmentAmount; 190 } 191 192 /** 193 * Get the brace adjustment amount. 194 * 195 * @return the positive offset to adjust braces 196 */ 197 public int getBraceAdjustment() { 198 return braceAdjustment; 199 } 200 201 /** 202 * Set the case indentation level. 203 * 204 * @param amount the case indentation level 205 */ 206 public void setCaseIndent(int amount) { 207 caseIndent = amount; 208 } 209 210 /** 211 * Get the case indentation level. 212 * 213 * @return the case indentation level 214 */ 215 public int getCaseIndent() { 216 return caseIndent; 217 } 218 219 /** 220 * Set the throws indentation level. 221 * 222 * @param throwsIndent the throws indentation level 223 */ 224 public void setThrowsIndent(int throwsIndent) { 225 this.throwsIndent = throwsIndent; 226 } 227 228 /** 229 * Get the throws indentation level. 230 * 231 * @return the throws indentation level 232 */ 233 public int getThrowsIndent() { 234 return throwsIndent; 235 } 236 237 /** 238 * Set the array initialisation indentation level. 239 * 240 * @param arrayInitIndent the array initialisation indentation level 241 */ 242 public void setArrayInitIndent(int arrayInitIndent) { 243 this.arrayInitIndent = arrayInitIndent; 244 } 245 246 /** 247 * Get the line-wrapping indentation level. 248 * 249 * @return the initialisation indentation level 250 */ 251 public int getArrayInitIndent() { 252 return arrayInitIndent; 253 } 254 255 /** 256 * Get the array line-wrapping indentation level. 257 * 258 * @return the line-wrapping indentation level 259 */ 260 public int getLineWrappingIndentation() { 261 return lineWrappingIndentation; 262 } 263 264 /** 265 * Set the line-wrapping indentation level. 266 * 267 * @param lineWrappingIndentation the line-wrapping indentation level 268 */ 269 public void setLineWrappingIndentation(int lineWrappingIndentation) { 270 this.lineWrappingIndentation = lineWrappingIndentation; 271 } 272 273 /** 274 * Log an error message. 275 * 276 * @param line the line number where the error was found 277 * @param key the message that describes the error 278 * @param args the details of the message 279 * 280 * @see java.text.MessageFormat 281 */ 282 public void indentationLog(int line, String key, Object... args) { 283 if (!incorrectIndentationLines.contains(line)) { 284 incorrectIndentationLines.add(line); 285 log(line, key, args); 286 } 287 } 288 289 /** 290 * Get the width of a tab. 291 * 292 * @return the width of a tab 293 */ 294 public int getIndentationTabWidth() { 295 return getTabWidth(); 296 } 297 298 @Override 299 public int[] getDefaultTokens() { 300 return getRequiredTokens(); 301 } 302 303 @Override 304 public int[] getAcceptableTokens() { 305 return getRequiredTokens(); 306 } 307 308 @Override 309 public int[] getRequiredTokens() { 310 return handlerFactory.getHandledTypes(); 311 } 312 313 @Override 314 public void beginTree(DetailAST ast) { 315 handlerFactory.clearCreatedHandlers(); 316 handlers.clear(); 317 final PrimordialHandler primordialHandler = new PrimordialHandler(this); 318 handlers.push(primordialHandler); 319 primordialHandler.checkIndentation(); 320 incorrectIndentationLines = new HashSet<>(); 321 } 322 323 @Override 324 public void visitToken(DetailAST ast) { 325 final AbstractExpressionHandler handler = handlerFactory.getHandler(this, ast, 326 handlers.peek()); 327 handlers.push(handler); 328 handler.checkIndentation(); 329 } 330 331 @Override 332 public void leaveToken(DetailAST ast) { 333 handlers.pop(); 334 } 335 336 /** 337 * Accessor for the line wrapping handler. 338 * 339 * @return the line wrapping handler 340 */ 341 public LineWrappingHandler getLineWrappingHandler() { 342 return lineWrappingHandler; 343 } 344 345 /** 346 * Accessor for the handler factory. 347 * 348 * @return the handler factory 349 */ 350 public final HandlerFactory getHandlerFactory() { 351 return handlerFactory; 352 } 353 354 }