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.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     * Creates a new {@code ClassMemberImpliedModifierCheck} instance.
096     */
097    public ClassMemberImpliedModifierCheck() {
098        // no code by default
099    }
100
101    /**
102     * Setter to control whether to enforce that {@code static} is explicitly coded
103     * on nested enums in classes and records.
104     *
105     * @param violateImplied
106     *        True to perform the check, false to turn the check off.
107     * @since 8.16
108     */
109    public void setViolateImpliedStaticOnNestedEnum(boolean violateImplied) {
110        violateImpliedStaticOnNestedEnum = violateImplied;
111    }
112
113    /**
114     * Setter to control whether to enforce that {@code static} is explicitly coded
115     * on nested interfaces in classes and records.
116     *
117     * @param violateImplied
118     *        True to perform the check, false to turn the check off.
119     * @since 8.16
120     */
121    public void setViolateImpliedStaticOnNestedInterface(boolean violateImplied) {
122        violateImpliedStaticOnNestedInterface = violateImplied;
123    }
124
125    /**
126     * Setter to control whether to enforce that {@code static} is explicitly coded
127     * on nested records in classes and records.
128     *
129     * @param violateImplied
130     *        True to perform the check, false to turn the check off.
131     * @since 8.36
132     */
133    public void setViolateImpliedStaticOnNestedRecord(boolean violateImplied) {
134        violateImpliedStaticOnNestedRecord = violateImplied;
135    }
136
137    @Override
138    public int[] getDefaultTokens() {
139        return getAcceptableTokens();
140    }
141
142    @Override
143    public int[] getRequiredTokens() {
144        return getAcceptableTokens();
145    }
146
147    @Override
148    public int[] getAcceptableTokens() {
149        return new int[] {
150            TokenTypes.INTERFACE_DEF,
151            TokenTypes.ENUM_DEF,
152            TokenTypes.RECORD_DEF,
153        };
154    }
155
156    @Override
157    public void visitToken(DetailAST ast) {
158        if (isInTypeBlock(ast)) {
159            final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
160            switch (ast.getType()) {
161                case TokenTypes.ENUM_DEF -> {
162                    if (violateImpliedStaticOnNestedEnum
163                            && modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) == null) {
164                        log(ast, MSG_KEY, STATIC_KEYWORD);
165                    }
166                }
167
168                case TokenTypes.INTERFACE_DEF -> {
169                    if (violateImpliedStaticOnNestedInterface
170                            && modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) == null) {
171                        log(ast, MSG_KEY, STATIC_KEYWORD);
172                    }
173                }
174
175                case TokenTypes.RECORD_DEF -> {
176                    if (violateImpliedStaticOnNestedRecord
177                            && modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) == null) {
178                        log(ast, MSG_KEY, STATIC_KEYWORD);
179                    }
180                }
181
182                default -> throw new IllegalStateException(ast.toString());
183            }
184        }
185    }
186
187    /**
188     * Checks if ast is in a class, enum, anon class or record block.
189     *
190     * @param ast the current ast
191     * @return true if ast is in a class, enum, anon class or record
192     */
193    private static boolean isInTypeBlock(DetailAST ast) {
194        return ScopeUtil.isInScope(ast, Scope.ANONINNER)
195                || ScopeUtil.isInClassBlock(ast)
196                || ScopeUtil.isInEnumBlock(ast)
197                || ScopeUtil.isInRecordBlock(ast);
198    }
199
200}