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 * Creates a new {@code LineEndingCheck} instance.
72 */
73 public LineEndingCheck() {
74 // no code by default
75 }
76
77 /**
78 * Setter to set lineEnding.
79 *
80 * @param ending string of value LF or CRLF
81 * @since 13.3.0
82 */
83 public void setLineEnding(String ending) {
84 lineEnding = Enum.valueOf(LineEndingOption.class,
85 ending.trim().toUpperCase(Locale.ENGLISH));
86 }
87
88 @Override
89 protected void processFiltered(File file, FileText fileText) {
90 try {
91 final byte[] content = Files.readAllBytes(file.toPath());
92 checkLineEndings(lineEnding, content);
93 }
94 catch (final IOException ignore) {
95 log(0, MSG_KEY_UNABLE_OPEN, file.getPath());
96 }
97 }
98
99 /**
100 * Checks that the file content uses the configured line ending
101 * and logs a violation for each line that does not match.
102 *
103 * @param expected the expected line ending
104 * @param content the file content as bytes
105 */
106 private void checkLineEndings(LineEndingOption expected, byte... content) {
107 int line = 1;
108
109 for (int index = 0; index < content.length; index++) {
110 final byte current = content[index];
111 if (LineEndingOption.CR.matches(current)) {
112 final boolean nextIsLf = index + 1 < content.length
113 && LineEndingOption.LF.matches(content[index + 1]);
114 final LineEndingOption actual;
115 if (nextIsLf) {
116 actual = LineEndingOption.CRLF;
117 }
118 else {
119 actual = LineEndingOption.CR;
120 }
121 if (actual != expected) {
122 logIncorrectLineEnding(line, actual);
123 }
124 line++;
125 }
126 else if (LineEndingOption.LF.matches(current)) {
127 final boolean prevIsCr = index > 0
128 && LineEndingOption.CR.matches(content[index - 1]);
129 if (!prevIsCr) {
130 final LineEndingOption actual = LineEndingOption.LF;
131 if (actual != expected) {
132 logIncorrectLineEnding(line, actual);
133 }
134 line++;
135 }
136 }
137 }
138 }
139
140 /**
141 * Logs an incorrect line ending detected at the given line.
142 *
143 * @param line the line number where the issue was found
144 * @param wrongLineEnding the detected incorrect line ending
145 */
146 private void logIncorrectLineEnding(int line,
147 LineEndingOption wrongLineEnding) {
148 log(line, MSG_KEY_WRONG_ENDING, lineEnding, wrongLineEnding);
149 }
150
151 }