001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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.coding; 021 022import java.util.ArrayList; 023import java.util.HashSet; 024import java.util.List; 025import java.util.Set; 026 027import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 028import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 029import com.puppycrawl.tools.checkstyle.api.DetailAST; 030import com.puppycrawl.tools.checkstyle.api.TokenTypes; 031import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 032 033/** 034 * <div> 035 * Checks that a {@code permits} clause of a sealed class or interface is not 036 * unnecessary, i.e. that it is not possible to omit the clause and have the 037 * compiler infer the exact same set of permitted subtypes. 038 * </div> 039 * 040 * <p> 041 * See the <a href="https://docs.oracle.com/javase/specs/jls/se22/html/jls-13.html#jls-13.4.2"> 042 * Java Language Specification</a> for more information about sealed classes. 043 * </p> 044 * 045 * <p> 046 * This Check does not perform full type resolution. It determines whether a 047 * permitted type is local to the file by comparing simple names against every 048 * type declaration (class, interface, enum, or record) found 049 * anywhere in the compilation unit, including nested and sibling top-level 050 * types. As a result, a permitted type whose simple name coincidentally 051 * matches an unrelated local type declaration could, in theory, be 052 * misidentified as local. In practice this situation cannot occur in code 053 * that compiles, since the Java compiler would not be able to resolve such an 054 * ambiguous reference in the {@code permits} clause. 055 * </p> 056 * 057 * @since 14.2.0 058 */ 059@FileStatefulCheck 060public class UnnecessaryPermitsClauseCheck extends AbstractCheck { 061 062 /** 063 * A key is pointing to the warning message text in "messages.properties" file. 064 */ 065 public static final String MSG_KEY = "unnecessary.permits.clause"; 066 067 /** 068 * A set of simple names of every type declared in the current compilation 069 * unit, including nested types. 070 */ 071 private final Set<String> localTypeNames = new HashSet<>(); 072 073 /** 074 * A list of {@link TokenTypes#PERMITS_CLAUSE} nodes found in the current compilation unit. 075 */ 076 private final List<DetailAST> permitsClauseAstList = new ArrayList<>(); 077 078 /** 079 * Creates a new {@code UnnecessaryPermitsClauseCheck} instance. 080 */ 081 public UnnecessaryPermitsClauseCheck() { 082 // no code by default 083 } 084 085 @Override 086 public int[] getDefaultTokens() { 087 return getRequiredTokens(); 088 } 089 090 @Override 091 public int[] getAcceptableTokens() { 092 return getRequiredTokens(); 093 } 094 095 @Override 096 public int[] getRequiredTokens() { 097 return new int[] { 098 TokenTypes.CLASS_DEF, 099 TokenTypes.INTERFACE_DEF, 100 TokenTypes.ENUM_DEF, 101 TokenTypes.RECORD_DEF, 102 TokenTypes.PERMITS_CLAUSE, 103 }; 104 } 105 106 @Override 107 public void beginTree(DetailAST rootAST) { 108 localTypeNames.clear(); 109 permitsClauseAstList.clear(); 110 } 111 112 @Override 113 public void visitToken(DetailAST ast) { 114 switch (ast.getType()) { 115 case TokenTypes.CLASS_DEF, 116 TokenTypes.INTERFACE_DEF, 117 TokenTypes.ENUM_DEF, 118 TokenTypes.RECORD_DEF -> { 119 final DetailAST nameAst = ast.findFirstToken(TokenTypes.IDENT); 120 localTypeNames.add(nameAst.getText()); 121 } 122 default -> permitsClauseAstList.add(ast); 123 } 124 } 125 126 @Override 127 public void finishTree(DetailAST rootAST) { 128 for (DetailAST permitsClause : permitsClauseAstList) { 129 if (isUnnecessary(permitsClause)) { 130 log(permitsClause, MSG_KEY); 131 } 132 } 133 } 134 135 /** 136 * Determines whether every type named in the given {@code permits} clause 137 * is declared somewhere within the same compilation unit, and is therefore redundant. 138 * 139 * @param permitsClause the {@link TokenTypes#PERMITS_CLAUSE} node to inspect 140 * @return {@code true} if the clause is unnecessary 141 */ 142 private boolean isUnnecessary(DetailAST permitsClause) { 143 boolean result = true; 144 DetailAST permittedType = permitsClause.getFirstChild(); 145 while (permittedType != null) { 146 if (isTypeName(permittedType)) { 147 final String simpleName = getSimpleName(permittedType); 148 if (!localTypeNames.contains(simpleName)) { 149 result = false; 150 break; 151 } 152 } 153 permittedType = permittedType.getNextSibling(); 154 } 155 return result; 156 } 157 158 /** 159 * Checks whether the given direct child of a {@code permits} clause 160 * represents a permitted type name, as opposed to a separating comma. 161 * 162 * @param ast the node to check 163 * @return {@code true} if the node is a (possibly qualified) type name 164 */ 165 private static boolean isTypeName(DetailAST ast) { 166 return TokenUtil.isOfType(ast, TokenTypes.IDENT, TokenTypes.DOT); 167 } 168 169 /** 170 * Extracts the simple (unqualified) name from a type name node, which is 171 * either a single {@link TokenTypes#IDENT} or a {@link TokenTypes#DOT} 172 * chain representing a qualified name. 173 * 174 * @param typeName the type name node 175 * @return the simple name of the type 176 */ 177 private static String getSimpleName(DetailAST typeName) { 178 final String simpleName; 179 if (typeName.getType() == TokenTypes.DOT) { 180 simpleName = typeName.getLastChild().getText(); 181 } 182 else { 183 simpleName = typeName.getText(); 184 } 185 return simpleName; 186 } 187 188}