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