//////////////////////////////////////////////////////////////////////////////// // checkstyle: Checks Java source code for adherence to a set of rules. // Copyright (C) 2001-2017 the original author or authors. // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA //////////////////////////////////////////////////////////////////////////////// package com.puppycrawl.tools.checkstyle.checks.indentation; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashSet; import java.util.Set; import com.puppycrawl.tools.checkstyle.api.AbstractCheck; import com.puppycrawl.tools.checkstyle.api.DetailAST; /** * Checks correct indentation of Java Code. * * <p> * The basic idea behind this is that while * pretty printers are sometimes convenient for reformatting of * legacy code, they often either aren't configurable enough or * just can't anticipate how format should be done. Sometimes this is * personal preference, other times it is practical experience. In any * case, this check should just ensure that a minimal set of indentation * rules are followed. * </p> * * <p> * Implementation -- * Basically, this check requests visitation for all handled token * types (those tokens registered in the HandlerFactory). When visitToken * is called, a new ExpressionHandler is created for the AST and pushed * onto the handlers stack. The new handler then checks the indentation * for the currently visiting AST. When leaveToken is called, the * ExpressionHandler is popped from the stack. * </p> * * <p> * While on the stack the ExpressionHandler can be queried for the * indentation level it suggests for children as well as for other * values. * </p> * * <p> * While an ExpressionHandler checks the indentation level of its own * AST, it typically also checks surrounding ASTs. For instance, a * while loop handler checks the while loop as well as the braces * and immediate children. * </p> * <pre> * - handler class -to-> ID mapping kept in Map * - parent passed in during construction * - suggest child indent level * - allows for some tokens to be on same line (ie inner classes OBJBLOCK) * and not increase indentation level * - looked at using double dispatch for getSuggestedChildIndent(), but it * doesn't seem worthwhile, at least now * - both tabs and spaces are considered whitespace in front of the line... * tabs are converted to spaces * - block parents with parens -- for, while, if, etc... -- are checked that * they match the level of the parent * </pre> * * @author jrichard * @author o_sukhodolsky * @author Maikel Steneker * @author maxvetrenko */ public class IndentationCheck extends AbstractCheck { /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_ERROR = "indentation.error"; /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_ERROR_MULTI = "indentation.error.multi"; /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_CHILD_ERROR = "indentation.child.error"; /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_CHILD_ERROR_MULTI = "indentation.child.error.multi"; /** Default indentation amount - based on Sun. */ private static final int DEFAULT_INDENTATION = 4; /** Handlers currently in use. */ private final Deque<AbstractExpressionHandler> handlers = new ArrayDeque<>(); /** Instance of line wrapping handler to use. */ private final LineWrappingHandler lineWrappingHandler = new LineWrappingHandler(this); /** Factory from which handlers are distributed. */ private final HandlerFactory handlerFactory = new HandlerFactory(); /** Lines logged as having incorrect indentation. */ private Set<Integer> incorrectIndentationLines; /** How many tabs or spaces to use. */ private int basicOffset = DEFAULT_INDENTATION; /** How much to indent a case label. */ private int caseIndent = DEFAULT_INDENTATION; /** How far brace should be indented when on next line. */ private int braceAdjustment; /** How far throws should be indented when on next line. */ private int throwsIndent = DEFAULT_INDENTATION; /** How much to indent an array initialization when on next line. */ private int arrayInitIndent = DEFAULT_INDENTATION; /** How far continuation line should be indented when line-wrapping is present. */ private int lineWrappingIndentation = DEFAULT_INDENTATION; /** * Force strict condition in line wrapping case. If value is true, line wrap indent * have to be same as lineWrappingIndentation parameter, if value is false, line wrap indent * have to be not less than lineWrappingIndentation parameter. */ private boolean forceStrictCondition; /** * Get forcing strict condition. * @return forceStrictCondition value. */ public boolean isForceStrictCondition() { return forceStrictCondition; } /** * Set forcing strict condition. * @param value user's value of forceStrictCondition. */ public void setForceStrictCondition(boolean value) { forceStrictCondition = value; } /** * Set the basic offset. * * @param basicOffset the number of tabs or spaces to indent */ public void setBasicOffset(int basicOffset) { this.basicOffset = basicOffset; } /** * Get the basic offset. * * @return the number of tabs or spaces to indent */ public int getBasicOffset() { return basicOffset; } /** * Adjusts brace indentation (positive offset). * * @param adjustmentAmount the brace offset */ public void setBraceAdjustment(int adjustmentAmount) { braceAdjustment = adjustmentAmount; } /** * Get the brace adjustment amount. * * @return the positive offset to adjust braces */ public int getBraceAdjustment() { return braceAdjustment; } /** * Set the case indentation level. * * @param amount the case indentation level */ public void setCaseIndent(int amount) { caseIndent = amount; } /** * Get the case indentation level. * * @return the case indentation level */ public int getCaseIndent() { return caseIndent; } /** * Set the throws indentation level. * * @param throwsIndent the throws indentation level */ public void setThrowsIndent(int throwsIndent) { this.throwsIndent = throwsIndent; } /** * Get the throws indentation level. * * @return the throws indentation level */ public int getThrowsIndent() { return throwsIndent; } /** * Set the array initialisation indentation level. * * @param arrayInitIndent the array initialisation indentation level */ public void setArrayInitIndent(int arrayInitIndent) { this.arrayInitIndent = arrayInitIndent; } /** * Get the line-wrapping indentation level. * * @return the initialisation indentation level */ public int getArrayInitIndent() { return arrayInitIndent; } /** * Get the array line-wrapping indentation level. * * @return the line-wrapping indentation level */ public int getLineWrappingIndentation() { return lineWrappingIndentation; } /** * Set the line-wrapping indentation level. * * @param lineWrappingIndentation the line-wrapping indentation level */ public void setLineWrappingIndentation(int lineWrappingIndentation) { this.lineWrappingIndentation = lineWrappingIndentation; } /** * Log an error message. * * @param line the line number where the error was found * @param key the message that describes the error * @param args the details of the message * * @see java.text.MessageFormat */ public void indentationLog(int line, String key, Object... args) { if (!incorrectIndentationLines.contains(line)) { incorrectIndentationLines.add(line); log(line, key, args); } } /** * Get the width of a tab. * * @return the width of a tab */ public int getIndentationTabWidth() { return getTabWidth(); } @Override public int[] getDefaultTokens() { return getAcceptableTokens(); } @Override public int[] getAcceptableTokens() { return handlerFactory.getHandledTypes(); } @Override public int[] getRequiredTokens() { return getAcceptableTokens(); } @Override public void beginTree(DetailAST ast) { handlerFactory.clearCreatedHandlers(); handlers.clear(); final PrimordialHandler primordialHandler = new PrimordialHandler(this); handlers.push(primordialHandler); primordialHandler.checkIndentation(); incorrectIndentationLines = new HashSet<>(); } @Override public void visitToken(DetailAST ast) { final AbstractExpressionHandler handler = handlerFactory.getHandler(this, ast, handlers.peek()); handlers.push(handler); handler.checkIndentation(); } @Override public void leaveToken(DetailAST ast) { handlers.pop(); } /** * Accessor for the line wrapping handler. * * @return the line wrapping handler */ public LineWrappingHandler getLineWrappingHandler() { return lineWrappingHandler; } /** * Accessor for the handler factory. * * @return the handler factory */ public final HandlerFactory getHandlerFactory() { return handlerFactory; } }