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 }