001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2017 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.whitespace;
021
022import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
023import com.puppycrawl.tools.checkstyle.api.DetailAST;
024import com.puppycrawl.tools.checkstyle.api.TokenTypes;
025import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
026
027/**
028 * <p>
029 * Checks that there is no whitespace after a token.
030 * More specifically, it checks that it is not followed by whitespace,
031 * or (if linebreaks are allowed) all characters on the line after are
032 * whitespace. To forbid linebreaks after a token, set property
033 * allowLineBreaks to false.
034 * </p>
035  * <p> By default the check will check the following operators:
036 *  {@link TokenTypes#ARRAY_INIT ARRAY_INIT},
037 *  {@link TokenTypes#BNOT BNOT},
038 *  {@link TokenTypes#DEC DEC},
039 *  {@link TokenTypes#DOT DOT},
040 *  {@link TokenTypes#INC INC},
041 *  {@link TokenTypes#LNOT LNOT},
042 *  {@link TokenTypes#UNARY_MINUS UNARY_MINUS},
043 *  {@link TokenTypes#UNARY_PLUS UNARY_PLUS},
044 *  {@link TokenTypes#TYPECAST TYPECAST},
045 *  {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR},
046 *  {@link TokenTypes#INDEX_OP INDEX_OP}.
047 * </p>
048 * <p>
049 * The check processes
050 * {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR},
051 * {@link TokenTypes#INDEX_OP INDEX_OP}
052 * specially from other tokens. Actually it is checked that there is
053 * no whitespace before this tokens, not after them.
054 * </p>
055 * <p>
056 * An example of how to configure the check is:
057 * </p>
058 * <pre>
059 * &lt;module name="NoWhitespaceAfter"/&gt;
060 * </pre>
061 * <p> An example of how to configure the check to forbid linebreaks after
062 * a {@link TokenTypes#DOT DOT} token is:
063 * </p>
064 * <pre>
065 * &lt;module name="NoWhitespaceAfter"&gt;
066 *     &lt;property name="tokens" value="DOT"/&gt;
067 *     &lt;property name="allowLineBreaks" value="false"/&gt;
068 * &lt;/module&gt;
069 * </pre>
070 * @author Rick Giles
071 * @author lkuehne
072 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a>
073 * @author attatrol
074 */
075public class NoWhitespaceAfterCheck extends AbstractCheck {
076
077    /**
078     * A key is pointing to the warning message text in "messages.properties"
079     * file.
080     */
081    public static final String MSG_KEY = "ws.followed";
082
083    /** Whether whitespace is allowed if the AST is at a linebreak. */
084    private boolean allowLineBreaks = true;
085
086    @Override
087    public int[] getDefaultTokens() {
088        return new int[] {
089            TokenTypes.ARRAY_INIT,
090            TokenTypes.INC,
091            TokenTypes.DEC,
092            TokenTypes.UNARY_MINUS,
093            TokenTypes.UNARY_PLUS,
094            TokenTypes.BNOT,
095            TokenTypes.LNOT,
096            TokenTypes.DOT,
097            TokenTypes.ARRAY_DECLARATOR,
098            TokenTypes.INDEX_OP,
099        };
100    }
101
102    @Override
103    public int[] getAcceptableTokens() {
104        return new int[] {
105            TokenTypes.ARRAY_INIT,
106            TokenTypes.INC,
107            TokenTypes.DEC,
108            TokenTypes.UNARY_MINUS,
109            TokenTypes.UNARY_PLUS,
110            TokenTypes.BNOT,
111            TokenTypes.LNOT,
112            TokenTypes.DOT,
113            TokenTypes.TYPECAST,
114            TokenTypes.ARRAY_DECLARATOR,
115            TokenTypes.INDEX_OP,
116            TokenTypes.LITERAL_SYNCHRONIZED,
117            TokenTypes.METHOD_REF,
118        };
119    }
120
121    @Override
122    public int[] getRequiredTokens() {
123        return CommonUtils.EMPTY_INT_ARRAY;
124    }
125
126    /**
127     * Control whether whitespace is flagged at linebreaks.
128     * @param allowLineBreaks whether whitespace should be
129     *     flagged at linebreaks.
130     */
131    public void setAllowLineBreaks(boolean allowLineBreaks) {
132        this.allowLineBreaks = allowLineBreaks;
133    }
134
135    @Override
136    public void visitToken(DetailAST ast) {
137        final DetailAST whitespaceFollowedAst = getWhitespaceFollowedNode(ast);
138
139        final int whitespaceColumnNo = getPositionAfter(whitespaceFollowedAst);
140        final int whitespaceLineNo = whitespaceFollowedAst.getLineNo();
141
142        if (hasTrailingWhitespace(ast, whitespaceColumnNo, whitespaceLineNo)) {
143            log(whitespaceLineNo, whitespaceColumnNo,
144                MSG_KEY, whitespaceFollowedAst.getText());
145        }
146    }
147
148    /**
149     * For a visited ast node returns node that should be checked
150     * for not being followed by whitespace.
151     * @param ast
152     *        , visited node.
153     * @return node before ast.
154     */
155    private static DetailAST getWhitespaceFollowedNode(DetailAST ast) {
156        final DetailAST whitespaceFollowedAst;
157        switch (ast.getType()) {
158            case TokenTypes.TYPECAST:
159                whitespaceFollowedAst = ast.findFirstToken(TokenTypes.RPAREN);
160                break;
161            case TokenTypes.ARRAY_DECLARATOR:
162                whitespaceFollowedAst = getArrayDeclaratorPreviousElement(ast);
163                break;
164            case TokenTypes.INDEX_OP:
165                whitespaceFollowedAst = getIndexOpPreviousElement(ast);
166                break;
167            default:
168                whitespaceFollowedAst = ast;
169        }
170        return whitespaceFollowedAst;
171    }
172
173    /**
174     * Gets position after token (place of possible redundant whitespace).
175     * @param ast Node representing token.
176     * @return position after token.
177     */
178    private static int getPositionAfter(DetailAST ast) {
179        final int after;
180        //If target of possible redundant whitespace is in method definition.
181        if (ast.getType() == TokenTypes.IDENT
182                && ast.getNextSibling() != null
183                && ast.getNextSibling().getType() == TokenTypes.LPAREN) {
184            final DetailAST methodDef = ast.getParent();
185            final DetailAST endOfParams = methodDef.findFirstToken(TokenTypes.RPAREN);
186            after = endOfParams.getColumnNo() + 1;
187        }
188        else {
189            after = ast.getColumnNo() + ast.getText().length();
190        }
191        return after;
192    }
193
194    /**
195     * Checks if there is unwanted whitespace after the visited node.
196     * @param ast
197     *        , visited node.
198     * @param whitespaceColumnNo
199     *        , column number of a possible whitespace.
200     * @param whitespaceLineNo
201     *        , line number of a possible whitespace.
202     * @return true if whitespace found.
203     */
204    private boolean hasTrailingWhitespace(DetailAST ast,
205        int whitespaceColumnNo, int whitespaceLineNo) {
206        final boolean result;
207        final int astLineNo = ast.getLineNo();
208        final String line = getLine(astLineNo - 1);
209        if (astLineNo == whitespaceLineNo && whitespaceColumnNo < line.length()) {
210            result = Character.isWhitespace(line.charAt(whitespaceColumnNo));
211        }
212        else {
213            result = !allowLineBreaks;
214        }
215        return result;
216    }
217
218    /**
219     * Returns proper argument for getPositionAfter method, it is a token after
220     * {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR}, in can be {@link TokenTypes#RBRACK
221     * RBRACK}, {@link TokenTypes#IDENT IDENT} or an array type definition (literal).
222     * @param ast
223     *        , {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} node.
224     * @return previous node by text order.
225     */
226    private static DetailAST getArrayDeclaratorPreviousElement(DetailAST ast) {
227        final DetailAST previousElement;
228        final DetailAST firstChild = ast.getFirstChild();
229        if (firstChild.getType() == TokenTypes.ARRAY_DECLARATOR) {
230            // second or higher array index
231            previousElement = firstChild.findFirstToken(TokenTypes.RBRACK);
232        }
233        else {
234            // first array index, is preceded with identifier or type
235            final DetailAST parent = getFirstNonArrayDeclaratorParent(ast);
236            switch (parent.getType()) {
237                // generics
238                case TokenTypes.TYPE_ARGUMENT:
239                    final DetailAST wildcard = parent.findFirstToken(TokenTypes.WILDCARD_TYPE);
240                    if (wildcard == null) {
241                        // usual generic type argument like <char[]>
242                        previousElement = getTypeLastNode(ast);
243                    }
244                    else {
245                        // constructions with wildcard like <? extends String[]>
246                        previousElement = getTypeLastNode(ast.getFirstChild());
247                    }
248                    break;
249                // 'new' is a special case with its own subtree structure
250                case TokenTypes.LITERAL_NEW:
251                    previousElement = getTypeLastNode(parent);
252                    break;
253                // mundane array declaration, can be either java style or C style
254                case TokenTypes.TYPE:
255                    previousElement = getPreviousNodeWithParentOfTypeAst(ast, parent);
256                    break;
257                // i.e. boolean[].class
258                case TokenTypes.DOT:
259                    previousElement = getTypeLastNode(ast);
260                    break;
261                // java 8 method reference
262                case TokenTypes.METHOD_REF:
263                    final DetailAST ident = getIdentLastToken(ast);
264                    if (ident == null) {
265                        //i.e. int[]::new
266                        previousElement = ast.getFirstChild();
267                    }
268                    else {
269                        previousElement = ident;
270                    }
271                    break;
272                default:
273                    throw new IllegalStateException("unexpected ast syntax " + parent);
274            }
275        }
276        return previousElement;
277    }
278
279    /**
280     * Gets previous node for {@link TokenTypes#INDEX_OP INDEX_OP} token
281     * for usage in getPositionAfter method, it is a simplified copy of
282     * getArrayDeclaratorPreviousElement method.
283     * @param ast
284     *        , {@link TokenTypes#INDEX_OP INDEX_OP} node.
285     * @return previous node by text order.
286     */
287    private static DetailAST getIndexOpPreviousElement(DetailAST ast) {
288        final DetailAST result;
289        final DetailAST firstChild = ast.getFirstChild();
290        if (firstChild.getType() == TokenTypes.INDEX_OP) {
291            // second or higher array index
292            result = firstChild.findFirstToken(TokenTypes.RBRACK);
293        }
294        else {
295            final DetailAST ident = getIdentLastToken(ast);
296            if (ident == null) {
297                // construction like ((byte[]) pixels)[0]
298                result = ast.findFirstToken(TokenTypes.RPAREN);
299            }
300            else {
301                result = ident;
302            }
303        }
304        return result;
305    }
306
307    /**
308     * Get node that owns {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} sequence.
309     * @param ast
310     *        , {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} node.
311     * @return owner node.
312     */
313    private static DetailAST getFirstNonArrayDeclaratorParent(DetailAST ast) {
314        DetailAST parent = ast.getParent();
315        while (parent.getType() == TokenTypes.ARRAY_DECLARATOR) {
316            parent = parent.getParent();
317        }
318        return parent;
319    }
320
321    /**
322     * Searches parameter node for a type node.
323     * Returns it or its last node if it has an extended structure.
324     * @param ast
325     *        , subject node.
326     * @return type node.
327     */
328    private static DetailAST getTypeLastNode(DetailAST ast) {
329        DetailAST result = ast.findFirstToken(TokenTypes.TYPE_ARGUMENTS);
330        if (result == null) {
331            result = getIdentLastToken(ast);
332            if (result == null) {
333                //primitive literal expected
334                result = ast.getFirstChild();
335            }
336        }
337        else {
338            result = result.findFirstToken(TokenTypes.GENERIC_END);
339        }
340        return result;
341    }
342
343    /**
344     * Finds previous node by text order for an array declarator,
345     * which parent type is {@link TokenTypes#TYPE TYPE}.
346     * @param ast
347     *        , array declarator node.
348     * @param parent
349     *        , its parent node.
350     * @return previous node by text order.
351     */
352    private static DetailAST getPreviousNodeWithParentOfTypeAst(DetailAST ast, DetailAST parent) {
353        final DetailAST previousElement;
354        final DetailAST ident = getIdentLastToken(parent.getParent());
355        final DetailAST lastTypeNode = getTypeLastNode(ast);
356        // sometimes there are ident-less sentences
357        // i.e. "(Object[]) null", but in casual case should be
358        // checked whether ident or lastTypeNode has preceding position
359        // determining if it is java style or C style
360        if (ident == null || ident.getLineNo() > ast.getLineNo()) {
361            previousElement = lastTypeNode;
362        }
363        else if (ident.getLineNo() < ast.getLineNo()) {
364            previousElement = ident;
365        }
366        //ident and lastTypeNode lay on one line
367        else {
368            if (ident.getColumnNo() > ast.getColumnNo()
369                || lastTypeNode.getColumnNo() > ident.getColumnNo()) {
370                previousElement = lastTypeNode;
371            }
372            else {
373                previousElement = ident;
374            }
375        }
376        return previousElement;
377    }
378
379    /**
380     * Gets leftmost token of identifier.
381     * @param ast
382     *        , token possibly possessing an identifier.
383     * @return leftmost token of identifier.
384     */
385    private static DetailAST getIdentLastToken(DetailAST ast) {
386        // single identifier token as a name is the most common case
387        DetailAST result = ast.findFirstToken(TokenTypes.IDENT);
388        if (result == null) {
389            final DetailAST dot = ast.findFirstToken(TokenTypes.DOT);
390            // method call case
391            if (dot == null) {
392                final DetailAST methodCall = ast.findFirstToken(TokenTypes.METHOD_CALL);
393                if (methodCall != null) {
394                    result = methodCall.findFirstToken(TokenTypes.RPAREN);
395                }
396            }
397            // qualified name case
398            else {
399                if (dot.findFirstToken(TokenTypes.DOT) == null) {
400                    result = dot.getFirstChild().getNextSibling();
401                }
402                else {
403                    result = dot.findFirstToken(TokenTypes.IDENT);
404                }
405            }
406        }
407        return result;
408    }
409
410}