001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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 * <div> 033 * Checks whether files end with a line separator. 034 * </div> 035 * 036 * <p> 037 * Rationale: Any not empty source files and text files in general should end with a line 038 * separator to let other easily add new content at the end of file and "diff" 039 * command does not show previous lines as changed. 040 * </p> 041 * 042 * <p> 043 * Example (the line with 'No newline at end of file' should not be in the diff): 044 * </p> 045 * {@snippet lang="text" : 046 * @@ -32,4 +32,5 @@ ForbidWildcardAsReturnTypeCheck.returnTypeClassNamesIgnoreRegex 047 * PublicReferenceToPrivateTypeCheck.name = Public Reference To Private Type 048 * 049 * StaticMethodCandidateCheck.name = Static Method Candidate 050 * -StaticMethodCandidateCheck.desc = Checks whether private methods should be declared as static. 051 * \ No newline at end of file 052 * +StaticMethodCandidateCheck.desc = Checks whether private methods should be declared as static. 053 * +StaticMethodCandidateCheck.skippedMethods = Method names to skip during the check. 054 * } 055 * 056 * <p> 057 * It can also trick the VCS to report the wrong owner for such lines. 058 * An engineer who has added nothing but a newline character becomes the last 059 * known author for the entire line. As a result, a mate can ask him a question 060 * to which he will not give the correct answer. 061 * </p> 062 * 063 * <p> 064 * Old Rationale: CVS source control management systems will even print 065 * a warning when it encounters a file that doesn't end with a line separator. 066 * </p> 067 * 068 * <p> 069 * Attention: property fileExtensions works with files that are passed by similar 070 * property for at <a href="https://checkstyle.org/config.html#Checker">Checker</a>. 071 * Please make sure required file extensions are mentioned at Checker's fileExtensions property. 072 * </p> 073 * 074 * <p> 075 * Notes: 076 * This will check against the platform-specific default line separator. 077 * </p> 078 * 079 * <p>This check verifies only the presence of a single line separator at the end of the file. 080 * It does not report violations for multiple trailing blank lines 081 * or additional newline characters beyond the final separator. 082 * </p> 083 * 084 * <p> 085 * It is also possible to enforce the use of a specific line-separator across 086 * platforms, with the {@code lineSeparator} property. 087 * </p> 088 * 089 * @since 3.1 090 */ 091@StatelessCheck 092public class NewlineAtEndOfFileCheck extends AbstractFileSetCheck { 093 094 /** 095 * A key is pointing to the warning message text in "messages.properties" file. 096 */ 097 public static final String MSG_KEY_UNABLE_OPEN = "unable.open"; 098 099 /** 100 * A key is pointing to the warning message text in "messages.properties" file. 101 */ 102 public static final String MSG_KEY_NO_NEWLINE_EOF = "noNewlineAtEOF"; 103 104 /** 105 * A key is pointing to the warning message text in "messages.properties" file. 106 */ 107 public static final String MSG_KEY_NO_NEWLINE_EOF_WITH_SEPARATOR = 108 "noNewlineAtEofWithSeparator"; 109 110 /** 111 * A key is pointing to the warning message text in "messages.properties" file. 112 */ 113 public static final String MSG_KEY_WRONG_ENDING = "wrong.line.end"; 114 115 /** Specify the type of line separator. */ 116 private LineSeparatorOption lineSeparator = LineSeparatorOption.LF_CR_CRLF; 117 118 /** 119 * Creates a new {@code NewlineAtEndOfFileCheck} instance. 120 */ 121 public NewlineAtEndOfFileCheck() { 122 // no code by default 123 } 124 125 @Override 126 protected void processFiltered(File file, FileText fileText) { 127 try { 128 readAndCheckFile(file); 129 } 130 catch (final IOException ignored) { 131 log(1, MSG_KEY_UNABLE_OPEN, file.getPath()); 132 } 133 } 134 135 /** 136 * Setter to specify the type of line separator. 137 * 138 * @param lineSeparatorParam The line separator to set 139 * @throws IllegalArgumentException If the specified line separator is not 140 * one of 'crlf', 'lf', 'cr', 'lf_cr_crlf' or 'system' 141 * @since 3.1 142 */ 143 public void setLineSeparator(String lineSeparatorParam) { 144 lineSeparator = 145 Enum.valueOf(LineSeparatorOption.class, lineSeparatorParam.trim() 146 .toUpperCase(Locale.ENGLISH)); 147 } 148 149 /** 150 * Reads the file provided and checks line separators. 151 * 152 * @param file the file to be processed 153 * @throws IOException When an IO error occurred while reading from the file provided 154 */ 155 private void readAndCheckFile(File file) throws IOException { 156 // Cannot use lines as the line separators have been removed! 157 try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) { 158 if (lineSeparator == LineSeparatorOption.LF 159 && endsWithNewline(randomAccessFile, LineSeparatorOption.CRLF)) { 160 log(1, MSG_KEY_WRONG_ENDING); 161 } 162 else if (!endsWithNewline(randomAccessFile, lineSeparator)) { 163 if (lineSeparator == LineSeparatorOption.LF 164 || lineSeparator == LineSeparatorOption.CR 165 || lineSeparator == LineSeparatorOption.CRLF) { 166 log(1, MSG_KEY_NO_NEWLINE_EOF_WITH_SEPARATOR, 167 lineSeparator.toString().toLowerCase(Locale.ENGLISH)); 168 } 169 else { 170 log(1, MSG_KEY_NO_NEWLINE_EOF); 171 } 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 provided reader 184 */ 185 private static boolean endsWithNewline(RandomAccessFile file, LineSeparatorOption separator) 186 throws IOException { 187 final boolean result; 188 final int len = separator.length(); 189 if (file.length() == 0) { 190 result = true; 191 } 192 else if (file.length() < len) { 193 result = false; 194 } 195 else { 196 file.seek(file.length() - len); 197 final byte[] lastBytes = new byte[len]; 198 final int readBytes = file.read(lastBytes); 199 if (readBytes != len) { 200 throw new IOException("Unable to read " + len + " bytes, got " 201 + readBytes); 202 } 203 result = separator.matches(lastBytes); 204 } 205 return result; 206 } 207 208}