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