001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 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.indentation; 021 022import java.util.ArrayDeque; 023import java.util.Deque; 024import java.util.HashSet; 025import java.util.Set; 026 027import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 028import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 029import com.puppycrawl.tools.checkstyle.api.DetailAST; 030 031/** 032 * <p> 033 * Checks correct indentation of Java code. 034 * </p> 035 * <p> 036 * The idea behind this is that while 037 * pretty printers are sometimes convenient for bulk reformats of 038 * legacy code, they often either aren't configurable enough or 039 * just can't anticipate how format should be done. Sometimes this is 040 * personal preference, other times it is practical experience. In any 041 * case, this check should just ensure that a minimal set of indentation 042 * rules is followed. 043 * </p> 044 * <ul> 045 * <li> 046 * Property {@code basicOffset} - Specify how far new indentation level should be 047 * indented when on the next line. 048 * Default value is {@code 4}. 049 * </li> 050 * <li> 051 * Property {@code braceAdjustment} - Specify how far a braces should be indented 052 * when on the next line. 053 * Default value is {@code 0}. 054 * </li> 055 * <li> 056 * Property {@code caseIndent} - Specify how far a case label should be indented 057 * when on next line. 058 * Default value is {@code 4}. 059 * </li> 060 * <li> 061 * Property {@code throwsIndent} - Specify how far a throws clause should be 062 * indented when on next line. 063 * Default value is {@code 4}. 064 * </li> 065 * <li> 066 * Property {@code arrayInitIndent} - Specify how far an array initialisation 067 * should be indented when on next line. Default value is {@code 4}. 068 * </li> 069 * <li> 070 * Property {@code lineWrappingIndentation} - Specify how far continuation line 071 * should be indented when line-wrapping is present. 072 * Default value is {@code 4}. 073 * </li> 074 * <li> 075 * Property {@code forceStrictCondition} - Force strict indent level in line 076 * wrapping case. If value is true, line wrap indent have to be same as 077 * lineWrappingIndentation parameter. If value is false, line wrap indent 078 * could be bigger on any value user would like. 079 * Default value is {@code false}. 080 * </li> 081 * </ul> 082 * <p> 083 * To configure the check: 084 * </p> 085 * <pre> 086 * <module name="Indentation"/> 087 * </pre> 088 * <p> 089 * To configure the check to enforce the indentation style recommended by Oracle: 090 * </p> 091 * <pre> 092 * <module name="Indentation"> 093 * <property name="caseIndent" value="0"/> 094 * </module> 095 * </pre> 096 * <p> 097 * To configure the Check to enforce strict condition in line-wrapping validation. 098 * </p> 099 * <pre> 100 * <module name="Indentation"> 101 * <property name="forceStrictCondition" value="true"/> 102 * </module> 103 * </pre> 104 * <p> 105 * Such config doesn't allow next cases: 106 * </p> 107 * <pre> 108 * void foo(String aFooString, 109 * int aFooInt) {} // indent:8 ; expected: 4; warn, because 8 != 4 110 * </pre> 111 * <p> 112 * But if forceStrictCondition = false, this code is valid: 113 * </p> 114 * <pre> 115 * void foo(String aFooString, 116 * int aFooInt) {} // indent:8 ; expected: > 4; ok, because 8 > 4 117 * </pre> 118 * 119 * @noinspection ThisEscapedInObjectConstruction 120 * @since 3.1 121 */ 122@FileStatefulCheck 123public class IndentationCheck extends AbstractCheck { 124 125 /* -- Implementation -- 126 * 127 * Basically, this check requests visitation for all handled token 128 * types (those tokens registered in the HandlerFactory). When visitToken 129 * is called, a new ExpressionHandler is created for the AST and pushed 130 * onto the handlers stack. The new handler then checks the indentation 131 * for the currently visiting AST. When leaveToken is called, the 132 * ExpressionHandler is popped from the stack. 133 * 134 * While on the stack the ExpressionHandler can be queried for the 135 * indentation level it suggests for children as well as for other 136 * values. 137 * 138 * While an ExpressionHandler checks the indentation level of its own 139 * AST, it typically also checks surrounding ASTs. For instance, a 140 * while loop handler checks the while loop as well as the braces 141 * and immediate children. 142 * 143 * - handler class -to-> ID mapping kept in Map 144 * - parent passed in during construction 145 * - suggest child indent level 146 * - allows for some tokens to be on same line (ie inner classes OBJBLOCK) 147 * and not increase indentation level 148 * - looked at using double dispatch for getSuggestedChildIndent(), but it 149 * doesn't seem worthwhile, at least now 150 * - both tabs and spaces are considered whitespace in front of the line... 151 * tabs are converted to spaces 152 * - block parents with parens -- for, while, if, etc... -- are checked that 153 * they match the level of the parent 154 */ 155 156 /** 157 * A key is pointing to the warning message text in "messages.properties" 158 * file. 159 */ 160 public static final String MSG_ERROR = "indentation.error"; 161 162 /** 163 * A key is pointing to the warning message text in "messages.properties" 164 * file. 165 */ 166 public static final String MSG_ERROR_MULTI = "indentation.error.multi"; 167 168 /** 169 * A key is pointing to the warning message text in "messages.properties" 170 * file. 171 */ 172 public static final String MSG_CHILD_ERROR = "indentation.child.error"; 173 174 /** 175 * A key is pointing to the warning message text in "messages.properties" 176 * file. 177 */ 178 public static final String MSG_CHILD_ERROR_MULTI = "indentation.child.error.multi"; 179 180 /** Default indentation amount - based on Sun. */ 181 private static final int DEFAULT_INDENTATION = 4; 182 183 /** Handlers currently in use. */ 184 private final Deque<AbstractExpressionHandler> handlers = new ArrayDeque<>(); 185 186 /** Instance of line wrapping handler to use. */ 187 private final LineWrappingHandler lineWrappingHandler = new LineWrappingHandler(this); 188 189 /** Factory from which handlers are distributed. */ 190 private final HandlerFactory handlerFactory = new HandlerFactory(); 191 192 /** Lines logged as having incorrect indentation. */ 193 private Set<Integer> incorrectIndentationLines; 194 195 /** Specify how far new indentation level should be indented when on the next line. */ 196 private int basicOffset = DEFAULT_INDENTATION; 197 198 /** Specify how far a case label should be indented when on next line. */ 199 private int caseIndent = DEFAULT_INDENTATION; 200 201 /** Specify how far a braces should be indented when on the next line. */ 202 private int braceAdjustment; 203 204 /** Specify how far a throws clause should be indented when on next line. */ 205 private int throwsIndent = DEFAULT_INDENTATION; 206 207 /** Specify how far an array initialisation should be indented when on next line. */ 208 private int arrayInitIndent = DEFAULT_INDENTATION; 209 210 /** Specify how far continuation line should be indented when line-wrapping is present. */ 211 private int lineWrappingIndentation = DEFAULT_INDENTATION; 212 213 /** 214 * Force strict indent level in line wrapping case. If value is true, line wrap indent 215 * have to be same as lineWrappingIndentation parameter. If value is false, line wrap indent 216 * could be bigger on any value user would like. 217 */ 218 private boolean forceStrictCondition; 219 220 /** 221 * Getter to query strict indent level in line wrapping case. If value is true, line wrap indent 222 * have to be same as lineWrappingIndentation parameter. If value is false, line wrap indent 223 * could be bigger on any value user would like. 224 * 225 * @return forceStrictCondition value. 226 */ 227 public boolean isForceStrictCondition() { 228 return forceStrictCondition; 229 } 230 231 /** 232 * Setter to force strict indent level in line wrapping case. If value is true, line wrap indent 233 * have to be same as lineWrappingIndentation parameter. If value is false, line wrap indent 234 * could be bigger on any value user would like. 235 * 236 * @param value user's value of forceStrictCondition. 237 */ 238 public void setForceStrictCondition(boolean value) { 239 forceStrictCondition = value; 240 } 241 242 /** 243 * Setter to specify how far new indentation level should be indented when on the next line. 244 * 245 * @param basicOffset the number of tabs or spaces to indent 246 */ 247 public void setBasicOffset(int basicOffset) { 248 this.basicOffset = basicOffset; 249 } 250 251 /** 252 * Getter to query how far new indentation level should be indented when on the next line. 253 * 254 * @return the number of tabs or spaces to indent 255 */ 256 public int getBasicOffset() { 257 return basicOffset; 258 } 259 260 /** 261 * Setter to specify how far a braces should be indented when on the next line. 262 * 263 * @param adjustmentAmount the brace offset 264 */ 265 public void setBraceAdjustment(int adjustmentAmount) { 266 braceAdjustment = adjustmentAmount; 267 } 268 269 /** 270 * Getter to query how far a braces should be indented when on the next line. 271 * 272 * @return the positive offset to adjust braces 273 */ 274 public int getBraceAdjustment() { 275 return braceAdjustment; 276 } 277 278 /** 279 * Setter to specify how far a case label should be indented when on next line. 280 * 281 * @param amount the case indentation level 282 */ 283 public void setCaseIndent(int amount) { 284 caseIndent = amount; 285 } 286 287 /** 288 * Getter to query how far a case label should be indented when on next line. 289 * 290 * @return the case indentation level 291 */ 292 public int getCaseIndent() { 293 return caseIndent; 294 } 295 296 /** 297 * Setter to specify how far a throws clause should be indented when on next line. 298 * 299 * @param throwsIndent the throws indentation level 300 */ 301 public void setThrowsIndent(int throwsIndent) { 302 this.throwsIndent = throwsIndent; 303 } 304 305 /** 306 * Getter to query how far a throws clause should be indented when on next line. 307 * 308 * @return the throws indentation level 309 */ 310 public int getThrowsIndent() { 311 return throwsIndent; 312 } 313 314 /** 315 * Setter to specify how far an array initialisation should be indented when on next line. 316 * 317 * @param arrayInitIndent the array initialisation indentation level 318 */ 319 public void setArrayInitIndent(int arrayInitIndent) { 320 this.arrayInitIndent = arrayInitIndent; 321 } 322 323 /** 324 * Getter to query how far an array initialisation should be indented when on next line. 325 * 326 * @return the initialisation indentation level 327 */ 328 public int getArrayInitIndent() { 329 return arrayInitIndent; 330 } 331 332 /** 333 * Getter to query how far continuation line should be indented when line-wrapping is present. 334 * 335 * @return the line-wrapping indentation level 336 */ 337 public int getLineWrappingIndentation() { 338 return lineWrappingIndentation; 339 } 340 341 /** 342 * Setter to specify how far continuation line should be indented when line-wrapping is present. 343 * 344 * @param lineWrappingIndentation the line-wrapping indentation level 345 */ 346 public void setLineWrappingIndentation(int lineWrappingIndentation) { 347 this.lineWrappingIndentation = lineWrappingIndentation; 348 } 349 350 /** 351 * Log a violation message. 352 * 353 * @param line the line number where the violation was found 354 * @param key the message that describes the violation 355 * @param args the details of the message 356 * 357 * @see java.text.MessageFormat 358 */ 359 public void indentationLog(int line, String key, Object... args) { 360 if (!incorrectIndentationLines.contains(line)) { 361 incorrectIndentationLines.add(line); 362 log(line, key, args); 363 } 364 } 365 366 /** 367 * Get the width of a tab. 368 * 369 * @return the width of a tab 370 */ 371 public int getIndentationTabWidth() { 372 return getTabWidth(); 373 } 374 375 @Override 376 public int[] getDefaultTokens() { 377 return getRequiredTokens(); 378 } 379 380 @Override 381 public int[] getAcceptableTokens() { 382 return getRequiredTokens(); 383 } 384 385 @Override 386 public int[] getRequiredTokens() { 387 return handlerFactory.getHandledTypes(); 388 } 389 390 @Override 391 public void beginTree(DetailAST ast) { 392 handlerFactory.clearCreatedHandlers(); 393 handlers.clear(); 394 final PrimordialHandler primordialHandler = new PrimordialHandler(this); 395 handlers.push(primordialHandler); 396 primordialHandler.checkIndentation(); 397 incorrectIndentationLines = new HashSet<>(); 398 } 399 400 @Override 401 public void visitToken(DetailAST ast) { 402 final AbstractExpressionHandler handler = handlerFactory.getHandler(this, ast, 403 handlers.peek()); 404 handlers.push(handler); 405 handler.checkIndentation(); 406 } 407 408 @Override 409 public void leaveToken(DetailAST ast) { 410 handlers.pop(); 411 } 412 413 /** 414 * Accessor for the line wrapping handler. 415 * 416 * @return the line wrapping handler 417 */ 418 public LineWrappingHandler getLineWrappingHandler() { 419 return lineWrappingHandler; 420 } 421 422 /** 423 * Accessor for the handler factory. 424 * 425 * @return the handler factory 426 */ 427 public final HandlerFactory getHandlerFactory() { 428 return handlerFactory; 429 } 430 431}