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 * Default value is {@code lf_cr_crlf}. 079 * </li> 080 * <li> 081 * Property {@code fileExtensions} - Specify the file type extension of the files to check. 082 * Default value is {@code all files}. 083 * </li> 084 * </ul> 085 * <p> 086 * To configure the check: 087 * </p> 088 * <pre> 089 * <module name="NewlineAtEndOfFile"/> 090 * </pre> 091 * <p>Example:</p> 092 * <pre> 093 * // File ending with a new line 094 * public class Test {⤶ 095 * ⤶ 096 * }⤶ // ok 097 * Note: The comment // ok is a virtual, not actually present in the file 098 * 099 * // File ending without a new line 100 * public class Test1 {⤶ 101 * ⤶ 102 * } // violation, the file does not end with a new line 103 * </pre> 104 * <p> 105 * To configure the check to always use Unix-style line separators: 106 * </p> 107 * <pre> 108 * <module name="NewlineAtEndOfFile"> 109 * <property name="lineSeparator" value="lf"/> 110 * </module> 111 * </pre> 112 * <p>Example:</p> 113 * <pre> 114 * // File ending with a new line 115 * public class Test {⤶ 116 * ⤶ 117 * }⤶ // ok 118 * Note: The comment // ok is a virtual, not actually present in the file 119 * 120 * // File ending without a new line 121 * public class Test1 {⤶ 122 * ⤶ 123 * }␍⤶ // violation, expected line ending for file is LF(\n), but CRLF(\r\n) is detected 124 * </pre> 125 * <p> 126 * To configure the check to work only on Java, XML and Python files: 127 * </p> 128 * <pre> 129 * <module name="NewlineAtEndOfFile"> 130 * <property name="fileExtensions" value="java, xml, py"/> 131 * </module> 132 * </pre> 133 * <p>Example:</p> 134 * <pre> 135 * // Any java file 136 * public class Test {⤶ 137 * } // violation, file should end with a new line. 138 * 139 * // Any py file 140 * print("Hello World") // violation, file should end with a new line. 141 * 142 * // Any txt file 143 * This is a sample text file. // ok, this file is not specified in the config. 144 * </pre> 145 * 146 * @since 3.1 147 */ 148@StatelessCheck 149public class NewlineAtEndOfFileCheck 150 extends AbstractFileSetCheck { 151 152 /** 153 * A key is pointing to the warning message text in "messages.properties" 154 * file. 155 */ 156 public static final String MSG_KEY_UNABLE_OPEN = "unable.open"; 157 158 /** 159 * A key is pointing to the warning message text in "messages.properties" 160 * file. 161 */ 162 public static final String MSG_KEY_NO_NEWLINE_EOF = "noNewlineAtEOF"; 163 164 /** 165 * A key is pointing to the warning message text in "messages.properties" 166 * file. 167 */ 168 public static final String MSG_KEY_WRONG_ENDING = "wrong.line.end"; 169 170 /** Specify the type of line separator. */ 171 private LineSeparatorOption lineSeparator = LineSeparatorOption.LF_CR_CRLF; 172 173 @Override 174 protected void processFiltered(File file, FileText fileText) { 175 try { 176 readAndCheckFile(file); 177 } 178 catch (final IOException ignored) { 179 log(1, MSG_KEY_UNABLE_OPEN, file.getPath()); 180 } 181 } 182 183 /** 184 * Setter to specify the type of line separator. 185 * 186 * @param lineSeparatorParam The line separator to set 187 * @throws IllegalArgumentException If the specified line separator is not 188 * one of 'crlf', 'lf', 'cr', 'lf_cr_crlf' or 'system' 189 */ 190 public void setLineSeparator(String lineSeparatorParam) { 191 lineSeparator = 192 Enum.valueOf(LineSeparatorOption.class, lineSeparatorParam.trim() 193 .toUpperCase(Locale.ENGLISH)); 194 } 195 196 /** 197 * Reads the file provided and checks line separators. 198 * 199 * @param file the file to be processed 200 * @throws IOException When an IO error occurred while reading from the 201 * file provided 202 */ 203 private void readAndCheckFile(File file) throws IOException { 204 // Cannot use lines as the line separators have been removed! 205 try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) { 206 if (lineSeparator == LineSeparatorOption.LF 207 && endsWithNewline(randomAccessFile, LineSeparatorOption.CRLF)) { 208 log(1, MSG_KEY_WRONG_ENDING, file.getPath()); 209 } 210 else if (!endsWithNewline(randomAccessFile, lineSeparator)) { 211 log(1, MSG_KEY_NO_NEWLINE_EOF, file.getPath()); 212 } 213 } 214 } 215 216 /** 217 * Checks whether the content provided by the Reader ends with the platform 218 * specific line separator. 219 * 220 * @param file The reader for the content to check 221 * @param separator The line separator 222 * @return boolean Whether the content ends with a line separator 223 * @throws IOException When an IO error occurred while reading from the 224 * provided reader 225 */ 226 private static boolean endsWithNewline(RandomAccessFile file, LineSeparatorOption separator) 227 throws IOException { 228 final boolean result; 229 final int len = separator.length(); 230 if (file.length() < len) { 231 result = false; 232 } 233 else { 234 file.seek(file.length() - len); 235 final byte[] lastBytes = new byte[len]; 236 final int readBytes = file.read(lastBytes); 237 if (readBytes != len) { 238 throw new IOException("Unable to read " + len + " bytes, got " 239 + readBytes); 240 } 241 result = separator.matches(lastBytes); 242 } 243 return result; 244 } 245 246}