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