001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 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.coding;
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;
026
027/**
028 * <div>
029 * Checks if unnecessary semicolon is in enum definitions.
030 * Semicolon is not needed if enum body contains only enum constants.
031 * </div>
032 *
033 * @since 8.22
034 */
035@StatelessCheck
036public final class UnnecessarySemicolonInEnumerationCheck extends AbstractCheck {
037
038    /**
039     * A key is pointing to the warning message text in "messages.properties" file.
040     */
041    public static final String MSG_SEMI = "unnecessary.semicolon";
042
043    /**
044     * Creates a new {@code UnnecessarySemicolonInEnumerationCheck} instance.
045     */
046    public UnnecessarySemicolonInEnumerationCheck() {
047        // no code by default
048    }
049
050    @Override
051    public int[] getDefaultTokens() {
052        return getRequiredTokens();
053    }
054
055    @Override
056    public int[] getAcceptableTokens() {
057        return getRequiredTokens();
058    }
059
060    @Override
061    public int[] getRequiredTokens() {
062        return new int[] {
063            TokenTypes.ENUM_DEF,
064        };
065    }
066
067    @Override
068    public void visitToken(DetailAST ast) {
069        final DetailAST enumBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
070        final boolean hasMembers = hasMembersAfterConstants(enumBlock);
071        DetailAST sibling = enumBlock.getFirstChild();
072        while (sibling != null) {
073            if (sibling.getType() == TokenTypes.SEMI
074                    && (!hasMembers || isPrecededBySemi(sibling))) {
075                log(sibling, MSG_SEMI);
076            }
077            sibling = sibling.getNextSibling();
078        }
079    }
080
081    /**
082     * Checks if enum body contains any member (method, constructor, field, etc.)
083     * in addition to enum constants and semicolons.
084     *
085     * @param enumBlock the enum body to check
086     * @return true if enum body has members, false if it only has constants and semicolons.
087     */
088    private static boolean hasMembersAfterConstants(DetailAST enumBlock) {
089        boolean result = false;
090        DetailAST sibling = enumBlock.getFirstChild();
091        while (sibling != null) {
092            final int type = sibling.getType();
093            if (type != TokenTypes.LCURLY
094                    && type != TokenTypes.RCURLY
095                    && type != TokenTypes.ENUM_CONSTANT_DEF
096                    && type != TokenTypes.SEMI
097                    && type != TokenTypes.COMMA) {
098                result = true;
099                break;
100            }
101            sibling = sibling.getNextSibling();
102        }
103        return result;
104    }
105
106    /**
107     * Checks if semicolon is immediately preceded by another semicolon,
108     * making it a redundant duplicate.
109     *
110     * @param ast semicolon to check
111     * @return true if previous sibling is also a semicolon, false otherwise.
112     */
113    private static boolean isPrecededBySemi(DetailAST ast) {
114        final DetailAST prev = ast.getPreviousSibling();
115        return prev.getType() == TokenTypes.SEMI;
116    }
117
118}