1 /////////////////////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules. 3 // Copyright (C) 2001-2024 the original author or authors. 4 // 5 // This library is free software; you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 2.1 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library; if not, write to the Free Software 17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 /////////////////////////////////////////////////////////////////////////////////////////////// 19 20 package com.puppycrawl.tools.checkstyle.checks; 21 22 import java.io.File; 23 import java.io.IOException; 24 import java.io.RandomAccessFile; 25 import java.util.Locale; 26 27 import com.puppycrawl.tools.checkstyle.StatelessCheck; 28 import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck; 29 import com.puppycrawl.tools.checkstyle.api.FileText; 30 31 /** 32 * <p> 33 * Checks whether files end with a line separator. 34 * </p> 35 * <p> 36 * Rationale: Any source files and text files in general should end with a line 37 * separator to let other easily add new content at the end of file and "diff" 38 * command does not show previous lines as changed. 39 * </p> 40 * <p> 41 * Example (the line with 'No newline at end of file' should not be in the diff): 42 * </p> 43 * <pre> 44 * @@ -32,4 +32,5 @@ ForbidWildcardAsReturnTypeCheck.returnTypeClassNamesIgnoreRegex 45 * PublicReferenceToPrivateTypeCheck.name = Public Reference To Private Type 46 * 47 * StaticMethodCandidateCheck.name = Static Method Candidate 48 * -StaticMethodCandidateCheck.desc = Checks whether private methods should be declared as static. 49 * \ No newline at end of file 50 * +StaticMethodCandidateCheck.desc = Checks whether private methods should be declared as static. 51 * +StaticMethodCandidateCheck.skippedMethods = Method names to skip during the check. 52 * </pre> 53 * <p> 54 * It can also trick the VCS to report the wrong owner for such lines. 55 * An engineer who has added nothing but a newline character becomes the last 56 * known author for the entire line. As a result, a mate can ask him a question 57 * to which he will not give the correct answer. 58 * </p> 59 * <p> 60 * Old Rationale: CVS source control management systems will even print 61 * a warning when it encounters a file that doesn't end with a line separator. 62 * </p> 63 * <p> 64 * Attention: property fileExtensions works with files that are passed by similar 65 * property for at <a href="https://checkstyle.org/config.html#Checker">Checker</a>. 66 * Please make sure required file extensions are mentioned at Checker's fileExtensions property. 67 * </p> 68 * <p> 69 * This will check against the platform-specific default line separator. 70 * </p> 71 * <p> 72 * It is also possible to enforce the use of a specific line-separator across 73 * platforms, with the {@code lineSeparator} property. 74 * </p> 75 * <ul> 76 * <li> 77 * Property {@code fileExtensions} - Specify the file extensions of the files to process. 78 * Type is {@code java.lang.String[]}. 79 * Default value is {@code ""}. 80 * </li> 81 * <li> 82 * Property {@code lineSeparator} - Specify the type of line separator. 83 * Type is {@code com.puppycrawl.tools.checkstyle.checks.LineSeparatorOption}. 84 * Default value is {@code lf_cr_crlf}. 85 * </li> 86 * </ul> 87 * <p> 88 * Parent is {@code com.puppycrawl.tools.checkstyle.Checker} 89 * </p> 90 * <p> 91 * Violation Message Keys: 92 * </p> 93 * <ul> 94 * <li> 95 * {@code noNewlineAtEOF} 96 * </li> 97 * <li> 98 * {@code unable.open} 99 * </li> 100 * <li> 101 * {@code wrong.line.end} 102 * </li> 103 * </ul> 104 * 105 * @since 3.1 106 */ 107 @StatelessCheck 108 public class NewlineAtEndOfFileCheck 109 extends AbstractFileSetCheck { 110 111 /** 112 * A key is pointing to the warning message text in "messages.properties" 113 * file. 114 */ 115 public static final String MSG_KEY_UNABLE_OPEN = "unable.open"; 116 117 /** 118 * A key is pointing to the warning message text in "messages.properties" 119 * file. 120 */ 121 public static final String MSG_KEY_NO_NEWLINE_EOF = "noNewlineAtEOF"; 122 123 /** 124 * A key is pointing to the warning message text in "messages.properties" 125 * file. 126 */ 127 public static final String MSG_KEY_WRONG_ENDING = "wrong.line.end"; 128 129 /** Specify the type of line separator. */ 130 private LineSeparatorOption lineSeparator = LineSeparatorOption.LF_CR_CRLF; 131 132 @Override 133 protected void processFiltered(File file, FileText fileText) { 134 try { 135 readAndCheckFile(file); 136 } 137 catch (final IOException ignored) { 138 log(1, MSG_KEY_UNABLE_OPEN, file.getPath()); 139 } 140 } 141 142 /** 143 * Setter to specify the type of line separator. 144 * 145 * @param lineSeparatorParam The line separator to set 146 * @throws IllegalArgumentException If the specified line separator is not 147 * one of 'crlf', 'lf', 'cr', 'lf_cr_crlf' or 'system' 148 * @since 3.1 149 */ 150 public void setLineSeparator(String lineSeparatorParam) { 151 lineSeparator = 152 Enum.valueOf(LineSeparatorOption.class, lineSeparatorParam.trim() 153 .toUpperCase(Locale.ENGLISH)); 154 } 155 156 /** 157 * Reads the file provided and checks line separators. 158 * 159 * @param file the file to be processed 160 * @throws IOException When an IO error occurred while reading from the 161 * file provided 162 */ 163 private void readAndCheckFile(File file) throws IOException { 164 // Cannot use lines as the line separators have been removed! 165 try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) { 166 if (lineSeparator == LineSeparatorOption.LF 167 && endsWithNewline(randomAccessFile, LineSeparatorOption.CRLF)) { 168 log(1, MSG_KEY_WRONG_ENDING); 169 } 170 else if (!endsWithNewline(randomAccessFile, lineSeparator)) { 171 log(1, MSG_KEY_NO_NEWLINE_EOF); 172 } 173 } 174 } 175 176 /** 177 * Checks whether the content provided by the Reader ends with the platform 178 * specific line separator. 179 * 180 * @param file The reader for the content to check 181 * @param separator The line separator 182 * @return boolean Whether the content ends with a line separator 183 * @throws IOException When an IO error occurred while reading from the 184 * provided reader 185 */ 186 private static boolean endsWithNewline(RandomAccessFile file, LineSeparatorOption separator) 187 throws IOException { 188 final boolean result; 189 final int len = separator.length(); 190 if (file.length() < len) { 191 result = false; 192 } 193 else { 194 file.seek(file.length() - len); 195 final byte[] lastBytes = new byte[len]; 196 final int readBytes = file.read(lastBytes); 197 if (readBytes != len) { 198 throw new IOException("Unable to read " + len + " bytes, got " 199 + readBytes); 200 } 201 result = separator.matches(lastBytes); 202 } 203 return result; 204 } 205 206 }