001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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.blocks;
021
022import java.util.Locale;
023
024import javax.annotation.Nullable;
025
026import com.puppycrawl.tools.checkstyle.StatelessCheck;
027import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
028import com.puppycrawl.tools.checkstyle.api.DetailAST;
029import com.puppycrawl.tools.checkstyle.api.TokenTypes;
030import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
031import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
032
033/**
034 * <p>
035 * Checks for the placement of left curly braces (<code>'{'</code>) for code blocks.
036 * </p>
037 * <ul>
038 * <li>
039 * Property {@code ignoreEnums} - Allow to ignore enums when left curly brace policy is EOL.
040 * Type is {@code boolean}.
041 * Default value is {@code true}.
042 * </li>
043 * <li>
044 * Property {@code option} - Specify the policy on placement of a left curly brace
045 * (<code>'{'</code>).
046 * Type is {@code com.puppycrawl.tools.checkstyle.checks.blocks.LeftCurlyOption}.
047 * Default value is {@code eol}.
048 * </li>
049 * <li>
050 * Property {@code tokens} - tokens to check
051 * Type is {@code java.lang.String[]}.
052 * Validation type is {@code tokenSet}.
053 * Default value is:
054 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_DEF">
055 * ANNOTATION_DEF</a>,
056 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF">
057 * CLASS_DEF</a>,
058 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF">
059 * CTOR_DEF</a>,
060 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF">
061 * ENUM_CONSTANT_DEF</a>,
062 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF">
063 * ENUM_DEF</a>,
064 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF">
065 * INTERFACE_DEF</a>,
066 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAMBDA">
067 * LAMBDA</a>,
068 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CASE">
069 * LITERAL_CASE</a>,
070 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CATCH">
071 * LITERAL_CATCH</a>,
072 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DEFAULT">
073 * LITERAL_DEFAULT</a>,
074 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO">
075 * LITERAL_DO</a>,
076 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_ELSE">
077 * LITERAL_ELSE</a>,
078 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FINALLY">
079 * LITERAL_FINALLY</a>,
080 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR">
081 * LITERAL_FOR</a>,
082 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF">
083 * LITERAL_IF</a>,
084 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SWITCH">
085 * LITERAL_SWITCH</a>,
086 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SYNCHRONIZED">
087 * LITERAL_SYNCHRONIZED</a>,
088 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_TRY">
089 * LITERAL_TRY</a>,
090 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE">
091 * LITERAL_WHILE</a>,
092 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
093 * METHOD_DEF</a>,
094 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#OBJBLOCK">
095 * OBJBLOCK</a>,
096 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STATIC_INIT">
097 * STATIC_INIT</a>,
098 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF">
099 * RECORD_DEF</a>,
100 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMPACT_CTOR_DEF">
101 * COMPACT_CTOR_DEF</a>.
102 * </li>
103 * </ul>
104 * <p>
105 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
106 * </p>
107 * <p>
108 * Violation Message Keys:
109 * </p>
110 * <ul>
111 * <li>
112 * {@code line.break.after}
113 * </li>
114 * <li>
115 * {@code line.new}
116 * </li>
117 * <li>
118 * {@code line.previous}
119 * </li>
120 * </ul>
121 *
122 * @since 3.0
123 */
124@StatelessCheck
125public class LeftCurlyCheck
126    extends AbstractCheck {
127
128    /**
129     * A key is pointing to the warning message text in "messages.properties"
130     * file.
131     */
132    public static final String MSG_KEY_LINE_NEW = "line.new";
133
134    /**
135     * A key is pointing to the warning message text in "messages.properties"
136     * file.
137     */
138    public static final String MSG_KEY_LINE_PREVIOUS = "line.previous";
139
140    /**
141     * A key is pointing to the warning message text in "messages.properties"
142     * file.
143     */
144    public static final String MSG_KEY_LINE_BREAK_AFTER = "line.break.after";
145
146    /** Open curly brace literal. */
147    private static final String OPEN_CURLY_BRACE = "{";
148
149    /** Allow to ignore enums when left curly brace policy is EOL. */
150    private boolean ignoreEnums = true;
151
152    /**
153     * Specify the policy on placement of a left curly brace (<code>'{'</code>).
154     * */
155    private LeftCurlyOption option = LeftCurlyOption.EOL;
156
157    /**
158     * Setter to specify the policy on placement of a left curly brace (<code>'{'</code>).
159     *
160     * @param optionStr string to decode option from
161     * @throws IllegalArgumentException if unable to decode
162     * @since 3.0
163     */
164    public void setOption(String optionStr) {
165        option = LeftCurlyOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
166    }
167
168    /**
169     * Setter to allow to ignore enums when left curly brace policy is EOL.
170     *
171     * @param ignoreEnums check's option for ignoring enums.
172     * @since 6.9
173     */
174    public void setIgnoreEnums(boolean ignoreEnums) {
175        this.ignoreEnums = ignoreEnums;
176    }
177
178    @Override
179    public int[] getDefaultTokens() {
180        return getAcceptableTokens();
181    }
182
183    @Override
184    public int[] getAcceptableTokens() {
185        return new int[] {
186            TokenTypes.ANNOTATION_DEF,
187            TokenTypes.CLASS_DEF,
188            TokenTypes.CTOR_DEF,
189            TokenTypes.ENUM_CONSTANT_DEF,
190            TokenTypes.ENUM_DEF,
191            TokenTypes.INTERFACE_DEF,
192            TokenTypes.LAMBDA,
193            TokenTypes.LITERAL_CASE,
194            TokenTypes.LITERAL_CATCH,
195            TokenTypes.LITERAL_DEFAULT,
196            TokenTypes.LITERAL_DO,
197            TokenTypes.LITERAL_ELSE,
198            TokenTypes.LITERAL_FINALLY,
199            TokenTypes.LITERAL_FOR,
200            TokenTypes.LITERAL_IF,
201            TokenTypes.LITERAL_SWITCH,
202            TokenTypes.LITERAL_SYNCHRONIZED,
203            TokenTypes.LITERAL_TRY,
204            TokenTypes.LITERAL_WHILE,
205            TokenTypes.METHOD_DEF,
206            TokenTypes.OBJBLOCK,
207            TokenTypes.STATIC_INIT,
208            TokenTypes.RECORD_DEF,
209            TokenTypes.COMPACT_CTOR_DEF,
210        };
211    }
212
213    @Override
214    public int[] getRequiredTokens() {
215        return CommonUtil.EMPTY_INT_ARRAY;
216    }
217
218    /**
219     * Visits token.
220     *
221     * @param ast the token to process
222     * @noinspection SwitchStatementWithTooManyBranches
223     * @noinspectionreason SwitchStatementWithTooManyBranches - we cannot reduce
224     *      the number of branches in this switch statement, since many tokens
225     *      require specific methods to find the first left curly
226     */
227    @Override
228    public void visitToken(DetailAST ast) {
229        final DetailAST startToken;
230        final DetailAST brace;
231
232        switch (ast.getType()) {
233            case TokenTypes.CTOR_DEF:
234            case TokenTypes.METHOD_DEF:
235            case TokenTypes.COMPACT_CTOR_DEF:
236                startToken = skipModifierAnnotations(ast);
237                brace = ast.findFirstToken(TokenTypes.SLIST);
238                break;
239            case TokenTypes.INTERFACE_DEF:
240            case TokenTypes.CLASS_DEF:
241            case TokenTypes.ANNOTATION_DEF:
242            case TokenTypes.ENUM_DEF:
243            case TokenTypes.ENUM_CONSTANT_DEF:
244            case TokenTypes.RECORD_DEF:
245                startToken = skipModifierAnnotations(ast);
246                brace = ast.findFirstToken(TokenTypes.OBJBLOCK);
247                break;
248            case TokenTypes.LITERAL_WHILE:
249            case TokenTypes.LITERAL_CATCH:
250            case TokenTypes.LITERAL_SYNCHRONIZED:
251            case TokenTypes.LITERAL_FOR:
252            case TokenTypes.LITERAL_TRY:
253            case TokenTypes.LITERAL_FINALLY:
254            case TokenTypes.LITERAL_DO:
255            case TokenTypes.LITERAL_IF:
256            case TokenTypes.STATIC_INIT:
257            case TokenTypes.LAMBDA:
258                startToken = ast;
259                brace = ast.findFirstToken(TokenTypes.SLIST);
260                break;
261            case TokenTypes.LITERAL_ELSE:
262                startToken = ast;
263                brace = getBraceAsFirstChild(ast);
264                break;
265            case TokenTypes.LITERAL_CASE:
266            case TokenTypes.LITERAL_DEFAULT:
267                startToken = ast;
268                brace = getBraceFromSwitchMember(ast);
269                break;
270            default:
271                // ATTENTION! We have default here, but we expect case TokenTypes.METHOD_DEF,
272                // TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO only.
273                // It has been done to improve coverage to 100%. I couldn't replace it with
274                // if-else-if block because code was ugly and didn't pass pmd check.
275
276                startToken = ast;
277                brace = ast.findFirstToken(TokenTypes.LCURLY);
278                break;
279        }
280
281        if (brace != null) {
282            verifyBrace(brace, startToken);
283        }
284    }
285
286    /**
287     * Gets the brace of a switch statement/ expression member.
288     *
289     * @param ast {@code DetailAST}.
290     * @return {@code DetailAST} if the first child is {@code TokenTypes.SLIST},
291     *     {@code null} otherwise.
292     */
293    @Nullable
294    private static DetailAST getBraceFromSwitchMember(DetailAST ast) {
295        final DetailAST brace;
296        final DetailAST parent = ast.getParent();
297        if (parent.getType() == TokenTypes.SWITCH_RULE) {
298            brace = parent.findFirstToken(TokenTypes.SLIST);
299        }
300        else {
301            brace = getBraceAsFirstChild(ast.getNextSibling());
302        }
303        return brace;
304    }
305
306    /**
307     * Gets a SLIST if it is the first child of the AST.
308     *
309     * @param ast {@code DetailAST}.
310     * @return {@code DetailAST} if the first child is {@code TokenTypes.SLIST},
311     *     {@code null} otherwise.
312     */
313    @Nullable
314    private static DetailAST getBraceAsFirstChild(DetailAST ast) {
315        DetailAST brace = null;
316        if (ast != null) {
317            final DetailAST candidate = ast.getFirstChild();
318            if (candidate != null && candidate.getType() == TokenTypes.SLIST) {
319                brace = candidate;
320            }
321        }
322        return brace;
323    }
324
325    /**
326     * Skip all {@code TokenTypes.ANNOTATION}s to the first non-annotation.
327     *
328     * @param ast {@code DetailAST}.
329     * @return {@code DetailAST}.
330     */
331    private static DetailAST skipModifierAnnotations(DetailAST ast) {
332        DetailAST resultNode = ast;
333        final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
334
335        if (modifiers != null) {
336            final DetailAST lastAnnotation = findLastAnnotation(modifiers);
337
338            if (lastAnnotation != null) {
339                if (lastAnnotation.getNextSibling() == null) {
340                    resultNode = modifiers.getNextSibling();
341                }
342                else {
343                    resultNode = lastAnnotation.getNextSibling();
344                }
345            }
346        }
347        return resultNode;
348    }
349
350    /**
351     * Find the last token of type {@code TokenTypes.ANNOTATION}
352     * under the given set of modifiers.
353     *
354     * @param modifiers {@code DetailAST}.
355     * @return {@code DetailAST} or null if there are no annotations.
356     */
357    private static DetailAST findLastAnnotation(DetailAST modifiers) {
358        DetailAST annotation = modifiers.findFirstToken(TokenTypes.ANNOTATION);
359        while (annotation != null && annotation.getNextSibling() != null
360               && annotation.getNextSibling().getType() == TokenTypes.ANNOTATION) {
361            annotation = annotation.getNextSibling();
362        }
363        return annotation;
364    }
365
366    /**
367     * Verifies that a specified left curly brace is placed correctly
368     * according to policy.
369     *
370     * @param brace token for left curly brace
371     * @param startToken token for start of expression
372     */
373    private void verifyBrace(final DetailAST brace,
374                             final DetailAST startToken) {
375        final String braceLine = getLine(brace.getLineNo() - 1);
376
377        // Check for being told to ignore, or have '{}' which is a special case
378        if (braceLine.length() <= brace.getColumnNo() + 1
379                || braceLine.charAt(brace.getColumnNo() + 1) != '}') {
380            if (option == LeftCurlyOption.NL) {
381                if (!CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
382                    log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
383                }
384            }
385            else if (option == LeftCurlyOption.EOL) {
386                validateEol(brace, braceLine);
387            }
388            else if (!TokenUtil.areOnSameLine(startToken, brace)) {
389                validateNewLinePosition(brace, startToken, braceLine);
390            }
391        }
392    }
393
394    /**
395     * Validate EOL case.
396     *
397     * @param brace brace AST
398     * @param braceLine line content
399     */
400    private void validateEol(DetailAST brace, String braceLine) {
401        if (CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
402            log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
403        }
404        if (!hasLineBreakAfter(brace)) {
405            log(brace, MSG_KEY_LINE_BREAK_AFTER, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
406        }
407    }
408
409    /**
410     * Validate token on new Line position.
411     *
412     * @param brace brace AST
413     * @param startToken start Token
414     * @param braceLine content of line with Brace
415     */
416    private void validateNewLinePosition(DetailAST brace, DetailAST startToken, String braceLine) {
417        // not on the same line
418        if (startToken.getLineNo() + 1 == brace.getLineNo()) {
419            if (CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
420                log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
421            }
422            else {
423                log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
424            }
425        }
426        else if (!CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
427            log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
428        }
429    }
430
431    /**
432     * Checks if left curly has line break after.
433     *
434     * @param leftCurly
435     *        Left curly token.
436     * @return
437     *        True, left curly has line break after.
438     */
439    private boolean hasLineBreakAfter(DetailAST leftCurly) {
440        DetailAST nextToken = null;
441        if (leftCurly.getType() == TokenTypes.SLIST) {
442            nextToken = leftCurly.getFirstChild();
443        }
444        else {
445            if (!ignoreEnums
446                    && leftCurly.getParent().getParent().getType() == TokenTypes.ENUM_DEF) {
447                nextToken = leftCurly.getNextSibling();
448            }
449        }
450        return nextToken == null
451                || nextToken.getType() == TokenTypes.RCURLY
452                || !TokenUtil.areOnSameLine(leftCurly, nextToken);
453    }
454
455}