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.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#RedundantModifier">
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 * <pre>
048 * public final class Person {
049 *   enum Age {  // violation
050 *     CHILD, ADULT
051 *   }
052 * }
053 * </pre>
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 * <ul>
062 * <li>
063 * Property {@code violateImpliedStaticOnNestedEnum} - Control whether to enforce that
064 * {@code static} is explicitly coded on nested enums in classes and records.
065 * Type is {@code boolean}.
066 * Default value is {@code true}.
067 * </li>
068 * <li>
069 * Property {@code violateImpliedStaticOnNestedInterface} - Control whether to enforce that
070 * {@code static} is explicitly coded on nested interfaces in classes and records.
071 * Type is {@code boolean}.
072 * Default value is {@code true}.
073 * </li>
074 * <li>
075 * Property {@code violateImpliedStaticOnNestedRecord} - Control whether to enforce that
076 * {@code static} is explicitly coded on nested records in classes and records.
077 * Type is {@code boolean}.
078 * Default value is {@code true}.
079 * </li>
080 * </ul>
081 *
082 * <p>
083 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
084 * </p>
085 *
086 * <p>
087 * Violation Message Keys:
088 * </p>
089 * <ul>
090 * <li>
091 * {@code class.implied.modifier}
092 * </li>
093 * </ul>
094 *
095 * @since 8.16
096 */
097@StatelessCheck
098public class ClassMemberImpliedModifierCheck
099    extends AbstractCheck {
100
101    /**
102     * A key is pointing to the warning message text in "messages.properties" file.
103     */
104    public static final String MSG_KEY = "class.implied.modifier";
105
106    /** Name for 'static' keyword. */
107    private static final String STATIC_KEYWORD = "static";
108
109    /**
110     * Control whether to enforce that {@code static} is explicitly coded
111     * on nested enums in classes and records.
112     */
113    private boolean violateImpliedStaticOnNestedEnum = true;
114
115    /**
116     * Control whether to enforce that {@code static} is explicitly coded
117     * on nested interfaces in classes and records.
118     */
119    private boolean violateImpliedStaticOnNestedInterface = true;
120
121    /**
122     * Control whether to enforce that {@code static} is explicitly coded
123     * on nested records in classes and records.
124     */
125    private boolean violateImpliedStaticOnNestedRecord = true;
126
127    /**
128     * Setter to control whether to enforce that {@code static} is explicitly coded
129     * on nested enums in classes and records.
130     *
131     * @param violateImplied
132     *        True to perform the check, false to turn the check off.
133     * @since 8.16
134     */
135    public void setViolateImpliedStaticOnNestedEnum(boolean violateImplied) {
136        violateImpliedStaticOnNestedEnum = violateImplied;
137    }
138
139    /**
140     * Setter to control whether to enforce that {@code static} is explicitly coded
141     * on nested interfaces in classes and records.
142     *
143     * @param violateImplied
144     *        True to perform the check, false to turn the check off.
145     * @since 8.16
146     */
147    public void setViolateImpliedStaticOnNestedInterface(boolean violateImplied) {
148        violateImpliedStaticOnNestedInterface = violateImplied;
149    }
150
151    /**
152     * Setter to control whether to enforce that {@code static} is explicitly coded
153     * on nested records in classes and records.
154     *
155     * @param violateImplied
156     *        True to perform the check, false to turn the check off.
157     * @since 8.36
158     */
159    public void setViolateImpliedStaticOnNestedRecord(boolean violateImplied) {
160        violateImpliedStaticOnNestedRecord = violateImplied;
161    }
162
163    @Override
164    public int[] getDefaultTokens() {
165        return getAcceptableTokens();
166    }
167
168    @Override
169    public int[] getRequiredTokens() {
170        return getAcceptableTokens();
171    }
172
173    @Override
174    public int[] getAcceptableTokens() {
175        return new int[] {
176            TokenTypes.INTERFACE_DEF,
177            TokenTypes.ENUM_DEF,
178            TokenTypes.RECORD_DEF,
179        };
180    }
181
182    @Override
183    public void visitToken(DetailAST ast) {
184        if (isInTypeBlock(ast)) {
185            final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
186            switch (ast.getType()) {
187                case TokenTypes.ENUM_DEF:
188                    if (violateImpliedStaticOnNestedEnum
189                            && modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) == null) {
190                        log(ast, MSG_KEY, STATIC_KEYWORD);
191                    }
192                    break;
193                case TokenTypes.INTERFACE_DEF:
194                    if (violateImpliedStaticOnNestedInterface
195                            && modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) == null) {
196                        log(ast, MSG_KEY, STATIC_KEYWORD);
197                    }
198                    break;
199                case TokenTypes.RECORD_DEF:
200                    if (violateImpliedStaticOnNestedRecord
201                            && modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) == null) {
202                        log(ast, MSG_KEY, STATIC_KEYWORD);
203                    }
204                    break;
205                default:
206                    throw new IllegalStateException(ast.toString());
207            }
208        }
209    }
210
211    /**
212     * Checks if ast is in a class, enum, anon class or record block.
213     *
214     * @param ast the current ast
215     * @return true if ast is in a class, enum, anon class or record
216     */
217    private static boolean isInTypeBlock(DetailAST ast) {
218        return ScopeUtil.isInScope(ast, Scope.ANONINNER)
219                || ScopeUtil.isInClassBlock(ast)
220                || ScopeUtil.isInEnumBlock(ast)
221                || ScopeUtil.isInRecordBlock(ast);
222    }
223
224}