001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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 * <module name="ClassMemberImpliedModifier" /> 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}