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.nio.file.Files;
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 file uses a specific line-ending sequence
34 * ({@code LF}, {@code CRLF}, or {@code CR}).
35 * </div>
36 *
37 * <p>
38 * A violation is reported for each line whose actual line ending does not match
39 * the configured one.
40 * </p>
41 *
42 * <p>
43 * Notes:
44 * Files containing mixed line endings may produce multiple violations,
45 * one for each non-matching line.
46 * </p>
47 *
48 * @since 13.3.0
49 */
50 @StatelessCheck
51 public class LineEndingCheck extends AbstractFileSetCheck {
52
53 /**
54 * A key is pointing to the warning message text in "message.properties"
55 * file.
56 */
57 public static final String MSG_KEY_UNABLE_OPEN = "unable.open";
58
59 /**
60 * A key is pointing to the warning message text in "message.properties"
61 * file.
62 */
63 public static final String MSG_KEY_WRONG_ENDING = "wrong.line.ending";
64
65 /**
66 * Default line ending LF.
67 */
68 private LineEndingOption lineEnding = LineEndingOption.LF;
69
70 /**
71 * Setter to set lineEnding.
72 *
73 * @param ending string of value LF or CRLF
74 * @since 13.3.0
75 */
76 public void setLineEnding(String ending) {
77 lineEnding = Enum.valueOf(LineEndingOption.class,
78 ending.trim().toUpperCase(Locale.ENGLISH));
79 }
80
81 @Override
82 protected void processFiltered(File file, FileText fileText) {
83 try {
84 final byte[] content = Files.readAllBytes(file.toPath());
85 checkLineEndings(lineEnding, content);
86 }
87 catch (final IOException ignore) {
88 log(0, MSG_KEY_UNABLE_OPEN, file.getPath());
89 }
90 }
91
92 /**
93 * Checks that the file content uses the configured line ending
94 * and logs a violation for each line that does not match.
95 *
96 * @param expected the expected line ending
97 * @param content the file content as bytes
98 */
99 private void checkLineEndings(LineEndingOption expected, byte... content) {
100 int line = 1;
101
102 for (int index = 0; index < content.length; index++) {
103 final byte current = content[index];
104 if (LineEndingOption.CR.matches(current)) {
105 final boolean nextIsLf = index + 1 < content.length
106 && LineEndingOption.LF.matches(content[index + 1]);
107 final LineEndingOption actual;
108 if (nextIsLf) {
109 actual = LineEndingOption.CRLF;
110 }
111 else {
112 actual = LineEndingOption.CR;
113 }
114 if (actual != expected) {
115 logIncorrectLineEnding(line, actual);
116 }
117 line++;
118 }
119 else if (LineEndingOption.LF.matches(current)) {
120 final boolean prevIsCr = index > 0
121 && LineEndingOption.CR.matches(content[index - 1]);
122 if (!prevIsCr) {
123 final LineEndingOption actual = LineEndingOption.LF;
124 if (actual != expected) {
125 logIncorrectLineEnding(line, actual);
126 }
127 line++;
128 }
129 }
130 }
131 }
132
133 /**
134 * Logs an incorrect line ending detected at the given line.
135 *
136 * @param line the line number where the issue was found
137 * @param wrongLineEnding the detected incorrect line ending
138 */
139 private void logIncorrectLineEnding(int line,
140 LineEndingOption wrongLineEnding) {
141 log(line, MSG_KEY_WRONG_ENDING, lineEnding, wrongLineEnding);
142 }
143
144 }