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>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 }