001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2022 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 java.util.Optional;
023
024import com.puppycrawl.tools.checkstyle.StatelessCheck;
025import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
026import com.puppycrawl.tools.checkstyle.api.DetailAST;
027import com.puppycrawl.tools.checkstyle.api.TokenTypes;
028import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
029
030/**
031 * <p>
032 * Checks that there is no whitespace after a token.
033 * More specifically, it checks that it is not followed by whitespace,
034 * or (if linebreaks are allowed) all characters on the line after are
035 * whitespace. To forbid linebreaks after a token, set property
036 * {@code allowLineBreaks} to {@code false}.
037 * </p>
038 * <p>
039 * The check processes
040 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ARRAY_DECLARATOR">
041 * ARRAY_DECLARATOR</a> and
042 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INDEX_OP">
043 * INDEX_OP</a> tokens specially from other tokens. Actually it is checked that
044 * there is no whitespace before these tokens, not after them. Space after the
045 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATIONS">
046 * ANNOTATIONS</a> before
047 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ARRAY_DECLARATOR">
048 * ARRAY_DECLARATOR</a> and
049 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INDEX_OP">
050 * INDEX_OP</a> will be ignored.
051 * </p>
052 * <p>
053 * If the annotation is between the type and the array, the check will skip validation for spaces
054 * </p>
055 * <pre>
056 * public void foo(final char @NotNull [] param) {} // No violation
057 * </pre>
058 * <ul>
059 * <li>
060 * Property {@code allowLineBreaks} - Control whether whitespace is allowed
061 * if the token is at a linebreak.
062 * Type is {@code boolean}.
063 * Default value is {@code true}.
064 * </li>
065 * <li>
066 * Property {@code tokens} - tokens to check
067 * Type is {@code java.lang.String[]}.
068 * Validation type is {@code tokenSet}.
069 * Default value is:
070 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ARRAY_INIT">
071 * ARRAY_INIT</a>,
072 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#AT">
073 * AT</a>,
074 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INC">
075 * INC</a>,
076 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DEC">
077 * DEC</a>,
078 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#UNARY_MINUS">
079 * UNARY_MINUS</a>,
080 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#UNARY_PLUS">
081 * UNARY_PLUS</a>,
082 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BNOT">
083 * BNOT</a>,
084 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LNOT">
085 * LNOT</a>,
086 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DOT">
087 * DOT</a>,
088 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ARRAY_DECLARATOR">
089 * ARRAY_DECLARATOR</a>,
090 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INDEX_OP">
091 * INDEX_OP</a>.
092 * </li>
093 * </ul>
094 * <p>
095 * To configure the check:
096 * </p>
097 * <pre>
098 * &lt;module name=&quot;NoWhitespaceAfter&quot;/&gt;
099 * </pre>
100 * <p>Example:</p>
101 * <pre>
102 * class Test {
103 *
104 *   public void lineBreak(String x) {
105 *     Integer.
106 *         parseInt(x); // Ok
107 *     Integer.parseInt(x); // Ok
108 *   }
109 *
110 *   public void dotOperator(String s) {
111 *     Integer.parseInt(s); // Ok
112 *     Integer. parseInt(s); // violation, '.' is followed by whitespace
113 *   }
114 *
115 *   public void arrayDec() {
116 *     int[] arr = arr; // Ok
117 *     int [] arr = arr; // violation, int is followed by whitespace
118 *   }
119 *
120 *   public void bitwiseNot(int a) {
121 *     a = ~ a; // violation '~' is followed by whitespace
122 *     a = ~a; // Ok
123 *   }
124 * }
125 * </pre>
126 * <p>To configure the check to forbid linebreaks after a DOT token:
127 * </p>
128 * <pre>
129 * &lt;module name=&quot;NoWhitespaceAfter&quot;&gt;
130 *   &lt;property name=&quot;tokens&quot; value=&quot;DOT&quot;/&gt;
131 *   &lt;property name=&quot;allowLineBreaks&quot; value=&quot;false&quot;/&gt;
132 * &lt;/module&gt;
133 * </pre>
134 * <p>Example:</p>
135 * <pre>
136 * class Test {
137 *
138 *   public void lineBreak(String x) {
139 *     Integer.
140 *         parseInt(x); // violation, '.' is followed by whitespace
141 *     Integer.parseInt(x); // Ok
142 *   }
143 *
144 *   public void dotOperator(String s) {
145 *     Integer.parseInt(s); // Ok
146 *     Integer. parseInt(s); // violation, '.' is followed by whitespace
147 *   }
148 *
149 *   public void arrayDec() {
150 *     int[] arr = arr; // Ok
151 *     int [] arr = arr; // Ok
152 *   }
153 *
154 *   public void bitwiseNot(int a) {
155 *     a = ~ a; // Ok
156 *     a = ~a; // Ok
157 *   }
158 * }
159 * </pre>
160 * <p>
161 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
162 * </p>
163 * <p>
164 * Violation Message Keys:
165 * </p>
166 * <ul>
167 * <li>
168 * {@code ws.followed}
169 * </li>
170 * </ul>
171 *
172 * @since 3.0
173 */
174@StatelessCheck
175public class NoWhitespaceAfterCheck extends AbstractCheck {
176
177    /**
178     * A key is pointing to the warning message text in "messages.properties"
179     * file.
180     */
181    public static final String MSG_KEY = "ws.followed";
182
183    /** Control whether whitespace is allowed if the token is at a linebreak. */
184    private boolean allowLineBreaks = true;
185
186    @Override
187    public int[] getDefaultTokens() {
188        return new int[] {
189            TokenTypes.ARRAY_INIT,
190            TokenTypes.AT,
191            TokenTypes.INC,
192            TokenTypes.DEC,
193            TokenTypes.UNARY_MINUS,
194            TokenTypes.UNARY_PLUS,
195            TokenTypes.BNOT,
196            TokenTypes.LNOT,
197            TokenTypes.DOT,
198            TokenTypes.ARRAY_DECLARATOR,
199            TokenTypes.INDEX_OP,
200        };
201    }
202
203    @Override
204    public int[] getAcceptableTokens() {
205        return new int[] {
206            TokenTypes.ARRAY_INIT,
207            TokenTypes.AT,
208            TokenTypes.INC,
209            TokenTypes.DEC,
210            TokenTypes.UNARY_MINUS,
211            TokenTypes.UNARY_PLUS,
212            TokenTypes.BNOT,
213            TokenTypes.LNOT,
214            TokenTypes.DOT,
215            TokenTypes.TYPECAST,
216            TokenTypes.ARRAY_DECLARATOR,
217            TokenTypes.INDEX_OP,
218            TokenTypes.LITERAL_SYNCHRONIZED,
219            TokenTypes.METHOD_REF,
220        };
221    }
222
223    @Override
224    public int[] getRequiredTokens() {
225        return CommonUtil.EMPTY_INT_ARRAY;
226    }
227
228    /**
229     * Setter to control whether whitespace is allowed if the token is at a linebreak.
230     *
231     * @param allowLineBreaks whether whitespace should be
232     *     flagged at linebreaks.
233     */
234    public void setAllowLineBreaks(boolean allowLineBreaks) {
235        this.allowLineBreaks = allowLineBreaks;
236    }
237
238    @Override
239    public void visitToken(DetailAST ast) {
240        if (shouldCheckWhitespaceAfter(ast)) {
241            final DetailAST whitespaceFollowedAst = getWhitespaceFollowedNode(ast);
242            final int whitespaceColumnNo = getPositionAfter(whitespaceFollowedAst);
243            final int whitespaceLineNo = whitespaceFollowedAst.getLineNo();
244
245            if (hasTrailingWhitespace(ast, whitespaceColumnNo, whitespaceLineNo)) {
246                log(ast, MSG_KEY, whitespaceFollowedAst.getText());
247            }
248        }
249    }
250
251    /**
252     * For a visited ast node returns node that should be checked
253     * for not being followed by whitespace.
254     *
255     * @param ast
256     *        , visited node.
257     * @return node before ast.
258     */
259    private static DetailAST getWhitespaceFollowedNode(DetailAST ast) {
260        final DetailAST whitespaceFollowedAst;
261        switch (ast.getType()) {
262            case TokenTypes.TYPECAST:
263                whitespaceFollowedAst = ast.findFirstToken(TokenTypes.RPAREN);
264                break;
265            case TokenTypes.ARRAY_DECLARATOR:
266                whitespaceFollowedAst = getArrayDeclaratorPreviousElement(ast);
267                break;
268            case TokenTypes.INDEX_OP:
269                whitespaceFollowedAst = getIndexOpPreviousElement(ast);
270                break;
271            default:
272                whitespaceFollowedAst = ast;
273        }
274        return whitespaceFollowedAst;
275    }
276
277    /**
278     * Returns whether whitespace after a visited node should be checked. For example, whitespace
279     * is not allowed between a type and an array declarator (returns true), except when there is
280     * an annotation in between the type and array declarator (returns false).
281     *
282     * @param ast the visited node
283     * @return true if whitespace after ast should be checked
284     */
285    private static boolean shouldCheckWhitespaceAfter(DetailAST ast) {
286        boolean checkWhitespace = true;
287        final DetailAST previousSibling = ast.getPreviousSibling();
288        if (previousSibling != null && previousSibling.getType() == TokenTypes.ANNOTATIONS) {
289            checkWhitespace = false;
290        }
291        return checkWhitespace;
292    }
293
294    /**
295     * Gets position after token (place of possible redundant whitespace).
296     *
297     * @param ast Node representing token.
298     * @return position after token.
299     */
300    private static int getPositionAfter(DetailAST ast) {
301        final int after;
302        // If target of possible redundant whitespace is in method definition.
303        if (ast.getType() == TokenTypes.IDENT
304                && ast.getNextSibling() != null
305                && ast.getNextSibling().getType() == TokenTypes.LPAREN) {
306            final DetailAST methodDef = ast.getParent();
307            final DetailAST endOfParams = methodDef.findFirstToken(TokenTypes.RPAREN);
308            after = endOfParams.getColumnNo() + 1;
309        }
310        else {
311            after = ast.getColumnNo() + ast.getText().length();
312        }
313        return after;
314    }
315
316    /**
317     * Checks if there is unwanted whitespace after the visited node.
318     *
319     * @param ast
320     *        , visited node.
321     * @param whitespaceColumnNo
322     *        , column number of a possible whitespace.
323     * @param whitespaceLineNo
324     *        , line number of a possible whitespace.
325     * @return true if whitespace found.
326     */
327    private boolean hasTrailingWhitespace(DetailAST ast,
328        int whitespaceColumnNo, int whitespaceLineNo) {
329        final boolean result;
330        final int astLineNo = ast.getLineNo();
331        final int[] line = getLineCodePoints(astLineNo - 1);
332        if (astLineNo == whitespaceLineNo && whitespaceColumnNo < line.length) {
333            result = CommonUtil.isCodePointWhitespace(line, whitespaceColumnNo);
334        }
335        else {
336            result = !allowLineBreaks;
337        }
338        return result;
339    }
340
341    /**
342     * Returns proper argument for getPositionAfter method, it is a token after
343     * {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR}, in can be {@link TokenTypes#RBRACK
344     * RBRACK}, {@link TokenTypes#IDENT IDENT} or an array type definition (literal).
345     *
346     * @param ast
347     *        , {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} node.
348     * @return previous node by text order.
349     * @throws IllegalStateException if an unexpected token type is encountered.
350     */
351    private static DetailAST getArrayDeclaratorPreviousElement(DetailAST ast) {
352        final DetailAST previousElement;
353
354        if (ast.getPreviousSibling() != null
355                && ast.getPreviousSibling().getType() == TokenTypes.ARRAY_DECLARATOR) {
356            // Covers higher dimension array declarations and initializations
357            previousElement = getPreviousElementOfMultiDimArray(ast);
358        }
359        else {
360            // first array index, is preceded with identifier or type
361            final DetailAST parent = ast.getParent();
362            switch (parent.getType()) {
363                // generics
364                case TokenTypes.TYPE_UPPER_BOUNDS:
365                case TokenTypes.TYPE_LOWER_BOUNDS:
366                    previousElement = ast.getPreviousSibling();
367                    break;
368                case TokenTypes.LITERAL_NEW:
369                case TokenTypes.TYPE_ARGUMENT:
370                case TokenTypes.DOT:
371                    previousElement = getTypeLastNode(ast);
372                    break;
373                // mundane array declaration, can be either java style or C style
374                case TokenTypes.TYPE:
375                    previousElement = getPreviousNodeWithParentOfTypeAst(ast, parent);
376                    break;
377                // java 8 method reference
378                case TokenTypes.METHOD_REF:
379                    final DetailAST ident = getIdentLastToken(ast);
380                    if (ident == null) {
381                        // i.e. int[]::new
382                        previousElement = ast.getParent().getFirstChild();
383                    }
384                    else {
385                        previousElement = ident;
386                    }
387                    break;
388                default:
389                    throw new IllegalStateException("unexpected ast syntax " + parent);
390            }
391        }
392        return previousElement;
393    }
394
395    /**
396     * Gets the previous element of a second or higher dimension of an
397     * array declaration or initialization.
398     *
399     * @param leftBracket the token to get previous element of
400     * @return the previous element
401     */
402    private static DetailAST getPreviousElementOfMultiDimArray(DetailAST leftBracket) {
403        final DetailAST previousRightBracket = leftBracket.getPreviousSibling().getLastChild();
404
405        DetailAST ident = null;
406        // This will get us past the type ident, to the actual identifier
407        DetailAST parent = leftBracket.getParent().getParent();
408        while (ident == null) {
409            ident = parent.findFirstToken(TokenTypes.IDENT);
410            parent = parent.getParent();
411        }
412
413        final DetailAST previousElement;
414        if (ident.getColumnNo() > previousRightBracket.getColumnNo()
415                && ident.getColumnNo() < leftBracket.getColumnNo()) {
416            // C style and Java style ' int[] arr []' in same construct
417            previousElement = ident;
418        }
419        else {
420            // 'int[][] arr' or 'int arr[][]'
421            previousElement = previousRightBracket;
422        }
423        return previousElement;
424    }
425
426    /**
427     * Gets previous node for {@link TokenTypes#INDEX_OP INDEX_OP} token
428     * for usage in getPositionAfter method, it is a simplified copy of
429     * getArrayDeclaratorPreviousElement method.
430     *
431     * @param ast
432     *        , {@link TokenTypes#INDEX_OP INDEX_OP} node.
433     * @return previous node by text order.
434     */
435    private static DetailAST getIndexOpPreviousElement(DetailAST ast) {
436        final DetailAST result;
437        final DetailAST firstChild = ast.getFirstChild();
438        if (firstChild.getType() == TokenTypes.INDEX_OP) {
439            // second or higher array index
440            result = firstChild.findFirstToken(TokenTypes.RBRACK);
441        }
442        else if (firstChild.getType() == TokenTypes.IDENT) {
443            result = firstChild;
444        }
445        else {
446            final DetailAST ident = getIdentLastToken(ast);
447            if (ident == null) {
448                final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
449                // construction like new int[]{1}[0]
450                if (rparen == null) {
451                    final DetailAST lastChild = firstChild.getLastChild();
452                    result = lastChild.findFirstToken(TokenTypes.RCURLY);
453                }
454                // construction like ((byte[]) pixels)[0]
455                else {
456                    result = rparen;
457                }
458            }
459            else {
460                result = ident;
461            }
462        }
463        return result;
464    }
465
466    /**
467     * Searches parameter node for a type node.
468     * Returns it or its last node if it has an extended structure.
469     *
470     * @param ast
471     *        , subject node.
472     * @return type node.
473     */
474    private static DetailAST getTypeLastNode(DetailAST ast) {
475        final DetailAST typeLastNode;
476        final DetailAST parent = ast.getParent();
477        final boolean isPrecededByTypeArgs =
478                parent.findFirstToken(TokenTypes.TYPE_ARGUMENTS) != null;
479        final Optional<DetailAST> objectArrayType = Optional.ofNullable(getIdentLastToken(ast));
480
481        if (isPrecededByTypeArgs) {
482            typeLastNode = parent.findFirstToken(TokenTypes.TYPE_ARGUMENTS)
483                    .findFirstToken(TokenTypes.GENERIC_END);
484        }
485        else if (objectArrayType.isPresent()) {
486            typeLastNode = objectArrayType.get();
487        }
488        else {
489            typeLastNode = parent.getFirstChild();
490        }
491
492        return typeLastNode;
493    }
494
495    /**
496     * Finds previous node by text order for an array declarator,
497     * which parent type is {@link TokenTypes#TYPE TYPE}.
498     *
499     * @param ast
500     *        , array declarator node.
501     * @param parent
502     *        , its parent node.
503     * @return previous node by text order.
504     */
505    private static DetailAST getPreviousNodeWithParentOfTypeAst(DetailAST ast, DetailAST parent) {
506        final DetailAST previousElement;
507        final DetailAST ident = getIdentLastToken(parent.getParent());
508        final DetailAST lastTypeNode = getTypeLastNode(ast);
509        final boolean isTypeCast = parent.getParent().getType() == TokenTypes.TYPECAST;
510        // sometimes there are ident-less sentences
511        // i.e. "(Object[]) null", but in casual case should be
512        // checked whether ident or lastTypeNode has preceding position
513        // determining if it is java style or C style
514
515        if (ident == null || isTypeCast || ident.getLineNo() > ast.getLineNo()) {
516            previousElement = lastTypeNode;
517        }
518        else if (ident.getLineNo() < ast.getLineNo()) {
519            previousElement = ident;
520        }
521        // ident and lastTypeNode lay on one line
522        else {
523            final int instanceOfSize = 13;
524            // +2 because ast has `[]` after the ident
525            if (ident.getColumnNo() >= ast.getColumnNo() + 2
526                // +13 because ident (at most 1 character) is followed by
527                // ' instanceof ' (12 characters)
528                || lastTypeNode.getColumnNo() >= ident.getColumnNo() + instanceOfSize) {
529                previousElement = lastTypeNode;
530            }
531            else {
532                previousElement = ident;
533            }
534        }
535        return previousElement;
536    }
537
538    /**
539     * Gets leftmost token of identifier.
540     *
541     * @param ast
542     *        , token possibly possessing an identifier.
543     * @return leftmost token of identifier.
544     */
545    private static DetailAST getIdentLastToken(DetailAST ast) {
546        final DetailAST result;
547        final DetailAST dot = getPrecedingDot(ast);
548        // method call case
549        if (dot == null || ast.getFirstChild().getType() == TokenTypes.METHOD_CALL) {
550            final DetailAST methodCall = ast.findFirstToken(TokenTypes.METHOD_CALL);
551            if (methodCall == null) {
552                result = ast.findFirstToken(TokenTypes.IDENT);
553            }
554            else {
555                result = methodCall.findFirstToken(TokenTypes.RPAREN);
556            }
557        }
558        // qualified name case
559        else {
560            result = dot.getFirstChild().getNextSibling();
561        }
562        return result;
563    }
564
565    /**
566     * Gets the dot preceding a class member array index operation or class
567     * reference.
568     *
569     * @param leftBracket the ast we are checking
570     * @return dot preceding the left bracket
571     */
572    private static DetailAST getPrecedingDot(DetailAST leftBracket) {
573        final DetailAST referencedClassDot =
574                leftBracket.getParent().findFirstToken(TokenTypes.DOT);
575        final DetailAST referencedMemberDot = leftBracket.findFirstToken(TokenTypes.DOT);
576        return Optional.ofNullable(referencedMemberDot).orElse(referencedClassDot);
577
578    }
579}