001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 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.modifier;
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.Scope;
026import com.puppycrawl.tools.checkstyle.api.TokenTypes;
027import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
028
029/**
030 * <div>
031 * Checks for implicit modifiers on nested types in classes and records.
032 * </div>
033 *
034 * <p>
035 * This check is effectively the opposite of
036 * <a href="https://checkstyle.org/checks/modifier/redundantmodifier.html">
037 * RedundantModifier</a>.
038 * It checks the modifiers on nested types in classes and records, ensuring that certain modifiers
039 * are explicitly specified even though they are actually redundant.
040 * </p>
041 *
042 * <p>
043 * Nested enums, interfaces, and records within a class are always {@code static} and as such the
044 * compiler does not require the {@code static} modifier. This check provides the ability to enforce
045 * that the {@code static} modifier is explicitly coded and not implicitly added by the compiler.
046 * </p>
047 * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
048 * public final class Person {
049 *   enum Age {  // violation
050 *     CHILD, ADULT
051 *   }
052 * }
053 * </code></pre></div>
054 *
055 * <p>
056 * Rationale for this check: Nested enums, interfaces, and records are treated differently from
057 * nested classes as they are only allowed to be {@code static}. Developers should not need to
058 * remember this rule, and this check provides the means to enforce that the modifier is coded
059 * explicitly.
060 * </p>
061 *
062 * @since 8.16
063 */
064@StatelessCheck
065public class ClassMemberImpliedModifierCheck
066    extends AbstractCheck {
067
068    /**
069     * A key is pointing to the warning message text in "messages.properties" file.
070     */
071    public static final String MSG_KEY = "class.implied.modifier";
072
073    /** Name for 'static' keyword. */
074    private static final String STATIC_KEYWORD = "static";
075
076    /**
077     * Control whether to enforce that {@code static} is explicitly coded
078     * on nested enums in classes and records.
079     */
080    private boolean violateImpliedStaticOnNestedEnum = true;
081
082    /**
083     * Control whether to enforce that {@code static} is explicitly coded
084     * on nested interfaces in classes and records.
085     */
086    private boolean violateImpliedStaticOnNestedInterface = true;
087
088    /**
089     * Control whether to enforce that {@code static} is explicitly coded
090     * on nested records in classes and records.
091     */
092    private boolean violateImpliedStaticOnNestedRecord = true;
093
094    /**
095     * Setter to control whether to enforce that {@code static} is explicitly coded
096     * on nested enums in classes and records.
097     *
098     * @param violateImplied
099     *        True to perform the check, false to turn the check off.
100     * @since 8.16
101     */
102    public void setViolateImpliedStaticOnNestedEnum(boolean violateImplied) {
103        violateImpliedStaticOnNestedEnum = violateImplied;
104    }
105
106    /**
107     * Setter to control whether to enforce that {@code static} is explicitly coded
108     * on nested interfaces in classes and records.
109     *
110     * @param violateImplied
111     *        True to perform the check, false to turn the check off.
112     * @since 8.16
113     */
114    public void setViolateImpliedStaticOnNestedInterface(boolean violateImplied) {
115        violateImpliedStaticOnNestedInterface = violateImplied;
116    }
117
118    /**
119     * Setter to control whether to enforce that {@code static} is explicitly coded
120     * on nested records in classes and records.
121     *
122     * @param violateImplied
123     *        True to perform the check, false to turn the check off.
124     * @since 8.36
125     */
126    public void setViolateImpliedStaticOnNestedRecord(boolean violateImplied) {
127        violateImpliedStaticOnNestedRecord = violateImplied;
128    }
129
130    @Override
131    public int[] getDefaultTokens() {
132        return getAcceptableTokens();
133    }
134
135    @Override
136    public int[] getRequiredTokens() {
137        return getAcceptableTokens();
138    }
139
140    @Override
141    public int[] getAcceptableTokens() {
142        return new int[] {
143            TokenTypes.INTERFACE_DEF,
144            TokenTypes.ENUM_DEF,
145            TokenTypes.RECORD_DEF,
146        };
147    }
148
149    @Override
150    public void visitToken(DetailAST ast) {
151        if (isInTypeBlock(ast)) {
152            final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
153            switch (ast.getType()) {
154                case TokenTypes.ENUM_DEF -> {
155                    if (violateImpliedStaticOnNestedEnum
156                            && modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) == null) {
157                        log(ast, MSG_KEY, STATIC_KEYWORD);
158                    }
159                }
160
161                case TokenTypes.INTERFACE_DEF -> {
162                    if (violateImpliedStaticOnNestedInterface
163                            && modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) == null) {
164                        log(ast, MSG_KEY, STATIC_KEYWORD);
165                    }
166                }
167
168                case TokenTypes.RECORD_DEF -> {
169                    if (violateImpliedStaticOnNestedRecord
170                            && modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) == null) {
171                        log(ast, MSG_KEY, STATIC_KEYWORD);
172                    }
173                }
174
175                default -> throw new IllegalStateException(ast.toString());
176            }
177        }
178    }
179
180    /**
181     * Checks if ast is in a class, enum, anon class or record block.
182     *
183     * @param ast the current ast
184     * @return true if ast is in a class, enum, anon class or record
185     */
186    private static boolean isInTypeBlock(DetailAST ast) {
187        return ScopeUtil.isInScope(ast, Scope.ANONINNER)
188                || ScopeUtil.isInClassBlock(ast)
189                || ScopeUtil.isInEnumBlock(ast)
190                || ScopeUtil.isInRecordBlock(ast);
191    }
192
193}