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>This check verifies only the presence of a single line separator at the end of the file.
80 * It does not report violations for multiple trailing blank lines
81 * or additional newline characters beyond the final separator.
82 * </p>
83 *
84 * <p>
85 * It is also possible to enforce the use of a specific line-separator across
86 * platforms, with the {@code lineSeparator} property.
87 * </p>
88 *
89 * @since 3.1
90 */
91 @StatelessCheck
92 public class NewlineAtEndOfFileCheck
93 extends AbstractFileSetCheck {
94
95 /**
96 * A key is pointing to the warning message text in "messages.properties"
97 * file.
98 */
99 public static final String MSG_KEY_UNABLE_OPEN = "unable.open";
100
101 /**
102 * A key is pointing to the warning message text in "messages.properties"
103 * file.
104 */
105 public static final String MSG_KEY_NO_NEWLINE_EOF = "noNewlineAtEOF";
106
107 /**
108 * A key is pointing to the warning message text in "messages.properties"
109 * file.
110 */
111 public static final String MSG_KEY_WRONG_ENDING = "wrong.line.end";
112
113 /** Specify the type of line separator. */
114 private LineSeparatorOption lineSeparator = LineSeparatorOption.LF_CR_CRLF;
115
116 @Override
117 protected void processFiltered(File file, FileText fileText) {
118 try {
119 readAndCheckFile(file);
120 }
121 catch (final IOException ignored) {
122 log(1, MSG_KEY_UNABLE_OPEN, file.getPath());
123 }
124 }
125
126 /**
127 * Setter to specify the type of line separator.
128 *
129 * @param lineSeparatorParam The line separator to set
130 * @throws IllegalArgumentException If the specified line separator is not
131 * one of 'crlf', 'lf', 'cr', 'lf_cr_crlf' or 'system'
132 * @since 3.1
133 */
134 public void setLineSeparator(String lineSeparatorParam) {
135 lineSeparator =
136 Enum.valueOf(LineSeparatorOption.class, lineSeparatorParam.trim()
137 .toUpperCase(Locale.ENGLISH));
138 }
139
140 /**
141 * Reads the file provided and checks line separators.
142 *
143 * @param file the file to be processed
144 * @throws IOException When an IO error occurred while reading from the
145 * file provided
146 */
147 private void readAndCheckFile(File file) throws IOException {
148 // Cannot use lines as the line separators have been removed!
149 try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {
150 if (lineSeparator == LineSeparatorOption.LF
151 && endsWithNewline(randomAccessFile, LineSeparatorOption.CRLF)) {
152 log(1, MSG_KEY_WRONG_ENDING);
153 }
154 else if (!endsWithNewline(randomAccessFile, lineSeparator)) {
155 log(1, MSG_KEY_NO_NEWLINE_EOF);
156 }
157 }
158 }
159
160 /**
161 * Checks whether the content provided by the Reader ends with the platform
162 * specific line separator.
163 *
164 * @param file The reader for the content to check
165 * @param separator The line separator
166 * @return boolean Whether the content ends with a line separator
167 * @throws IOException When an IO error occurred while reading from the
168 * provided reader
169 */
170 private static boolean endsWithNewline(RandomAccessFile file, LineSeparatorOption separator)
171 throws IOException {
172 final boolean result;
173 final int len = separator.length();
174 if (file.length() == 0) {
175 result = true;
176 }
177 else if (file.length() < len) {
178 result = false;
179 }
180 else {
181 file.seek(file.length() - len);
182 final byte[] lastBytes = new byte[len];
183 final int readBytes = file.read(lastBytes);
184 if (readBytes != len) {
185 throw new IOException("Unable to read " + len + " bytes, got "
186 + readBytes);
187 }
188 result = separator.matches(lastBytes);
189 }
190 return result;
191 }
192
193 }