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; 021 022import java.io.File; 023import java.util.regex.Pattern; 024 025import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 026import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.TokenTypes; 029import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 030 031/** 032 * <p> 033 * Checks that the outer type name and the file name match. 034 * For example, the class {@code Foo} must be in a file named {@code Foo.java}. 035 * </p> 036 * <p> 037 * To configure the check: 038 * </p> 039 * <pre> 040 * <module name="OuterTypeFilename"/> 041 * </pre> 042 * <p>Example of class Test in a file named Test.java</p> 043 * <pre> 044 * public class Test { // OK 045 * 046 * } 047 * </pre> 048 * <p>Example of class Foo in a file named Test.java</p> 049 * <pre> 050 * class Foo { // violation 051 * 052 * } 053 * </pre> 054 * <p>Example of interface Foo in a file named Test.java</p> 055 * <pre> 056 * interface Foo { // violation 057 * 058 * } 059 * </pre> 060 * <p>Example of enum Foo in a file named Test.java</p> 061 * <pre> 062 * enum Foo { // violation 063 * 064 * } 065 * </pre> 066 * <p>Example of record Foo in a file named Test.java</p> 067 * <pre> 068 * record Foo { // violation 069 * 070 * } 071 * </pre> 072 * <p> 073 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 074 * </p> 075 * <p> 076 * Violation Message Keys: 077 * </p> 078 * <ul> 079 * <li> 080 * {@code type.file.mismatch} 081 * </li> 082 * </ul> 083 * 084 * @since 5.3 085 */ 086@FileStatefulCheck 087public class OuterTypeFilenameCheck extends AbstractCheck { 088 089 /** 090 * A key is pointing to the warning message text in "messages.properties" 091 * file. 092 */ 093 public static final String MSG_KEY = "type.file.mismatch"; 094 095 /** Pattern matching any file extension with dot included. */ 096 private static final Pattern FILE_EXTENSION_PATTERN = Pattern.compile("\\.[^.]*$"); 097 098 /** Indicates whether the first token has been seen in the file. */ 099 private boolean seenFirstToken; 100 101 /** Current file name. */ 102 private String fileName; 103 104 /** If file has public type. */ 105 private boolean hasPublic; 106 107 /** Outer type with mismatched file name. */ 108 private DetailAST wrongType; 109 110 @Override 111 public int[] getDefaultTokens() { 112 return getRequiredTokens(); 113 } 114 115 @Override 116 public int[] getAcceptableTokens() { 117 return getRequiredTokens(); 118 } 119 120 @Override 121 public int[] getRequiredTokens() { 122 return new int[] { 123 TokenTypes.CLASS_DEF, 124 TokenTypes.INTERFACE_DEF, 125 TokenTypes.ENUM_DEF, 126 TokenTypes.ANNOTATION_DEF, 127 TokenTypes.RECORD_DEF, 128 }; 129 } 130 131 @Override 132 public void beginTree(DetailAST rootAST) { 133 fileName = getFileName(); 134 seenFirstToken = false; 135 hasPublic = false; 136 wrongType = null; 137 } 138 139 @Override 140 public void visitToken(DetailAST ast) { 141 if (seenFirstToken) { 142 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 143 if (modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null 144 && TokenUtil.isRootNode(ast.getParent())) { 145 hasPublic = true; 146 } 147 } 148 else { 149 final String outerTypeName = ast.findFirstToken(TokenTypes.IDENT).getText(); 150 151 if (!fileName.equals(outerTypeName)) { 152 wrongType = ast; 153 } 154 } 155 seenFirstToken = true; 156 } 157 158 @Override 159 public void finishTree(DetailAST rootAST) { 160 if (!hasPublic && wrongType != null) { 161 log(wrongType, MSG_KEY); 162 } 163 } 164 165 /** 166 * Get source file name. 167 * 168 * @return source file name. 169 */ 170 // suppress deprecation until https://github.com/checkstyle/checkstyle/issues/11166 171 @SuppressWarnings("deprecation") 172 private String getFileName() { 173 String name = getFileContents().getFileName(); 174 name = name.substring(name.lastIndexOf(File.separatorChar) + 1); 175 return FILE_EXTENSION_PATTERN.matcher(name).replaceAll(""); 176 } 177 178}