View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.modifier;
21  
22  import com.puppycrawl.tools.checkstyle.StatelessCheck;
23  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
24  import com.puppycrawl.tools.checkstyle.api.DetailAST;
25  import com.puppycrawl.tools.checkstyle.api.Scope;
26  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
27  import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
28  
29  /**
30   * <p>
31   * Checks for implicit modifiers on nested types in classes and records.
32   * </p>
33   * <p>
34   * This check is effectively the opposite of
35   * <a href="https://checkstyle.org/checks/modifier/redundantmodifier.html#RedundantModifier">
36   * RedundantModifier</a>.
37   * It checks the modifiers on nested types in classes and records, ensuring that certain modifiers
38   * are explicitly specified even though they are actually redundant.
39   * </p>
40   * <p>
41   * Nested enums, interfaces, and records within a class are always {@code static} and as such the
42   * compiler does not require the {@code static} modifier. This check provides the ability to enforce
43   * that the {@code static} modifier is explicitly coded and not implicitly added by the compiler.
44   * </p>
45   * <pre>
46   * public final class Person {
47   *   enum Age {  // violation
48   *     CHILD, ADULT
49   *   }
50   * }
51   * </pre>
52   * <p>
53   * Rationale for this check: Nested enums, interfaces, and records are treated differently from
54   * nested classes as they are only allowed to be {@code static}. Developers should not need to
55   * remember this rule, and this check provides the means to enforce that the modifier is coded
56   * explicitly.
57   * </p>
58   * <ul>
59   * <li>
60   * Property {@code violateImpliedStaticOnNestedEnum} - Control whether to enforce that
61   * {@code static} is explicitly coded on nested enums in classes and records.
62   * Type is {@code boolean}.
63   * Default value is {@code true}.
64   * </li>
65   * <li>
66   * Property {@code violateImpliedStaticOnNestedInterface} - Control whether to enforce that
67   * {@code static} is explicitly coded on nested interfaces in classes and records.
68   * Type is {@code boolean}.
69   * Default value is {@code true}.
70   * </li>
71   * <li>
72   * Property {@code violateImpliedStaticOnNestedRecord} - Control whether to enforce that
73   * {@code static} is explicitly coded on nested records in classes and records.
74   * Type is {@code boolean}.
75   * Default value is {@code true}.
76   * </li>
77   * </ul>
78   * <p>
79   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
80   * </p>
81   * <p>
82   * Violation Message Keys:
83   * </p>
84   * <ul>
85   * <li>
86   * {@code class.implied.modifier}
87   * </li>
88   * </ul>
89   *
90   * @since 8.16
91   */
92  @StatelessCheck
93  public class ClassMemberImpliedModifierCheck
94      extends AbstractCheck {
95  
96      /**
97       * A key is pointing to the warning message text in "messages.properties" file.
98       */
99      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 }