1 /////////////////////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules. 3 // Copyright (C) 2001-2025 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 * <div> 33 * Checks whether files end with a line separator. 34 * </div> 35 * 36 * <p> 37 * Rationale: Any not empty source files and text files in general should end with a line 38 * separator to let other easily add new content at the end of file and "diff" 39 * command does not show previous lines as changed. 40 * </p> 41 * 42 * <p> 43 * Example (the line with 'No newline at end of file' should not be in the diff): 44 * </p> 45 * <div class="wrapper"><pre class="prettyprint"><code class="language-text"> 46 * @@ -32,4 +32,5 @@ ForbidWildcardAsReturnTypeCheck.returnTypeClassNamesIgnoreRegex 47 * PublicReferenceToPrivateTypeCheck.name = Public Reference To Private Type 48 * 49 * StaticMethodCandidateCheck.name = Static Method Candidate 50 * -StaticMethodCandidateCheck.desc = Checks whether private methods should be declared as static. 51 * \ No newline at end of file 52 * +StaticMethodCandidateCheck.desc = Checks whether private methods should be declared as static. 53 * +StaticMethodCandidateCheck.skippedMethods = Method names to skip during the check. 54 * </code></pre></div> 55 * 56 * <p> 57 * It can also trick the VCS to report the wrong owner for such lines. 58 * An engineer who has added nothing but a newline character becomes the last 59 * known author for the entire line. As a result, a mate can ask him a question 60 * to which he will not give the correct answer. 61 * </p> 62 * 63 * <p> 64 * Old Rationale: CVS source control management systems will even print 65 * a warning when it encounters a file that doesn't end with a line separator. 66 * </p> 67 * 68 * <p> 69 * Attention: property fileExtensions works with files that are passed by similar 70 * property for at <a href="https://checkstyle.org/config.html#Checker">Checker</a>. 71 * Please make sure required file extensions are mentioned at Checker's fileExtensions property. 72 * </p> 73 * 74 * <p> 75 * Notes: 76 * This will check against the platform-specific default line separator. 77 * </p> 78 * 79 * <p> 80 * It is also possible to enforce the use of a specific line-separator across 81 * platforms, with the {@code lineSeparator} property. 82 * </p> 83 * 84 * @since 3.1 85 */ 86 @StatelessCheck 87 public class NewlineAtEndOfFileCheck 88 extends AbstractFileSetCheck { 89 90 /** 91 * A key is pointing to the warning message text in "messages.properties" 92 * file. 93 */ 94 public static final String MSG_KEY_UNABLE_OPEN = "unable.open"; 95 96 /** 97 * A key is pointing to the warning message text in "messages.properties" 98 * file. 99 */ 100 public static final String MSG_KEY_NO_NEWLINE_EOF = "noNewlineAtEOF"; 101 102 /** 103 * A key is pointing to the warning message text in "messages.properties" 104 * file. 105 */ 106 public static final String MSG_KEY_WRONG_ENDING = "wrong.line.end"; 107 108 /** Specify the type of line separator. */ 109 private LineSeparatorOption lineSeparator = LineSeparatorOption.LF_CR_CRLF; 110 111 @Override 112 protected void processFiltered(File file, FileText fileText) { 113 try { 114 readAndCheckFile(file); 115 } 116 catch (final IOException ignored) { 117 log(1, MSG_KEY_UNABLE_OPEN, file.getPath()); 118 } 119 } 120 121 /** 122 * Setter to specify the type of line separator. 123 * 124 * @param lineSeparatorParam The line separator to set 125 * @throws IllegalArgumentException If the specified line separator is not 126 * one of 'crlf', 'lf', 'cr', 'lf_cr_crlf' or 'system' 127 * @since 3.1 128 */ 129 public void setLineSeparator(String lineSeparatorParam) { 130 lineSeparator = 131 Enum.valueOf(LineSeparatorOption.class, lineSeparatorParam.trim() 132 .toUpperCase(Locale.ENGLISH)); 133 } 134 135 /** 136 * Reads the file provided and checks line separators. 137 * 138 * @param file the file to be processed 139 * @throws IOException When an IO error occurred while reading from the 140 * file provided 141 */ 142 private void readAndCheckFile(File file) throws IOException { 143 // Cannot use lines as the line separators have been removed! 144 try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) { 145 if (lineSeparator == LineSeparatorOption.LF 146 && endsWithNewline(randomAccessFile, LineSeparatorOption.CRLF)) { 147 log(1, MSG_KEY_WRONG_ENDING); 148 } 149 else if (!endsWithNewline(randomAccessFile, lineSeparator)) { 150 log(1, MSG_KEY_NO_NEWLINE_EOF); 151 } 152 } 153 } 154 155 /** 156 * Checks whether the content provided by the Reader ends with the platform 157 * specific line separator. 158 * 159 * @param file The reader for the content to check 160 * @param separator The line separator 161 * @return boolean Whether the content ends with a line separator 162 * @throws IOException When an IO error occurred while reading from the 163 * provided reader 164 */ 165 private static boolean endsWithNewline(RandomAccessFile file, LineSeparatorOption separator) 166 throws IOException { 167 final boolean result; 168 final int len = separator.length(); 169 if (file.length() == 0) { 170 result = true; 171 } 172 else if (file.length() < len) { 173 result = false; 174 } 175 else { 176 file.seek(file.length() - len); 177 final byte[] lastBytes = new byte[len]; 178 final int readBytes = file.read(lastBytes); 179 if (readBytes != len) { 180 throw new IOException("Unable to read " + len + " bytes, got " 181 + readBytes); 182 } 183 result = separator.matches(lastBytes); 184 } 185 return result; 186 } 187 188 }