View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 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>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_NO_NEWLINE_EOF_WITH_SEPARATOR =
112             "noNewlineAtEofWithSeparator";
113 
114     /**
115      * A key is pointing to the warning message text in "messages.properties"
116      * file.
117      */
118     public static final String MSG_KEY_WRONG_ENDING = "wrong.line.end";
119 
120     /** Specify the type of line separator. */
121     private LineSeparatorOption lineSeparator = LineSeparatorOption.LF_CR_CRLF;
122 
123     /**
124      * Creates a new {@code NewlineAtEndOfFileCheck} instance.
125      */
126     public NewlineAtEndOfFileCheck() {
127         // no code by default
128     }
129 
130     @Override
131     protected void processFiltered(File file, FileText fileText) {
132         try {
133             readAndCheckFile(file);
134         }
135         catch (final IOException ignored) {
136             log(1, MSG_KEY_UNABLE_OPEN, file.getPath());
137         }
138     }
139 
140     /**
141      * Setter to specify the type of line separator.
142      *
143      * @param lineSeparatorParam The line separator to set
144      * @throws IllegalArgumentException If the specified line separator is not
145      *         one of 'crlf', 'lf', 'cr', 'lf_cr_crlf' or 'system'
146      * @since 3.1
147      */
148     public void setLineSeparator(String lineSeparatorParam) {
149         lineSeparator =
150             Enum.valueOf(LineSeparatorOption.class, lineSeparatorParam.trim()
151                 .toUpperCase(Locale.ENGLISH));
152     }
153 
154     /**
155      * Reads the file provided and checks line separators.
156      *
157      * @param file the file to be processed
158      * @throws IOException When an IO error occurred while reading from the
159      *         file provided
160      */
161     private void readAndCheckFile(File file) throws IOException {
162         // Cannot use lines as the line separators have been removed!
163         try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {
164             if (lineSeparator == LineSeparatorOption.LF
165                     && endsWithNewline(randomAccessFile, LineSeparatorOption.CRLF)) {
166                 log(1, MSG_KEY_WRONG_ENDING);
167             }
168             else if (!endsWithNewline(randomAccessFile, lineSeparator)) {
169                 if (lineSeparator == LineSeparatorOption.LF
170                         || lineSeparator == LineSeparatorOption.CR
171                         || lineSeparator == LineSeparatorOption.CRLF) {
172                     log(1, MSG_KEY_NO_NEWLINE_EOF_WITH_SEPARATOR,
173                             lineSeparator.toString().toLowerCase(Locale.ENGLISH));
174                 }
175                 else {
176                     log(1, MSG_KEY_NO_NEWLINE_EOF);
177                 }
178             }
179         }
180     }
181 
182     /**
183      * Checks whether the content provided by the Reader ends with the platform
184      * specific line separator.
185      *
186      * @param file The reader for the content to check
187      * @param separator The line separator
188      * @return boolean Whether the content ends with a line separator
189      * @throws IOException When an IO error occurred while reading from the
190      *         provided reader
191      */
192     private static boolean endsWithNewline(RandomAccessFile file, LineSeparatorOption separator)
193             throws IOException {
194         final boolean result;
195         final int len = separator.length();
196         if (file.length() == 0) {
197             result = true;
198         }
199         else if (file.length() < len) {
200             result = false;
201         }
202         else {
203             file.seek(file.length() - len);
204             final byte[] lastBytes = new byte[len];
205             final int readBytes = file.read(lastBytes);
206             if (readBytes != len) {
207                 throw new IOException("Unable to read " + len + " bytes, got "
208                         + readBytes);
209             }
210             result = separator.matches(lastBytes);
211         }
212         return result;
213     }
214 
215 }