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; 021 022import java.io.File; 023import java.io.IOException; 024import java.io.RandomAccessFile; 025import java.util.Locale; 026 027import com.puppycrawl.tools.checkstyle.StatelessCheck; 028import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck; 029import com.puppycrawl.tools.checkstyle.api.FileText; 030 031/** 032 * <p> 033 * Checks whether files end with a line separator. 034 * </p> 035 * <p> 036 * Rationale: Any source files and text files in general should end with a line 037 * separator to let other easily add new content at the end of file and "diff" 038 * command does not show previous lines as changed. 039 * </p> 040 * <p> 041 * Example (line 36 should not be in diff): 042 * </p> 043 * <pre> 044 * @@ -32,4 +32,5 @@ ForbidWildcardAsReturnTypeCheck.returnTypeClassNamesIgnoreRegex 045 * PublicReferenceToPrivateTypeCheck.name = Public Reference To Private Type 046 * 047 * StaticMethodCandidateCheck.name = Static Method Candidate 048 * -StaticMethodCandidateCheck.desc = Checks whether private methods should be declared as static. 049 * \ No newline at end of file 050 * +StaticMethodCandidateCheck.desc = Checks whether private methods should be declared as static. 051 * +StaticMethodCandidateCheck.skippedMethods = Method names to skip during the check. 052 * </pre> 053 * <p> 054 * It can also trick the VCS to report the wrong owner for such lines. 055 * An engineer who has added nothing but a newline character becomes the last 056 * known author for the entire line. As a result, a mate can ask him a question 057 * to which he will not give the correct answer. 058 * </p> 059 * <p> 060 * Old Rationale: CVS source control management systems will even print 061 * a warning when it encounters a file that doesn't end with a line separator. 062 * </p> 063 * <p> 064 * Attention: property fileExtensions works with files that are passed by similar 065 * property for at <a href="https://checkstyle.org/config.html#Checker">Checker</a>. 066 * Please make sure required file extensions are mentioned at Checker's fileExtensions property. 067 * </p> 068 * <p> 069 * This will check against the platform-specific default line separator. 070 * </p> 071 * <p> 072 * It is also possible to enforce the use of a specific line-separator across 073 * platforms, with the {@code lineSeparator} property. 074 * </p> 075 * <ul> 076 * <li> 077 * Property {@code lineSeparator} - Specify the type of line separator. 078 * Type is {@code com.puppycrawl.tools.checkstyle.checks.LineSeparatorOption}. 079 * Default value is {@code lf_cr_crlf}. 080 * </li> 081 * <li> 082 * Property {@code fileExtensions} - Specify the file type extension of the files to check. 083 * Type is {@code java.lang.String[]}. 084 * Default value is {@code all files}. 085 * </li> 086 * </ul> 087 * <p> 088 * To configure the check: 089 * </p> 090 * <pre> 091 * <module name="NewlineAtEndOfFile"/> 092 * </pre> 093 * <p>Example:</p> 094 * <pre> 095 * // File ending with a new line 096 * public class Test {⤶ 097 * ⤶ 098 * }⤶ // ok 099 * Note: The comment // ok is a virtual, not actually present in the file 100 * 101 * // File ending without a new line 102 * public class Test1 {⤶ 103 * ⤶ 104 * } // violation, the file does not end with a new line 105 * </pre> 106 * <p> 107 * To configure the check to always use Unix-style line separators: 108 * </p> 109 * <pre> 110 * <module name="NewlineAtEndOfFile"> 111 * <property name="lineSeparator" value="lf"/> 112 * </module> 113 * </pre> 114 * <p>Example:</p> 115 * <pre> 116 * // File ending with a new line 117 * public class Test {⤶ 118 * ⤶ 119 * }⤶ // ok 120 * Note: The comment // ok is a virtual, not actually present in the file 121 * 122 * // File ending without a new line 123 * public class Test1 {⤶ 124 * ⤶ 125 * }␍⤶ // violation, expected line ending for file is LF(\n), but CRLF(\r\n) is detected 126 * </pre> 127 * <p> 128 * To configure the check to work only on Java, XML and Python files: 129 * </p> 130 * <pre> 131 * <module name="NewlineAtEndOfFile"> 132 * <property name="fileExtensions" value="java, xml, py"/> 133 * </module> 134 * </pre> 135 * <p>Example:</p> 136 * <pre> 137 * // Any java file 138 * public class Test {⤶ 139 * } // violation, file should end with a new line. 140 * 141 * // Any py file 142 * print("Hello World") // violation, file should end with a new line. 143 * 144 * // Any txt file 145 * This is a sample text file. // ok, this file is not specified in the config. 146 * </pre> 147 * <p> 148 * Parent is {@code com.puppycrawl.tools.checkstyle.Checker} 149 * </p> 150 * <p> 151 * Violation Message Keys: 152 * </p> 153 * <ul> 154 * <li> 155 * {@code noNewlineAtEOF} 156 * </li> 157 * <li> 158 * {@code unable.open} 159 * </li> 160 * <li> 161 * {@code wrong.line.end} 162 * </li> 163 * </ul> 164 * 165 * @since 3.1 166 */ 167@StatelessCheck 168public class NewlineAtEndOfFileCheck 169 extends AbstractFileSetCheck { 170 171 /** 172 * A key is pointing to the warning message text in "messages.properties" 173 * file. 174 */ 175 public static final String MSG_KEY_UNABLE_OPEN = "unable.open"; 176 177 /** 178 * A key is pointing to the warning message text in "messages.properties" 179 * file. 180 */ 181 public static final String MSG_KEY_NO_NEWLINE_EOF = "noNewlineAtEOF"; 182 183 /** 184 * A key is pointing to the warning message text in "messages.properties" 185 * file. 186 */ 187 public static final String MSG_KEY_WRONG_ENDING = "wrong.line.end"; 188 189 /** Specify the type of line separator. */ 190 private LineSeparatorOption lineSeparator = LineSeparatorOption.LF_CR_CRLF; 191 192 @Override 193 protected void processFiltered(File file, FileText fileText) { 194 try { 195 readAndCheckFile(file); 196 } 197 catch (final IOException ignored) { 198 log(1, MSG_KEY_UNABLE_OPEN, file.getPath()); 199 } 200 } 201 202 /** 203 * Setter to specify the type of line separator. 204 * 205 * @param lineSeparatorParam The line separator to set 206 * @throws IllegalArgumentException If the specified line separator is not 207 * one of 'crlf', 'lf', 'cr', 'lf_cr_crlf' or 'system' 208 */ 209 public void setLineSeparator(String lineSeparatorParam) { 210 lineSeparator = 211 Enum.valueOf(LineSeparatorOption.class, lineSeparatorParam.trim() 212 .toUpperCase(Locale.ENGLISH)); 213 } 214 215 /** 216 * Reads the file provided and checks line separators. 217 * 218 * @param file the file to be processed 219 * @throws IOException When an IO error occurred while reading from the 220 * file provided 221 */ 222 private void readAndCheckFile(File file) throws IOException { 223 // Cannot use lines as the line separators have been removed! 224 try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) { 225 if (lineSeparator == LineSeparatorOption.LF 226 && endsWithNewline(randomAccessFile, LineSeparatorOption.CRLF)) { 227 log(1, MSG_KEY_WRONG_ENDING, file.getPath()); 228 } 229 else if (!endsWithNewline(randomAccessFile, lineSeparator)) { 230 log(1, MSG_KEY_NO_NEWLINE_EOF, file.getPath()); 231 } 232 } 233 } 234 235 /** 236 * Checks whether the content provided by the Reader ends with the platform 237 * specific line separator. 238 * 239 * @param file The reader for the content to check 240 * @param separator The line separator 241 * @return boolean Whether the content ends with a line separator 242 * @throws IOException When an IO error occurred while reading from the 243 * provided reader 244 */ 245 private static boolean endsWithNewline(RandomAccessFile file, LineSeparatorOption separator) 246 throws IOException { 247 final boolean result; 248 final int len = separator.length(); 249 if (file.length() < len) { 250 result = false; 251 } 252 else { 253 file.seek(file.length() - len); 254 final byte[] lastBytes = new byte[len]; 255 final int readBytes = file.read(lastBytes); 256 if (readBytes != len) { 257 throw new IOException("Unable to read " + len + " bytes, got " 258 + readBytes); 259 } 260 result = separator.matches(lastBytes); 261 } 262 return result; 263 } 264 265}