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