//////////////////////////////////////////////////////////////////////////////// // 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.whitespace; import com.puppycrawl.tools.checkstyle.api.AbstractCheck; import com.puppycrawl.tools.checkstyle.api.DetailAST; import com.puppycrawl.tools.checkstyle.api.TokenTypes; import com.puppycrawl.tools.checkstyle.utils.CommonUtils; /** * Checks that a token is surrounded by whitespace. * * <p>By default the check will check the following operators: * {@link TokenTypes#LITERAL_ASSERT ASSERT}, * {@link TokenTypes#ASSIGN ASSIGN}, * {@link TokenTypes#BAND BAND}, * {@link TokenTypes#BAND_ASSIGN BAND_ASSIGN}, * {@link TokenTypes#BOR BOR}, * {@link TokenTypes#BOR_ASSIGN BOR_ASSIGN}, * {@link TokenTypes#BSR BSR}, * {@link TokenTypes#BSR_ASSIGN BSR_ASSIGN}, * {@link TokenTypes#BXOR BXOR}, * {@link TokenTypes#BXOR_ASSIGN BXOR_ASSIGN}, * {@link TokenTypes#COLON COLON}, * {@link TokenTypes#DIV DIV}, * {@link TokenTypes#DIV_ASSIGN DIV_ASSIGN}, * {@link TokenTypes#DO_WHILE DO_WHILE}, * {@link TokenTypes#EQUAL EQUAL}, * {@link TokenTypes#GE GE}, * {@link TokenTypes#GT GT}, * {@link TokenTypes#LAND LAND}, * {@link TokenTypes#LCURLY LCURLY}, * {@link TokenTypes#LE LE}, * {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH}, * {@link TokenTypes#LITERAL_DO LITERAL_DO}, * {@link TokenTypes#LITERAL_ELSE LITERAL_ELSE}, * {@link TokenTypes#LITERAL_FINALLY LITERAL_FINALLY}, * {@link TokenTypes#LITERAL_FOR LITERAL_FOR}, * {@link TokenTypes#LITERAL_IF LITERAL_IF}, * {@link TokenTypes#LITERAL_RETURN LITERAL_RETURN}, * {@link TokenTypes#LITERAL_SWITCH LITERAL_SWITCH}, * {@link TokenTypes#LITERAL_SYNCHRONIZED LITERAL_SYNCHRONIZED}, * {@link TokenTypes#LITERAL_TRY LITERAL_TRY}, * {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE}, * {@link TokenTypes#LOR LOR}, * {@link TokenTypes#LT LT}, * {@link TokenTypes#MINUS MINUS}, * {@link TokenTypes#MINUS_ASSIGN MINUS_ASSIGN}, * {@link TokenTypes#MOD MOD}, * {@link TokenTypes#MOD_ASSIGN MOD_ASSIGN}, * {@link TokenTypes#NOT_EQUAL NOT_EQUAL}, * {@link TokenTypes#PLUS PLUS}, * {@link TokenTypes#PLUS_ASSIGN PLUS_ASSIGN}, * {@link TokenTypes#QUESTION QUESTION}, * {@link TokenTypes#RCURLY RCURLY}, * {@link TokenTypes#SL SL}, * {@link TokenTypes#SLIST SLIST}, * {@link TokenTypes#SL_ASSIGN SL_ASSIGN}, * {@link TokenTypes#SR SR}, * {@link TokenTypes#SR_ASSIGN SR_ASSIGN}, * {@link TokenTypes#STAR STAR}, * {@link TokenTypes#STAR_ASSIGN STAR_ASSIGN}, * {@link TokenTypes#LITERAL_ASSERT LITERAL_ASSERT}, * {@link TokenTypes#TYPE_EXTENSION_AND TYPE_EXTENSION_AND}. * * <p>An example of how to configure the check is: * * <pre> * <module name="WhitespaceAround"/> * </pre> * * <p>An example of how to configure the check for whitespace only around * assignment operators is: * * <pre> * <module name="WhitespaceAround"> * <property name="tokens" * value="ASSIGN,DIV_ASSIGN,PLUS_ASSIGN,MINUS_ASSIGN,STAR_ASSIGN, * MOD_ASSIGN,SR_ASSIGN,BSR_ASSIGN,SL_ASSIGN,BXOR_ASSIGN, * BOR_ASSIGN,BAND_ASSIGN"/> * </module> * </pre> * * <p>An example of how to configure the check for whitespace only around * curly braces is: * <pre> * <module name="WhitespaceAround"> * <property name="tokens" * value="LCURLY,RCURLY"/> * </module> * </pre> * * <p>In addition, this check can be configured to allow empty methods, types, * for, while, do-while loops, lambdas and constructor bodies. * For example: * * <pre>{@code * public MyClass() {} // empty constructor * public void func() {} // empty method * public interface Foo {} // empty interface * public class Foo {} // empty class * public enum Foo {} // empty enum * MyClass c = new MyClass() {}; // empty anonymous class * while (i = 1) {} // empty while loop * for (int i = 1; i > 1; i++) {} // empty for loop * do {} while (i = 1); // empty do-while loop * Runnable noop = () -> {}; // empty lambda * public @interface Beta {} // empty annotation type * }</pre> * * <p>This check does not flag as violation double brace initialization like:</p> * <pre> * new Properties() {{ * setProperty("key", "value"); * }}; * </pre> * * <p>To configure the check to allow empty method blocks use * * <pre> <property name="allowEmptyMethods" value="true" /></pre> * * <p>To configure the check to allow empty constructor blocks use * * <pre> <property name="allowEmptyConstructors" value="true" /></pre> * * <p>To configure the check to allow empty type blocks use * * <pre> <property name="allowEmptyTypes" value="true" /></pre> * * <p>To configure the check to allow empty loop blocks use * * <pre> <property name="allowEmptyLoops" value="true" /></pre> * * <p>To configure the check to allow empty lambdas blocks use * * <pre> <property name="allowEmptyLambdas" value="true" /></pre> * * <p>Also, this check can be configured to ignore the colon in an enhanced for * loop. The colon in an enhanced for loop is ignored by default * * <p>To configure the check to ignore the colon * * <pre> <property name="ignoreEnhancedForColon" value="true" /></pre> * * @author Oliver Burn * @author maxvetrenko * @author Andrei Selkin */ public class WhitespaceAroundCheck extends AbstractCheck { /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_WS_NOT_PRECEDED = "ws.notPreceded"; /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_WS_NOT_FOLLOWED = "ws.notFollowed"; /** Whether or not empty constructor bodies are allowed. */ private boolean allowEmptyConstructors; /** Whether or not empty method bodies are allowed. */ private boolean allowEmptyMethods; /** Whether or not empty classes, enums and interfaces are allowed. */ private boolean allowEmptyTypes; /** Whether or not empty loops are allowed. */ private boolean allowEmptyLoops; /** Whether or not empty lambda blocks are allowed. */ private boolean allowEmptyLambdas; /** Whether or not empty catch blocks are allowed. */ private boolean allowEmptyCatches; /** Whether or not to ignore a colon in a enhanced for loop. */ private boolean ignoreEnhancedForColon = true; @Override public int[] getDefaultTokens() { return new int[] { TokenTypes.ASSIGN, TokenTypes.BAND, TokenTypes.BAND_ASSIGN, TokenTypes.BOR, TokenTypes.BOR_ASSIGN, TokenTypes.BSR, TokenTypes.BSR_ASSIGN, TokenTypes.BXOR, TokenTypes.BXOR_ASSIGN, TokenTypes.COLON, TokenTypes.DIV, TokenTypes.DIV_ASSIGN, TokenTypes.DO_WHILE, TokenTypes.EQUAL, TokenTypes.GE, TokenTypes.GT, TokenTypes.LAMBDA, TokenTypes.LAND, TokenTypes.LCURLY, TokenTypes.LE, TokenTypes.LITERAL_CATCH, TokenTypes.LITERAL_DO, TokenTypes.LITERAL_ELSE, TokenTypes.LITERAL_FINALLY, TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_IF, TokenTypes.LITERAL_RETURN, TokenTypes.LITERAL_SWITCH, TokenTypes.LITERAL_SYNCHRONIZED, TokenTypes.LITERAL_TRY, TokenTypes.LITERAL_WHILE, TokenTypes.LOR, TokenTypes.LT, TokenTypes.MINUS, TokenTypes.MINUS_ASSIGN, TokenTypes.MOD, TokenTypes.MOD_ASSIGN, TokenTypes.NOT_EQUAL, TokenTypes.PLUS, TokenTypes.PLUS_ASSIGN, TokenTypes.QUESTION, TokenTypes.RCURLY, TokenTypes.SL, TokenTypes.SLIST, TokenTypes.SL_ASSIGN, TokenTypes.SR, TokenTypes.SR_ASSIGN, TokenTypes.STAR, TokenTypes.STAR_ASSIGN, TokenTypes.LITERAL_ASSERT, TokenTypes.TYPE_EXTENSION_AND, }; } @Override public int[] getAcceptableTokens() { return new int[] { TokenTypes.ASSIGN, TokenTypes.ARRAY_INIT, TokenTypes.BAND, TokenTypes.BAND_ASSIGN, TokenTypes.BOR, TokenTypes.BOR_ASSIGN, TokenTypes.BSR, TokenTypes.BSR_ASSIGN, TokenTypes.BXOR, TokenTypes.BXOR_ASSIGN, TokenTypes.COLON, TokenTypes.DIV, TokenTypes.DIV_ASSIGN, TokenTypes.DO_WHILE, TokenTypes.EQUAL, TokenTypes.GE, TokenTypes.GT, TokenTypes.LAMBDA, TokenTypes.LAND, TokenTypes.LCURLY, TokenTypes.LE, TokenTypes.LITERAL_CATCH, TokenTypes.LITERAL_DO, TokenTypes.LITERAL_ELSE, TokenTypes.LITERAL_FINALLY, TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_IF, TokenTypes.LITERAL_RETURN, TokenTypes.LITERAL_SWITCH, TokenTypes.LITERAL_SYNCHRONIZED, TokenTypes.LITERAL_TRY, TokenTypes.LITERAL_WHILE, TokenTypes.LOR, TokenTypes.LT, TokenTypes.MINUS, TokenTypes.MINUS_ASSIGN, TokenTypes.MOD, TokenTypes.MOD_ASSIGN, TokenTypes.NOT_EQUAL, TokenTypes.PLUS, TokenTypes.PLUS_ASSIGN, TokenTypes.QUESTION, TokenTypes.RCURLY, TokenTypes.SL, TokenTypes.SLIST, TokenTypes.SL_ASSIGN, TokenTypes.SR, TokenTypes.SR_ASSIGN, TokenTypes.STAR, TokenTypes.STAR_ASSIGN, TokenTypes.LITERAL_ASSERT, TokenTypes.TYPE_EXTENSION_AND, TokenTypes.WILDCARD_TYPE, TokenTypes.GENERIC_START, TokenTypes.GENERIC_END, }; } @Override public int[] getRequiredTokens() { return CommonUtils.EMPTY_INT_ARRAY; } /** * Sets whether or not empty method bodies are allowed. * @param allow {@code true} to allow empty method bodies. */ public void setAllowEmptyMethods(boolean allow) { allowEmptyMethods = allow; } /** * Sets whether or not empty constructor bodies are allowed. * @param allow {@code true} to allow empty constructor bodies. */ public void setAllowEmptyConstructors(boolean allow) { allowEmptyConstructors = allow; } /** * Sets whether or not to ignore the whitespace around the * colon in an enhanced for loop. * @param ignore {@code true} to ignore enhanced for colon. */ public void setIgnoreEnhancedForColon(boolean ignore) { ignoreEnhancedForColon = ignore; } /** * Sets whether or not empty type bodies are allowed. * @param allow {@code true} to allow empty type bodies. */ public void setAllowEmptyTypes(boolean allow) { allowEmptyTypes = allow; } /** * Sets whether or not empty loop bodies are allowed. * @param allow {@code true} to allow empty loops bodies. */ public void setAllowEmptyLoops(boolean allow) { allowEmptyLoops = allow; } /** * Sets whether or not empty lambdas bodies are allowed. * @param allow {@code true} to allow empty lambda expressions. */ public void setAllowEmptyLambdas(boolean allow) { allowEmptyLambdas = allow; } /** * Sets whether or not empty catch blocks are allowed. * @param allow {@code true} to allow empty catch blocks. */ public void setAllowEmptyCatches(boolean allow) { allowEmptyCatches = allow; } @Override public void visitToken(DetailAST ast) { final int currentType = ast.getType(); if (!isNotRelevantSituation(ast, currentType)) { final String line = getLine(ast.getLineNo() - 1); final int before = ast.getColumnNo() - 1; final int after = ast.getColumnNo() + ast.getText().length(); if (before >= 0) { final char prevChar = line.charAt(before); if (shouldCheckSeparationFromPreviousToken(ast) && !Character.isWhitespace(prevChar)) { log(ast.getLineNo(), ast.getColumnNo(), MSG_WS_NOT_PRECEDED, ast.getText()); } } if (after < line.length()) { final char nextChar = line.charAt(after); if (shouldCheckSeparationFromNextToken(ast, nextChar) && !Character.isWhitespace(nextChar)) { log(ast.getLineNo(), ast.getColumnNo() + ast.getText().length(), MSG_WS_NOT_FOLLOWED, ast.getText()); } } } } /** * Is ast not a target of Check. * @param ast ast * @param currentType type of ast * @return true is ok to skip validation */ private boolean isNotRelevantSituation(DetailAST ast, int currentType) { final int parentType = ast.getParent().getType(); final boolean starImport = currentType == TokenTypes.STAR && parentType == TokenTypes.DOT; final boolean slistInsideCaseGroup = currentType == TokenTypes.SLIST && parentType == TokenTypes.CASE_GROUP; final boolean starImportOrSlistInsideCaseGroup = starImport || slistInsideCaseGroup; final boolean colonOfCaseOrDefaultOrForEach = isColonOfCaseOrDefault(currentType, parentType) || isColonOfForEach(currentType, parentType); final boolean emptyBlockOrType = isEmptyBlock(ast, parentType) || allowEmptyTypes && isEmptyType(ast); return starImportOrSlistInsideCaseGroup || colonOfCaseOrDefaultOrForEach || emptyBlockOrType || isArrayInitialization(currentType, parentType); } /** * Check if it should be checked if previous token is separated from current by * whitespace. * This function is needed to recognise double brace initialization as valid, * unfortunately its not possible to implement this functionality * in isNotRelevantSituation method, because in this method when we return * true(is not relevant) ast is later doesn't check at all. For example: * new Properties() {{setProperty("double curly braces", "are not a style error"); * }}; * For second left curly brace in first line when we would return true from * isNotRelevantSituation it wouldn't later check that the next token(setProperty) * is not separated from previous token. * @param ast current AST. * @return true if it should be checked if previous token is separated by whitespace, * false otherwise. */ private static boolean shouldCheckSeparationFromPreviousToken(DetailAST ast) { return !isPartOfDoubleBraceInitializerForPreviousToken(ast); } /** * Check if it should be checked if next token is separated from current by * whitespace. Explanation why this method is needed is identical to one * included in shouldCheckSeparationFromPreviousToken method. * @param ast current AST. * @param nextChar next character. * @return true if it should be checked if next token is separated by whitespace, * false otherwise. */ private static boolean shouldCheckSeparationFromNextToken(DetailAST ast, char nextChar) { return !(ast.getType() == TokenTypes.LITERAL_RETURN && ast.getFirstChild().getType() == TokenTypes.SEMI) && ast.getType() != TokenTypes.ARRAY_INIT && !isAnonymousInnerClassEnd(ast.getType(), nextChar) && !isPartOfDoubleBraceInitializerForNextToken(ast); } /** * Check for "})" or "};" or "},". Happens with anon-inners * @param currentType token * @param nextChar next symbol * @return true is that is end of anon inner class */ private static boolean isAnonymousInnerClassEnd(int currentType, char nextChar) { return currentType == TokenTypes.RCURLY && (nextChar == ')' || nextChar == ';' || nextChar == ',' || nextChar == '.'); } /** * Is empty block. * @param ast ast * @param parentType parent * @return true is block is empty */ private boolean isEmptyBlock(DetailAST ast, int parentType) { return isEmptyMethodBlock(ast, parentType) || isEmptyCtorBlock(ast, parentType) || isEmptyLoop(ast, parentType) || isEmptyLambda(ast, parentType) || isEmptyCatch(ast, parentType); } /** * Tests if a given {@code DetailAST} is part of an empty block. * An example empty block might look like the following * <p> * <pre> public void myMethod(int val) {}</pre> * </p> * In the above, the method body is an empty block ("{}"). * * @param ast the {@code DetailAST} to test. * @param parentType the token type of {@code ast}'s parent. * @param match the parent token type we're looking to match. * @return {@code true} if {@code ast} makes up part of an * empty block contained under a {@code match} token type * node. */ private static boolean isEmptyBlock(DetailAST ast, int parentType, int match) { final boolean result; final int type = ast.getType(); if (type == TokenTypes.RCURLY) { final DetailAST parent = ast.getParent(); final DetailAST grandParent = ast.getParent().getParent(); result = parentType == TokenTypes.SLIST && parent.getFirstChild().getType() == TokenTypes.RCURLY && grandParent.getType() == match; } else { result = type == TokenTypes.SLIST && parentType == match && ast.getFirstChild().getType() == TokenTypes.RCURLY; } return result; } /** * Whether colon belongs to cases or defaults. * @param currentType current * @param parentType parent * @return true if current token in colon of case or default tokens */ private static boolean isColonOfCaseOrDefault(int currentType, int parentType) { return currentType == TokenTypes.COLON && (parentType == TokenTypes.LITERAL_DEFAULT || parentType == TokenTypes.LITERAL_CASE); } /** * Whether colon belongs to for-each. * @param currentType current * @param parentType parent * @return true if current token in colon of for-each token */ private boolean isColonOfForEach(int currentType, int parentType) { return currentType == TokenTypes.COLON && parentType == TokenTypes.FOR_EACH_CLAUSE && ignoreEnhancedForColon; } /** * Is array initialization. * @param currentType current token * @param parentType parent token * @return true is current token inside array initialization */ private static boolean isArrayInitialization(int currentType, int parentType) { return (currentType == TokenTypes.RCURLY || currentType == TokenTypes.LCURLY) && (parentType == TokenTypes.ARRAY_INIT || parentType == TokenTypes.ANNOTATION_ARRAY_INIT); } /** * Test if the given {@code DetailAST} is part of an allowed empty * method block. * @param ast the {@code DetailAST} to test. * @param parentType the token type of {@code ast}'s parent. * @return {@code true} if {@code ast} makes up part of an * allowed empty method block. */ private boolean isEmptyMethodBlock(DetailAST ast, int parentType) { return allowEmptyMethods && isEmptyBlock(ast, parentType, TokenTypes.METHOD_DEF); } /** * Test if the given {@code DetailAST} is part of an allowed empty * constructor (ctor) block. * @param ast the {@code DetailAST} to test. * @param parentType the token type of {@code ast}'s parent. * @return {@code true} if {@code ast} makes up part of an * allowed empty constructor block. */ private boolean isEmptyCtorBlock(DetailAST ast, int parentType) { return allowEmptyConstructors && isEmptyBlock(ast, parentType, TokenTypes.CTOR_DEF); } /** * * @param ast ast the {@code DetailAST} to test. * @param parentType the token type of {@code ast}'s parent. * @return {@code true} if {@code ast} makes up part of an * allowed empty loop block. */ private boolean isEmptyLoop(DetailAST ast, int parentType) { return allowEmptyLoops && (isEmptyBlock(ast, parentType, TokenTypes.LITERAL_FOR) || isEmptyBlock(ast, parentType, TokenTypes.LITERAL_WHILE) || isEmptyBlock(ast, parentType, TokenTypes.LITERAL_DO)); } /** * Test if the given {@code DetailAST} is part of an allowed empty * lambda block. * @param ast the {@code DetailAST} to test. * @param parentType the token type of {@code ast}'s parent. * @return {@code true} if {@code ast} makes up part of an * allowed empty lambda block. */ private boolean isEmptyLambda(DetailAST ast, int parentType) { return allowEmptyLambdas && isEmptyBlock(ast, parentType, TokenTypes.LAMBDA); } /** * Tests if the given {@code DetailAst} is part of an allowed empty * catch block. * @param ast the {@code DetailAst} to test. * @param parentType the token type of {@code ast}'s parent * @return {@code true} if {@code ast} makes up part of an * allowed empty catch block. */ private boolean isEmptyCatch(DetailAST ast, int parentType) { return allowEmptyCatches && isEmptyBlock(ast, parentType, TokenTypes.LITERAL_CATCH); } /** * Test if the given {@code DetailAST} is part of an empty block. * An example empty block might look like the following * <p> * <pre> class Foo {}</pre> * </p> * * @param ast ast the {@code DetailAST} to test. * @return {@code true} if {@code ast} makes up part of an * empty block contained under a {@code match} token type * node. */ private static boolean isEmptyType(DetailAST ast) { final int type = ast.getType(); final DetailAST nextSibling = ast.getNextSibling(); final DetailAST previousSibling = ast.getPreviousSibling(); return type == TokenTypes.LCURLY && nextSibling.getType() == TokenTypes.RCURLY || type == TokenTypes.RCURLY && previousSibling != null && previousSibling.getType() == TokenTypes.LCURLY; } /** * Check if given ast is part of double brace initializer and if it * should omit checking if previous token is separated by whitespace. * @param ast ast to check * @return true if it should omit checking for previous token, false otherwise */ private static boolean isPartOfDoubleBraceInitializerForPreviousToken(DetailAST ast) { final boolean initializerBeginsAfterClassBegins = ast.getType() == TokenTypes.SLIST && ast.getParent().getType() == TokenTypes.INSTANCE_INIT; final boolean classEndsAfterInitializerEnds = ast.getType() == TokenTypes.RCURLY && ast.getPreviousSibling() != null && ast.getPreviousSibling().getType() == TokenTypes.INSTANCE_INIT; return initializerBeginsAfterClassBegins || classEndsAfterInitializerEnds; } /** * Check if given ast is part of double brace initializer and if it * should omit checking if next token is separated by whitespace. * See <a href="https://github.com/checkstyle/checkstyle/pull/2845"> * PR#2845</a> for more information why this function was needed. * @param ast ast to check * @return true if it should omit checking for next token, false otherwise */ private static boolean isPartOfDoubleBraceInitializerForNextToken(DetailAST ast) { final boolean classBeginBeforeInitializerBegin = ast.getType() == TokenTypes.LCURLY && ast.getNextSibling().getType() == TokenTypes.INSTANCE_INIT; final boolean initalizerEndsBeforeClassEnds = ast.getType() == TokenTypes.RCURLY && ast.getParent().getType() == TokenTypes.SLIST && ast.getParent().getParent().getType() == TokenTypes.INSTANCE_INIT && ast.getParent().getParent().getNextSibling().getType() == TokenTypes.RCURLY; return classBeginBeforeInitializerBegin || initalizerEndsBeforeClassEnds; } }