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