| //////////////////////////////////////////////////////////////////////////////// |
| // 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.StatelessCheck; |
| 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; |
| |
| /** |
| * <p> |
| * Checks that there is no whitespace after a token. |
| * More specifically, it checks that it is not followed by whitespace, |
| * or (if linebreaks are allowed) all characters on the line after are |
| * whitespace. To forbid linebreaks after a token, set property |
| * allowLineBreaks to false. |
| * </p> |
| * <p> By default the check will check the following operators: |
| * {@link TokenTypes#ARRAY_INIT ARRAY_INIT}, |
| * {@link TokenTypes#AT AT}, |
| * {@link TokenTypes#BNOT BNOT}, |
| * {@link TokenTypes#DEC DEC}, |
| * {@link TokenTypes#DOT DOT}, |
| * {@link TokenTypes#INC INC}, |
| * {@link TokenTypes#LNOT LNOT}, |
| * {@link TokenTypes#UNARY_MINUS UNARY_MINUS}, |
| * {@link TokenTypes#UNARY_PLUS UNARY_PLUS}, |
| * {@link TokenTypes#TYPECAST TYPECAST}, |
| * {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR}, |
| * {@link TokenTypes#INDEX_OP INDEX_OP}. |
| * </p> |
| * <p> |
| * The check processes |
| * {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR}, |
| * {@link TokenTypes#INDEX_OP INDEX_OP} |
| * specially from other tokens. Actually it is checked that there is |
| * no whitespace before this tokens, not after them. |
| * Spaces after the {@link TokenTypes#ANNOTATIONS ANNOTATIONS} |
| * before {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} |
| * and {@link TokenTypes#INDEX_OP INDEX_OP} will be ignored. |
| * </p> |
| * <p> |
| * An example of how to configure the check is: |
| * </p> |
| * <pre> |
| * <module name="NoWhitespaceAfter"/> |
| * </pre> |
| * <p> An example of how to configure the check to forbid linebreaks after |
| * a {@link TokenTypes#DOT DOT} token is: |
| * </p> |
| * <pre> |
| * <module name="NoWhitespaceAfter"> |
| * <property name="tokens" value="DOT"/> |
| * <property name="allowLineBreaks" value="false"/> |
| * </module> |
| * </pre> |
| * <p> |
| * If the annotation is between the type and the array, the check will skip validation for spaces: |
| * </p> |
| * <pre> |
| * public void foo(final char @NotNull [] param) {} // No violation |
| * </pre> |
| * @author Rick Giles |
| * @author lkuehne |
| * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a> |
| * @author attatrol |
| */ |
| @StatelessCheck |
| public class NoWhitespaceAfterCheck extends AbstractCheck { |
| |
| /** |
| * A key is pointing to the warning message text in "messages.properties" |
| * file. |
| */ |
| public static final String MSG_KEY = "ws.followed"; |
| |
| /** Whether whitespace is allowed if the AST is at a linebreak. */ |
| private boolean allowLineBreaks = true; |
| |
| @Override |
| public int[] getDefaultTokens() { |
| return new int[] { |
| TokenTypes.ARRAY_INIT, |
| TokenTypes.AT, |
| TokenTypes.INC, |
| TokenTypes.DEC, |
| TokenTypes.UNARY_MINUS, |
| TokenTypes.UNARY_PLUS, |
| TokenTypes.BNOT, |
| TokenTypes.LNOT, |
| TokenTypes.DOT, |
| TokenTypes.ARRAY_DECLARATOR, |
| TokenTypes.INDEX_OP, |
| }; |
| } |
| |
| @Override |
| public int[] getAcceptableTokens() { |
| return new int[] { |
| TokenTypes.ARRAY_INIT, |
| TokenTypes.AT, |
| TokenTypes.INC, |
| TokenTypes.DEC, |
| TokenTypes.UNARY_MINUS, |
| TokenTypes.UNARY_PLUS, |
| TokenTypes.BNOT, |
| TokenTypes.LNOT, |
| TokenTypes.DOT, |
| TokenTypes.TYPECAST, |
| TokenTypes.ARRAY_DECLARATOR, |
| TokenTypes.INDEX_OP, |
| TokenTypes.LITERAL_SYNCHRONIZED, |
| TokenTypes.METHOD_REF, |
| }; |
| } |
| |
| @Override |
| public int[] getRequiredTokens() { |
| return CommonUtils.EMPTY_INT_ARRAY; |
| } |
| |
| /** |
| * Control whether whitespace is flagged at linebreaks. |
| * @param allowLineBreaks whether whitespace should be |
| * flagged at linebreaks. |
| */ |
| public void setAllowLineBreaks(boolean allowLineBreaks) { |
| this.allowLineBreaks = allowLineBreaks; |
| } |
| |
| @Override |
| public void visitToken(DetailAST ast) { |
| final DetailAST whitespaceFollowedAst = getWhitespaceFollowedNode(ast); |
| |
| if (whitespaceFollowedAst.getNextSibling() == null |
| || whitespaceFollowedAst.getNextSibling().getType() != TokenTypes.ANNOTATIONS) { |
| final int whitespaceColumnNo = getPositionAfter(whitespaceFollowedAst); |
| final int whitespaceLineNo = whitespaceFollowedAst.getLineNo(); |
| |
| if (hasTrailingWhitespace(ast, whitespaceColumnNo, whitespaceLineNo)) { |
| log(whitespaceLineNo, whitespaceColumnNo, |
| MSG_KEY, whitespaceFollowedAst.getText()); |
| } |
| } |
| } |
| |
| /** |
| * For a visited ast node returns node that should be checked |
| * for not being followed by whitespace. |
| * @param ast |
| * , visited node. |
| * @return node before ast. |
| */ |
| private static DetailAST getWhitespaceFollowedNode(DetailAST ast) { |
| final DetailAST whitespaceFollowedAst; |
| switch (ast.getType()) { |
| case TokenTypes.TYPECAST: |
| whitespaceFollowedAst = ast.findFirstToken(TokenTypes.RPAREN); |
| break; |
| case TokenTypes.ARRAY_DECLARATOR: |
| whitespaceFollowedAst = getArrayDeclaratorPreviousElement(ast); |
| break; |
| case TokenTypes.INDEX_OP: |
| whitespaceFollowedAst = getIndexOpPreviousElement(ast); |
| break; |
| default: |
| whitespaceFollowedAst = ast; |
| } |
| return whitespaceFollowedAst; |
| } |
| |
| /** |
| * Gets position after token (place of possible redundant whitespace). |
| * @param ast Node representing token. |
| * @return position after token. |
| */ |
| private static int getPositionAfter(DetailAST ast) { |
| final int after; |
| //If target of possible redundant whitespace is in method definition. |
| if (ast.getType() == TokenTypes.IDENT |
| && ast.getNextSibling() != null |
| && ast.getNextSibling().getType() == TokenTypes.LPAREN) { |
| final DetailAST methodDef = ast.getParent(); |
| final DetailAST endOfParams = methodDef.findFirstToken(TokenTypes.RPAREN); |
| after = endOfParams.getColumnNo() + 1; |
| } |
| else { |
| after = ast.getColumnNo() + ast.getText().length(); |
| } |
| return after; |
| } |
| |
| /** |
| * Checks if there is unwanted whitespace after the visited node. |
| * @param ast |
| * , visited node. |
| * @param whitespaceColumnNo |
| * , column number of a possible whitespace. |
| * @param whitespaceLineNo |
| * , line number of a possible whitespace. |
| * @return true if whitespace found. |
| */ |
| private boolean hasTrailingWhitespace(DetailAST ast, |
| int whitespaceColumnNo, int whitespaceLineNo) { |
| final boolean result; |
| final int astLineNo = ast.getLineNo(); |
| final String line = getLine(astLineNo - 1); |
| if (astLineNo == whitespaceLineNo && whitespaceColumnNo < line.length()) { |
| result = Character.isWhitespace(line.charAt(whitespaceColumnNo)); |
| } |
| else { |
| result = !allowLineBreaks; |
| } |
| return result; |
| } |
| |
| /** |
| * Returns proper argument for getPositionAfter method, it is a token after |
| * {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR}, in can be {@link TokenTypes#RBRACK |
| * RBRACK}, {@link TokenTypes#IDENT IDENT} or an array type definition (literal). |
| * @param ast |
| * , {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} node. |
| * @return previous node by text order. |
| */ |
| private static DetailAST getArrayDeclaratorPreviousElement(DetailAST ast) { |
| final DetailAST previousElement; |
| final DetailAST firstChild = ast.getFirstChild(); |
| if (firstChild.getType() == TokenTypes.ARRAY_DECLARATOR) { |
| // second or higher array index |
| previousElement = firstChild.findFirstToken(TokenTypes.RBRACK); |
| } |
| else { |
| // first array index, is preceded with identifier or type |
| final DetailAST parent = getFirstNonArrayDeclaratorParent(ast); |
| switch (parent.getType()) { |
| // generics |
| case TokenTypes.TYPE_ARGUMENT: |
| final DetailAST wildcard = parent.findFirstToken(TokenTypes.WILDCARD_TYPE); |
| if (wildcard == null) { |
| // usual generic type argument like <char[]> |
| previousElement = getTypeLastNode(ast); |
| } |
| else { |
| // constructions with wildcard like <? extends String[]> |
| previousElement = getTypeLastNode(ast.getFirstChild()); |
| } |
| break; |
| // 'new' is a special case with its own subtree structure |
| case TokenTypes.LITERAL_NEW: |
| previousElement = getTypeLastNode(parent); |
| break; |
| // mundane array declaration, can be either java style or C style |
| case TokenTypes.TYPE: |
| previousElement = getPreviousNodeWithParentOfTypeAst(ast, parent); |
| break; |
| // i.e. boolean[].class |
| case TokenTypes.DOT: |
| previousElement = getTypeLastNode(ast); |
| break; |
| // java 8 method reference |
| case TokenTypes.METHOD_REF: |
| final DetailAST ident = getIdentLastToken(ast); |
| if (ident == null) { |
| //i.e. int[]::new |
| previousElement = ast.getFirstChild(); |
| } |
| else { |
| previousElement = ident; |
| } |
| break; |
| default: |
| throw new IllegalStateException("unexpected ast syntax " + parent); |
| } |
| } |
| return previousElement; |
| } |
| |
| /** |
| * Gets previous node for {@link TokenTypes#INDEX_OP INDEX_OP} token |
| * for usage in getPositionAfter method, it is a simplified copy of |
| * getArrayDeclaratorPreviousElement method. |
| * @param ast |
| * , {@link TokenTypes#INDEX_OP INDEX_OP} node. |
| * @return previous node by text order. |
| */ |
| private static DetailAST getIndexOpPreviousElement(DetailAST ast) { |
| final DetailAST result; |
| final DetailAST firstChild = ast.getFirstChild(); |
| if (firstChild.getType() == TokenTypes.INDEX_OP) { |
| // second or higher array index |
| result = firstChild.findFirstToken(TokenTypes.RBRACK); |
| } |
| else { |
| final DetailAST ident = getIdentLastToken(ast); |
| if (ident == null) { |
| final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN); |
| // construction like new int[]{1}[0] |
| if (rparen == null) { |
| final DetailAST lastChild = firstChild.getLastChild(); |
| result = lastChild.findFirstToken(TokenTypes.RCURLY); |
| } |
| // construction like ((byte[]) pixels)[0] |
| else { |
| result = rparen; |
| } |
| } |
| else { |
| result = ident; |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Get node that owns {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} sequence. |
| * @param ast |
| * , {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} node. |
| * @return owner node. |
| */ |
| private static DetailAST getFirstNonArrayDeclaratorParent(DetailAST ast) { |
| DetailAST parent = ast.getParent(); |
| while (parent.getType() == TokenTypes.ARRAY_DECLARATOR) { |
| parent = parent.getParent(); |
| } |
| return parent; |
| } |
| |
| /** |
| * Searches parameter node for a type node. |
| * Returns it or its last node if it has an extended structure. |
| * @param ast |
| * , subject node. |
| * @return type node. |
| */ |
| private static DetailAST getTypeLastNode(DetailAST ast) { |
| DetailAST result = ast.findFirstToken(TokenTypes.TYPE_ARGUMENTS); |
| if (result == null) { |
| result = getIdentLastToken(ast); |
| if (result == null) { |
| //primitive literal expected |
| result = ast.getFirstChild(); |
| } |
| } |
| else { |
| result = result.findFirstToken(TokenTypes.GENERIC_END); |
| } |
| return result; |
| } |
| |
| /** |
| * Finds previous node by text order for an array declarator, |
| * which parent type is {@link TokenTypes#TYPE TYPE}. |
| * @param ast |
| * , array declarator node. |
| * @param parent |
| * , its parent node. |
| * @return previous node by text order. |
| */ |
| private static DetailAST getPreviousNodeWithParentOfTypeAst(DetailAST ast, DetailAST parent) { |
| final DetailAST previousElement; |
| final DetailAST ident = getIdentLastToken(parent.getParent()); |
| final DetailAST lastTypeNode = getTypeLastNode(ast); |
| // sometimes there are ident-less sentences |
| // i.e. "(Object[]) null", but in casual case should be |
| // checked whether ident or lastTypeNode has preceding position |
| // determining if it is java style or C style |
| if (ident == null || ident.getLineNo() > ast.getLineNo()) { |
| previousElement = lastTypeNode; |
| } |
| else if (ident.getLineNo() < ast.getLineNo()) { |
| previousElement = ident; |
| } |
| //ident and lastTypeNode lay on one line |
| else { |
| final int instanceOfSize = 13; |
| // +2 because ast has `[]` after the ident |
| if (ident.getColumnNo() >= ast.getColumnNo() + 2 |
| // +13 because ident (at most 1 character) is followed by |
| // ' instanceof ' (12 characters) |
| || lastTypeNode.getColumnNo() >= ident.getColumnNo() + instanceOfSize) { |
| previousElement = lastTypeNode; |
| } |
| else { |
| previousElement = ident; |
| } |
| } |
| return previousElement; |
| } |
| |
| /** |
| * Gets leftmost token of identifier. |
| * @param ast |
| * , token possibly possessing an identifier. |
| * @return leftmost token of identifier. |
| */ |
| private static DetailAST getIdentLastToken(DetailAST ast) { |
| // single identifier token as a name is the most common case |
| DetailAST result = ast.findFirstToken(TokenTypes.IDENT); |
| if (result == null) { |
| final DetailAST dot = ast.findFirstToken(TokenTypes.DOT); |
| // method call case |
| if (dot == null) { |
| final DetailAST methodCall = ast.findFirstToken(TokenTypes.METHOD_CALL); |
| if (methodCall != null) { |
| result = methodCall.findFirstToken(TokenTypes.RPAREN); |
| } |
| } |
| // qualified name case |
| else { |
| if (dot.findFirstToken(TokenTypes.DOT) == null) { |
| result = dot.getFirstChild().getNextSibling(); |
| } |
| else { |
| result = dot.findFirstToken(TokenTypes.IDENT); |
| } |
| } |
| } |
| return result; |
| } |
| |
| } |