View Javadoc
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   * &#64;&#64; -32,4 +32,5 &#64;&#64; 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   * <ul>
84   * <li>
85   * Property {@code fileExtensions} - Specify the file extensions of the files to process.
86   * Type is {@code java.lang.String[]}.
87   * Default value is {@code ""}.
88   * </li>
89   * <li>
90   * Property {@code lineSeparator} - Specify the type of line separator.
91   * Type is {@code com.puppycrawl.tools.checkstyle.checks.LineSeparatorOption}.
92   * Default value is {@code lf_cr_crlf}.
93   * </li>
94   * </ul>
95   *
96   * <p>
97   * Parent is {@code com.puppycrawl.tools.checkstyle.Checker}
98   * </p>
99   *
100  * <p>
101  * Violation Message Keys:
102  * </p>
103  * <ul>
104  * <li>
105  * {@code noNewlineAtEOF}
106  * </li>
107  * <li>
108  * {@code unable.open}
109  * </li>
110  * <li>
111  * {@code wrong.line.end}
112  * </li>
113  * </ul>
114  *
115  * @since 3.1
116  */
117 @StatelessCheck
118 public class NewlineAtEndOfFileCheck
119     extends AbstractFileSetCheck {
120 
121     /**
122      * A key is pointing to the warning message text in "messages.properties"
123      * file.
124      */
125     public static final String MSG_KEY_UNABLE_OPEN = "unable.open";
126 
127     /**
128      * A key is pointing to the warning message text in "messages.properties"
129      * file.
130      */
131     public static final String MSG_KEY_NO_NEWLINE_EOF = "noNewlineAtEOF";
132 
133     /**
134      * A key is pointing to the warning message text in "messages.properties"
135      * file.
136      */
137     public static final String MSG_KEY_WRONG_ENDING = "wrong.line.end";
138 
139     /** Specify the type of line separator. */
140     private LineSeparatorOption lineSeparator = LineSeparatorOption.LF_CR_CRLF;
141 
142     @Override
143     protected void processFiltered(File file, FileText fileText) {
144         try {
145             readAndCheckFile(file);
146         }
147         catch (final IOException ignored) {
148             log(1, MSG_KEY_UNABLE_OPEN, file.getPath());
149         }
150     }
151 
152     /**
153      * Setter to specify the type of line separator.
154      *
155      * @param lineSeparatorParam The line separator to set
156      * @throws IllegalArgumentException If the specified line separator is not
157      *         one of 'crlf', 'lf', 'cr', 'lf_cr_crlf' or 'system'
158      * @since 3.1
159      */
160     public void setLineSeparator(String lineSeparatorParam) {
161         lineSeparator =
162             Enum.valueOf(LineSeparatorOption.class, lineSeparatorParam.trim()
163                 .toUpperCase(Locale.ENGLISH));
164     }
165 
166     /**
167      * Reads the file provided and checks line separators.
168      *
169      * @param file the file to be processed
170      * @throws IOException When an IO error occurred while reading from the
171      *         file provided
172      */
173     private void readAndCheckFile(File file) throws IOException {
174         // Cannot use lines as the line separators have been removed!
175         try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {
176             if (lineSeparator == LineSeparatorOption.LF
177                     && endsWithNewline(randomAccessFile, LineSeparatorOption.CRLF)) {
178                 log(1, MSG_KEY_WRONG_ENDING);
179             }
180             else if (!endsWithNewline(randomAccessFile, lineSeparator)) {
181                 log(1, MSG_KEY_NO_NEWLINE_EOF);
182             }
183         }
184     }
185 
186     /**
187      * Checks whether the content provided by the Reader ends with the platform
188      * specific line separator.
189      *
190      * @param file The reader for the content to check
191      * @param separator The line separator
192      * @return boolean Whether the content ends with a line separator
193      * @throws IOException When an IO error occurred while reading from the
194      *         provided reader
195      */
196     private static boolean endsWithNewline(RandomAccessFile file, LineSeparatorOption separator)
197             throws IOException {
198         final boolean result;
199         final int len = separator.length();
200         if (file.length() == 0) {
201             result = true;
202         }
203         else if (file.length() < len) {
204             result = false;
205         }
206         else {
207             file.seek(file.length() - len);
208             final byte[] lastBytes = new byte[len];
209             final int readBytes = file.read(lastBytes);
210             if (readBytes != len) {
211                 throw new IOException("Unable to read " + len + " bytes, got "
212                         + readBytes);
213             }
214             result = separator.matches(lastBytes);
215         }
216         return result;
217     }
218 
219 }