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