diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..d8127358
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,5 @@
+# Test resources that need specific eol
+src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/** -text
+
+# Force LF line endings for diff test files
+*.diff text eol=lf
diff --git a/README.md b/README.md
index a2651cd4..360242ef 100644
--- a/README.md
+++ b/README.md
@@ -219,25 +219,25 @@ This table tracks the auto-fix support status of OpenRewrite recipes for each Ch
### Miscellaneous
-| Status | Check | Recipe | Coverage Notes |
-|--------|----------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------|----------------|
-| 🟢 | [`ArrayTypeStyle`](https://checkstyle.sourceforge.io/checks/misc/arraytypestyle.html#ArrayTypeStyle) | `TBD` | |
-| 🔴 | [`AvoidEscapedUnicodeCharacters`](https://checkstyle.sourceforge.io/checks/misc/avoidescapedunicodecharacters.html#AvoidEscapedUnicodeCharacters) | | Need to determine appropriate replacements |
-| 🟢 | [`CommentsIndentation`](https://checkstyle.sourceforge.io/checks/misc/commentsindentation.html#CommentsIndentation) | `TBD` | |
-| 🔴 | [`DescendantToken`](https://checkstyle.sourceforge.io/checks/misc/descendanttoken.html#DescendantToken) | | Context-dependent token restrictions |
-| 🟢 | [`FinalParameters`](https://checkstyle.sourceforge.io/checks/misc/finalparameters.html#FinalParameters) | `TBD` | |
-| 🟢 | [`HexLiteralCase`](https://checkstyle.sourceforge.io/checks/misc/hexliteralcase.html#HexLiteralCase) | [`HexLiteralCase`](https://github.com/checkstyle/checkstyle-openrewrite-recipes/blob/main/src/main/java/org/checkstyle/autofix/recipe/HexLiteralCase.java) | |
-| 🟢 | [`Indentation`](https://checkstyle.sourceforge.io/checks/misc/indentation.html#Indentation) | `TBD` | |
-| 🟢 | [`NewlineAtEndOfFile`](https://checkstyle.sourceforge.io/checks/misc/newlineatendoffile.html#NewlineAtEndOfFile) | `TBD` | |
-| 🔴 | [`NoCodeInFile`](https://checkstyle.sourceforge.io/checks/misc/nocodeinfile.html#NoCodeInFile) | | Add code or remove file |
-| 🔴 | [`OrderedProperties`](https://checkstyle.sourceforge.io/checks/misc/orderedproperties.html#OrderedProperties) | | Reorder properties |
-| 🔴 | [`OuterTypeFilename`](https://checkstyle.sourceforge.io/checks/misc/outertypefilename.html#OuterTypeFilename) | | Rename file or class |
-| 🔴 | [`TodoComment`](https://checkstyle.sourceforge.io/checks/misc/todocomment.html#TodoComment) | | Resolve TODO comments |
-| 🟢 | [`TrailingComment`](https://checkstyle.sourceforge.io/checks/misc/trailingcomment.html#TrailingComment) | `TBD` | |
-| 🔴 | [`Translation`](https://checkstyle.sourceforge.io/checks/misc/translation.html#Translation) | | Fix property file translations |
-| 🟢 | [`UncommentedMain`](https://checkstyle.sourceforge.io/checks/misc/uncommentedmain.html#UncommentedMain) | `TBD` | |
-| 🔴 | [`UniqueProperties`](https://checkstyle.sourceforge.io/checks/misc/uniqueproperties.html#UniqueProperties) | | Remove duplicate properties |
-| 🟢 | [`UpperEll`](https://checkstyle.sourceforge.io/checks/misc/upperell.html#UpperEll) | [`UpperEll`](https://github.com/checkstyle/checkstyle-openrewrite-recipes/blob/main/src/main/java/org/checkstyle/autofix/recipe/UpperEll.java) | |
+| Status | Check | Recipe | Coverage Notes |
+|--------|----------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------|
+| 🟢 | [`ArrayTypeStyle`](https://checkstyle.sourceforge.io/checks/misc/arraytypestyle.html#ArrayTypeStyle) | `TBD` | |
+| 🔴 | [`AvoidEscapedUnicodeCharacters`](https://checkstyle.sourceforge.io/checks/misc/avoidescapedunicodecharacters.html#AvoidEscapedUnicodeCharacters) | | Need to determine appropriate replacements |
+| 🟢 | [`CommentsIndentation`](https://checkstyle.sourceforge.io/checks/misc/commentsindentation.html#CommentsIndentation) | `TBD` | |
+| 🔴 | [`DescendantToken`](https://checkstyle.sourceforge.io/checks/misc/descendanttoken.html#DescendantToken) | | Context-dependent token restrictions |
+| 🟢 | [`FinalParameters`](https://checkstyle.sourceforge.io/checks/misc/finalparameters.html#FinalParameters) | `TBD` | |
+| 🟢 | [`HexLiteralCase`](https://checkstyle.sourceforge.io/checks/misc/hexliteralcase.html#HexLiteralCase) | [`HexLiteralCase`](https://github.com/checkstyle/checkstyle-openrewrite-recipes/blob/main/src/main/java/org/checkstyle/autofix/recipe/HexLiteralCase.java) | |
+| 🟢 | [`Indentation`](https://checkstyle.sourceforge.io/checks/misc/indentation.html#Indentation) | `TBD` | |
+| 🟢 | [`NewlineAtEndOfFile`](https://checkstyle.sourceforge.io/checks/misc/newlineatendoffile.html#NewlineAtEndOfFile) | [`NewlineAtEndOfFile`](https://github.com/checkstyle/checkstyle-openrewrite-recipes/blob/main/src/main/java/org/checkstyle/autofix/recipe/NewlineAtEndOfFile.java) | |
+| 🔴 | [`NoCodeInFile`](https://checkstyle.sourceforge.io/checks/misc/nocodeinfile.html#NoCodeInFile) | | Add code or remove file |
+| 🔴 | [`OrderedProperties`](https://checkstyle.sourceforge.io/checks/misc/orderedproperties.html#OrderedProperties) | | Reorder properties |
+| 🔴 | [`OuterTypeFilename`](https://checkstyle.sourceforge.io/checks/misc/outertypefilename.html#OuterTypeFilename) | | Rename file or class |
+| 🔴 | [`TodoComment`](https://checkstyle.sourceforge.io/checks/misc/todocomment.html#TodoComment) | | Resolve TODO comments |
+| 🟢 | [`TrailingComment`](https://checkstyle.sourceforge.io/checks/misc/trailingcomment.html#TrailingComment) | `TBD` | |
+| 🔴 | [`Translation`](https://checkstyle.sourceforge.io/checks/misc/translation.html#Translation) | | Fix property file translations |
+| 🟢 | [`UncommentedMain`](https://checkstyle.sourceforge.io/checks/misc/uncommentedmain.html#UncommentedMain) | `TBD` | |
+| 🔴 | [`UniqueProperties`](https://checkstyle.sourceforge.io/checks/misc/uniqueproperties.html#UniqueProperties) | | Remove duplicate properties |
+| 🟢 | [`UpperEll`](https://checkstyle.sourceforge.io/checks/misc/upperell.html#UpperEll) | [`UpperEll`](https://github.com/checkstyle/checkstyle-openrewrite-recipes/blob/main/src/main/java/org/checkstyle/autofix/recipe/UpperEll.java) | |
### Modifiers
diff --git a/config/pitest-suppressions.xml b/config/pitest-suppressions.xml
index fe74ac4a..41fa593d 100644
--- a/config/pitest-suppressions.xml
+++ b/config/pitest-suppressions.xml
@@ -460,15 +460,6 @@
header = config.getProperty(HEADER_PROPERTY);
-
- Header.java
- org.checkstyle.autofix.recipe.Header
- extractLicenseHeader
- org.pitest.mutationtest.engine.gregor.mutators.experimental.ArgumentPropagationMutator
- replaced call to org/checkstyle/autofix/recipe/Header::toLfLineEnding with argument
- header = toLfLineEnding(Files.readString(Path.of(headerFilePath), charsetToUse));
-
-
Header.java
org.checkstyle.autofix.recipe.Header
@@ -505,15 +496,6 @@
return "Header recipe";
-
- Header.java
- org.checkstyle.autofix.recipe.Header
- toLfLineEnding
- org.pitest.mutationtest.engine.gregor.mutators.experimental.NakedReceiverMutator
- replaced call to java/lang/String::replaceAll with receiver
- return text.replaceAll("(?x)\\\\r(?=\\\\n)|\\r(?=\\n)", "");
-
-
Header.java
org.checkstyle.autofix.recipe.Header$HeaderVisitor
@@ -523,22 +505,13 @@
return violations.removeIf(violation -> {
-
- Header.java
- org.checkstyle.autofix.recipe.Header$HeaderVisitor
- lambda$extractCurrentHeader$0
- org.pitest.mutationtest.engine.gregor.mutators.experimental.ArgumentPropagationMutator
- replaced call to org/checkstyle/autofix/recipe/Header::toLfLineEnding with argument
- + toLfLineEnding(comment.getSuffix());
-
-
Header.java
org.checkstyle.autofix.recipe.Header$HeaderVisitor
lambda$extractCurrentHeader$0
org.pitest.mutationtest.engine.gregor.mutators.NonVoidMethodCallMutator
removed call to org/checkstyle/autofix/recipe/Header$HeaderVisitor::getCursor
- return comment.printComment(getCursor())
+ return comment.printComment(getCursor()) + comment.getSuffix();
@@ -730,6 +703,213 @@
J.Literal result = super.visitLiteral(literal, executionContext);
+
+ NewlineAtEndOfFile.java
+ org.checkstyle.autofix.recipe.NewlineAtEndOfFile
+ getDescription
+ org.pitest.mutationtest.engine.gregor.mutators.returns.EmptyObjectReturnValsMutator
+ replaced return value with "" for org/checkstyle/autofix/recipe/NewlineAtEndOfFile::getDescription
+ return "Some tools work better when files end with an empty line.";
+
+
+
+ NewlineAtEndOfFile.java
+ org.checkstyle.autofix.recipe.NewlineAtEndOfFile
+ getDisplayName
+ org.pitest.mutationtest.engine.gregor.mutators.returns.EmptyObjectReturnValsMutator
+ replaced return value with "" for org/checkstyle/autofix/recipe/NewlineAtEndOfFile::getDisplayName
+ return "End files with a single newline";
+
+
+
+ NewlineAtEndOfFile.java
+ org.checkstyle.autofix.recipe.NewlineAtEndOfFile$NewLineAtEndOfFileVisitor
+ determineLineEnding
+ org.pitest.mutationtest.engine.gregor.mutators.NonVoidMethodCallMutator
+ removed call to org/checkstyle/autofix/recipe/NewlineAtEndOfFile$NewLineAtEndOfFileVisitor::getAutodetectedLineEnding
+ case "lf_cr_crlf" -> getAutodetectedLineEnding(sourceFile);
+
+
+
+ NewlineAtEndOfFile.java
+ org.checkstyle.autofix.recipe.NewlineAtEndOfFile$NewLineAtEndOfFileVisitor
+ determineLineEnding
+ org.pitest.mutationtest.engine.gregor.mutators.NonVoidMethodCallMutator
+ removed call to java/lang/System::lineSeparator
+ case "system" -> System.lineSeparator();
+
+
+
+ NewlineAtEndOfFile.java
+ org.checkstyle.autofix.recipe.NewlineAtEndOfFile$NewLineAtEndOfFileVisitor
+ determineLineEnding
+ org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator_2
+ RemoveSwitch 2 (case value 2)
+ return switch (lineSeparatorConfig) {
+
+
+
+ NewlineAtEndOfFile.java
+ org.checkstyle.autofix.recipe.NewlineAtEndOfFile$NewLineAtEndOfFileVisitor
+ determineLineEnding
+ org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator_3
+ RemoveSwitch 3 (case value 3)
+ return switch (lineSeparatorConfig) {
+
+
+
+ NewlineAtEndOfFile.java
+ org.checkstyle.autofix.recipe.NewlineAtEndOfFile$NewLineAtEndOfFileVisitor
+ determineLineEnding
+ org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator_4
+ RemoveSwitch 4 (case value 4)
+ return switch (lineSeparatorConfig) {
+
+
+
+ NewlineAtEndOfFile.java
+ org.checkstyle.autofix.recipe.NewlineAtEndOfFile$NewLineAtEndOfFileVisitor
+ getAutodetectedLineEnding
+ org.pitest.mutationtest.engine.gregor.mutators.NonVoidMethodCallMutator
+ removed call to org/openrewrite/style/Style::from
+ Style.from(GeneralFormatStyle.class, sourceFile, () -> {
+
+
+
+ NewlineAtEndOfFile.java
+ org.checkstyle.autofix.recipe.NewlineAtEndOfFile$NewLineAtEndOfFileVisitor
+ getAutodetectedLineEnding
+ org.pitest.mutationtest.engine.gregor.mutators.NonVoidMethodCallMutator
+ removed call to org/openrewrite/style/GeneralFormatStyle::newLine
+ return generalFormatStyle.newLine();
+
+
+
+ NewlineAtEndOfFile.java
+ org.checkstyle.autofix.recipe.NewlineAtEndOfFile$NewLineAtEndOfFileVisitor
+ getAutodetectedLineEnding
+ org.pitest.mutationtest.engine.gregor.mutators.returns.EmptyObjectReturnValsMutator
+ replaced return value with "" for org/checkstyle/autofix/recipe/NewlineAtEndOfFile$NewLineAtEndOfFileVisitor::getAutodetectedLineEnding
+ return generalFormatStyle.newLine();
+
+
+
+ NewlineAtEndOfFile.java
+ org.checkstyle.autofix.recipe.NewlineAtEndOfFile$NewLineAtEndOfFileVisitor
+ hasViolation
+ org.pitest.mutationtest.engine.gregor.mutators.returns.BooleanTrueReturnValsMutator
+ replaced boolean return with true for org/checkstyle/autofix/recipe/NewlineAtEndOfFile$NewLineAtEndOfFileVisitor::hasViolation
+ return violations.stream()
+
+
+
+ NewlineAtEndOfFile.java
+ org.checkstyle.autofix.recipe.NewlineAtEndOfFile$NewLineAtEndOfFileVisitor
+ lambda$getAutodetectedLineEnding$1
+ org.pitest.mutationtest.engine.gregor.mutators.NonVoidMethodCallMutator
+ removed call to org/openrewrite/java/format/AutodetectGeneralFormatStyle::autodetectGeneralFormatStyle
+ .autodetectGeneralFormatStyle(sourceFile);
+
+
+
+ NewlineAtEndOfFile.java
+ org.checkstyle.autofix.recipe.NewlineAtEndOfFile$NewLineAtEndOfFileVisitor
+ lambda$getAutodetectedLineEnding$1
+ org.pitest.mutationtest.engine.gregor.mutators.returns.NullReturnValsMutator
+ replaced return value with null for org/checkstyle/autofix/recipe/NewlineAtEndOfFile$NewLineAtEndOfFileVisitor::lambda$getAutodetectedLineEnding$1
+ return AutodetectGeneralFormatStyle
+
+
+
+ NewlineAtEndOfFile.java
+ org.checkstyle.autofix.recipe.NewlineAtEndOfFile$NewLineAtEndOfFileVisitor
+ lambda$hasViolation$2
+ org.pitest.mutationtest.engine.gregor.mutators.returns.BooleanTrueReturnValsMutator
+ replaced boolean return with true for org/checkstyle/autofix/recipe/NewlineAtEndOfFile$NewLineAtEndOfFileVisitor::lambda$hasViolation$2
+ .anyMatch(violation -> violation.getFilePath().endsWith(filePath));
+
+
+
+ NewlineAtEndOfFile.java
+ org.checkstyle.autofix.recipe.NewlineAtEndOfFile$NewLineAtEndOfFileVisitor
+ mapLast
+ org.pitest.mutationtest.engine.gregor.mutators.NonVoidMethodCallMutator
+ removed call to java/util/List::isEmpty
+ if (comments != null && !comments.isEmpty()) {
+
+
+
+ NewlineAtEndOfFile.java
+ org.checkstyle.autofix.recipe.NewlineAtEndOfFile$NewLineAtEndOfFileVisitor
+ mapLast
+ org.pitest.mutationtest.engine.gregor.mutators.RemoveConditionalMutator_EQUAL_IF
+ removed conditional - replaced equality check with true
+ if (comments != null && !comments.isEmpty()) {
+
+
+
+ NewlineAtEndOfFile.java
+ org.checkstyle.autofix.recipe.NewlineAtEndOfFile$NewLineAtEndOfFileVisitor
+ mapLast
+ org.pitest.mutationtest.engine.gregor.mutators.RemoveConditionalMutator_EQUAL_IF
+ removed conditional - replaced equality check with true
+ if (last != newLast) {
+
+
+
+ NewlineAtEndOfFile.java
+ org.checkstyle.autofix.recipe.NewlineAtEndOfFile$NewLineAtEndOfFileVisitor
+ visit
+ org.pitest.mutationtest.engine.gregor.mutators.experimental.NakedReceiverMutator
+ replaced call to java/nio/file/Path::toAbsolutePath with receiver
+ final Path filePath = sourceFile.getSourcePath().toAbsolutePath();
+
+
+
+ NewlineAtEndOfFile.java
+ org.checkstyle.autofix.recipe.NewlineAtEndOfFile$NewLineAtEndOfFileVisitor
+ visit
+ org.pitest.mutationtest.engine.gregor.mutators.NonVoidMethodCallMutator
+ removed call to org/openrewrite/java/tree/Space::getLastWhitespace
+ final String lastWhitespace = eof.getLastWhitespace();
+
+
+
+ NewlineAtEndOfFile.java
+ org.checkstyle.autofix.recipe.NewlineAtEndOfFile$NewLineAtEndOfFileVisitor
+ visit
+ org.pitest.mutationtest.engine.gregor.mutators.NonVoidMethodCallMutator
+ removed call to java/lang/String::equals
+ if (!expectedLineEnding.equals(lastWhitespace)) {
+
+
+
+ NewlineAtEndOfFile.java
+ org.checkstyle.autofix.recipe.NewlineAtEndOfFile$NewLineAtEndOfFileVisitor
+ visit
+ org.pitest.mutationtest.engine.gregor.mutators.RemoveConditionalMutator_EQUAL_IF
+ removed conditional - replaced equality check with true
+ if (!expectedLineEnding.equals(lastWhitespace)) {
+
+
+
+ NewlineAtEndOfFile.java
+ org.checkstyle.autofix.recipe.NewlineAtEndOfFile$NewLineAtEndOfFileVisitor
+ visit
+ org.pitest.mutationtest.engine.gregor.mutators.RemoveConditionalMutator_EQUAL_IF
+ removed conditional - replaced equality check with true
+ if (hasViolation(filePath)) {
+
+
+
+ NewlineAtEndOfFile.java
+ org.checkstyle.autofix.recipe.NewlineAtEndOfFile$NewLineAtEndOfFileVisitor
+ visit
+ org.pitest.mutationtest.engine.gregor.mutators.RemoveConditionalMutator_EQUAL_IF
+ removed conditional - replaced equality check with true
+ if (tree instanceof JavaSourceFile sourceFile) {
+
+
PositionHelper.java
org.checkstyle.autofix.PositionHelper
diff --git a/pom.xml b/pom.xml
index fcd7ed08..a0da0080 100644
--- a/pom.xml
+++ b/pom.xml
@@ -341,8 +341,8 @@
10
50000
- 85
- 90
+ 84
+ 89
diff --git a/src/main/java/org/checkstyle/autofix/CheckstyleCheck.java b/src/main/java/org/checkstyle/autofix/CheckstyleCheck.java
index fef6b723..946a2d3d 100644
--- a/src/main/java/org/checkstyle/autofix/CheckstyleCheck.java
+++ b/src/main/java/org/checkstyle/autofix/CheckstyleCheck.java
@@ -23,6 +23,7 @@
public enum CheckstyleCheck {
FINAL_LOCAL_VARIABLE("com.puppycrawl.tools.checkstyle.checks.coding.FinalLocalVariableCheck"),
HEADER("com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck"),
+ NEWLINE_AT_END_OF_FILE("com.puppycrawl.tools.checkstyle.checks.NewlineAtEndOfFileCheck"),
UPPER_ELL("com.puppycrawl.tools.checkstyle.checks.UpperEllCheck"),
HEX_LITERAL_CASE("com.puppycrawl.tools.checkstyle.checks.HexLiteralCaseCheck"),
REDUNDANT_IMPORT("com.puppycrawl.tools.checkstyle.checks.imports.RedundantImportCheck");
diff --git a/src/main/java/org/checkstyle/autofix/CheckstyleRecipeRegistry.java b/src/main/java/org/checkstyle/autofix/CheckstyleRecipeRegistry.java
index 997be2b1..5d2c299a 100644
--- a/src/main/java/org/checkstyle/autofix/CheckstyleRecipeRegistry.java
+++ b/src/main/java/org/checkstyle/autofix/CheckstyleRecipeRegistry.java
@@ -30,6 +30,7 @@
import org.checkstyle.autofix.recipe.FinalLocalVariable;
import org.checkstyle.autofix.recipe.Header;
import org.checkstyle.autofix.recipe.HexLiteralCase;
+import org.checkstyle.autofix.recipe.NewlineAtEndOfFile;
import org.checkstyle.autofix.recipe.RedundantImport;
import org.checkstyle.autofix.recipe.UpperEll;
import org.openrewrite.Recipe;
@@ -48,6 +49,7 @@ public final class CheckstyleRecipeRegistry {
RECIPE_MAP.put(CheckstyleCheck.HEX_LITERAL_CASE, HexLiteralCase::new);
RECIPE_MAP.put(CheckstyleCheck.FINAL_LOCAL_VARIABLE, FinalLocalVariable::new);
RECIPE_MAP_WITH_CONFIG.put(CheckstyleCheck.HEADER, Header::new);
+ RECIPE_MAP_WITH_CONFIG.put(CheckstyleCheck.NEWLINE_AT_END_OF_FILE, NewlineAtEndOfFile::new);
RECIPE_MAP.put(CheckstyleCheck.REDUNDANT_IMPORT, RedundantImport::new);
}
diff --git a/src/main/java/org/checkstyle/autofix/recipe/Header.java b/src/main/java/org/checkstyle/autofix/recipe/Header.java
index 0b07f949..06fdc41c 100644
--- a/src/main/java/org/checkstyle/autofix/recipe/Header.java
+++ b/src/main/java/org/checkstyle/autofix/recipe/Header.java
@@ -39,7 +39,7 @@ public class Header extends Recipe {
private static final String HEADER_PROPERTY = "header";
private static final String HEADER_FILE_PROPERTY = "headerFile";
private static final String CHARSET_PROPERTY = "charset";
- private static final String LINE_SEPARATOR = "\n";
+ private static final String LINE_SEPARATOR = System.lineSeparator();
private final List violations;
private final CheckConfiguration config;
@@ -75,7 +75,7 @@ private static String extractLicenseHeader(CheckConfiguration config) {
.getPropertyOrDefault(CHARSET_PROPERTY, Charset.defaultCharset().name()));
final String headerFilePath = config.getProperty(HEADER_FILE_PROPERTY);
try {
- header = toLfLineEnding(Files.readString(Path.of(headerFilePath), charsetToUse));
+ header = Files.readString(Path.of(headerFilePath), charsetToUse);
}
catch (IOException exception) {
throw new IllegalArgumentException("Failed to extract header from config",
@@ -85,10 +85,6 @@ private static String extractLicenseHeader(CheckConfiguration config) {
return header;
}
- private static String toLfLineEnding(String text) {
- return text.replaceAll("(?x)\\\\r(?=\\\\n)|\\r(?=\\n)", "");
- }
-
private static class HeaderVisitor extends JavaIsoVisitor {
private final List violations;
private final String licenseHeader;
@@ -121,8 +117,7 @@ public J visit(Tree tree, ExecutionContext executionContext) {
private String extractCurrentHeader(JavaSourceFile sourceFile) {
return sourceFile.getComments().stream()
.map(comment -> {
- return comment.printComment(getCursor())
- + toLfLineEnding(comment.getSuffix());
+ return comment.printComment(getCursor()) + comment.getSuffix();
})
.collect(Collectors.joining(""));
}
diff --git a/src/main/java/org/checkstyle/autofix/recipe/NewlineAtEndOfFile.java b/src/main/java/org/checkstyle/autofix/recipe/NewlineAtEndOfFile.java
new file mode 100644
index 00000000..7b5f1a4c
--- /dev/null
+++ b/src/main/java/org/checkstyle/autofix/recipe/NewlineAtEndOfFile.java
@@ -0,0 +1,151 @@
+///////////////////////////////////////////////////////////////////////////////////////////////
+// checkstyle-openrewrite-recipes: Automatically fix Checkstyle violations with OpenRewrite.
+// Copyright (C) 2025 The Checkstyle OpenRewrite Recipes Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.checkstyle.autofix.recipe;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.UnaryOperator;
+
+import org.checkstyle.autofix.parser.CheckConfiguration;
+import org.checkstyle.autofix.parser.CheckstyleViolation;
+import org.openrewrite.ExecutionContext;
+import org.openrewrite.Recipe;
+import org.openrewrite.Tree;
+import org.openrewrite.TreeVisitor;
+import org.openrewrite.java.JavaIsoVisitor;
+import org.openrewrite.java.format.AutodetectGeneralFormatStyle;
+import org.openrewrite.java.tree.Comment;
+import org.openrewrite.java.tree.J;
+import org.openrewrite.java.tree.JavaSourceFile;
+import org.openrewrite.java.tree.Space;
+import org.openrewrite.style.GeneralFormatStyle;
+import org.openrewrite.style.Style;
+
+public class NewlineAtEndOfFile extends Recipe {
+
+ private final List violations;
+ private final CheckConfiguration config;
+
+ public NewlineAtEndOfFile(List violations, CheckConfiguration config) {
+ this.violations = violations;
+ this.config = config;
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "End files with a single newline";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Some tools work better when files end with an empty line.";
+ }
+
+ @Override
+ public TreeVisitor, ExecutionContext> getVisitor() {
+ final String lineSeparator = config.getProperty("lineSeparator");
+ return new NewLineAtEndOfFileVisitor(violations, lineSeparator);
+ }
+
+ private static class NewLineAtEndOfFileVisitor extends JavaIsoVisitor {
+ private final List violations;
+ private final String lineSeparatorConfig;
+
+ NewLineAtEndOfFileVisitor(List violations,
+ String lineSeparatorConfig) {
+ this.violations = violations;
+ this.lineSeparatorConfig = lineSeparatorConfig.toLowerCase();
+ }
+
+ @Override
+ public J visit(Tree tree, ExecutionContext executionContext) {
+ J result = (J) tree;
+
+ if (tree instanceof JavaSourceFile sourceFile) {
+
+ final Path filePath = sourceFile.getSourcePath().toAbsolutePath();
+
+ if (hasViolation(filePath)) {
+ final String expectedLineEnding = determineLineEnding(sourceFile);
+
+ final Space eof = sourceFile.getEof();
+ final String lastWhitespace = eof.getLastWhitespace();
+
+ if (!expectedLineEnding.equals(lastWhitespace)) {
+ final List comments = eof.getComments();
+ if (comments.isEmpty()) {
+ result = sourceFile.withEof(Space.format(expectedLineEnding));
+ }
+ else {
+ result = sourceFile.withEof(sourceFile.getEof().withComments(mapLast(
+ comments, comment -> comment.withSuffix(expectedLineEnding))));
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ private String determineLineEnding(JavaSourceFile sourceFile) {
+ return switch (lineSeparatorConfig) {
+ case "lf" -> "\n";
+ case "crlf" -> "\r\n";
+ case "cr" -> "\r";
+ case "system" -> System.lineSeparator();
+ case "lf_cr_crlf" -> getAutodetectedLineEnding(sourceFile);
+ default -> {
+ throw new IllegalStateException("Unexpected value: " + lineSeparatorConfig);
+ }
+ };
+ }
+
+ private String getAutodetectedLineEnding(JavaSourceFile sourceFile) {
+ final GeneralFormatStyle generalFormatStyle =
+ Style.from(GeneralFormatStyle.class, sourceFile, () -> {
+ return AutodetectGeneralFormatStyle
+ .autodetectGeneralFormatStyle(sourceFile);
+ });
+ return generalFormatStyle.newLine();
+ }
+
+ private static List mapLast(List comments,
+ UnaryOperator mapper) {
+ List result = comments;
+
+ if (comments != null && !comments.isEmpty()) {
+ final int lastIndex = comments.size() - 1;
+ final Comment last = comments.get(lastIndex);
+ final Comment newLast = mapper.apply(last);
+
+ if (last != newLast) {
+ result = new ArrayList<>(comments);
+ result.set(lastIndex, newLast);
+ }
+ }
+
+ return result;
+ }
+
+ private boolean hasViolation(Path filePath) {
+ return violations.stream()
+ .anyMatch(violation -> violation.getFilePath().endsWith(filePath));
+ }
+ }
+}
diff --git a/src/test/java/org/checkstyle/autofix/generator/GenerateDiffFilesTest.java b/src/test/java/org/checkstyle/autofix/generator/GenerateDiffFilesTest.java
index a0d3d90e..90dea49f 100644
--- a/src/test/java/org/checkstyle/autofix/generator/GenerateDiffFilesTest.java
+++ b/src/test/java/org/checkstyle/autofix/generator/GenerateDiffFilesTest.java
@@ -84,6 +84,7 @@ private void createDiff(Path inputFile) throws IOException, InterruptedException
}
final String diff = out.toString();
- Files.writeString(diffFile, diff);
+ final String normalizedDiff = diff.replaceAll("\\r\\n?", "\n");
+ Files.writeString(diffFile, normalizedDiff);
}
}
diff --git a/src/test/java/org/checkstyle/autofix/recipe/AbstractRecipeTestSupport.java b/src/test/java/org/checkstyle/autofix/recipe/AbstractRecipeTestSupport.java
index 8fcb4e0f..5d4509ab 100644
--- a/src/test/java/org/checkstyle/autofix/recipe/AbstractRecipeTestSupport.java
+++ b/src/test/java/org/checkstyle/autofix/recipe/AbstractRecipeTestSupport.java
@@ -100,8 +100,9 @@ private void verify(Configuration config, Path reportPath, String inputPath, Str
try {
final Recipe mainRecipe = new CheckstyleAutoFix(reportPath.toString(),
configPath.toString());
- final String beforeCode = readFile(getPath(inputPath));
- final String expectedAfterCode = readFile(getPath(outputPath));
+ final String beforeCode = Files.readString(Path.of(getPath(inputPath)));
+ final String expectedAfterCode = Files.readString(Path.of(getPath(outputPath)));
+
testRecipe(beforeCode, expectedAfterCode,
getPath(inputPath), new InputClassRenamer(),
mainRecipe, new RemoveViolationComments());
@@ -194,8 +195,14 @@ private Configuration getCheckConfigurations(String inputPath) throws Exception
private String[] convertToExpectedMessages(List violations) {
return violations.stream()
.map(violation -> {
- return violation.getLine() + ":"
- + violation.getColumn() + ": " + violation.getMessage();
+ String message = violation.getLine() + ":";
+ if (violation.getColumn() != -1) {
+ message += violation.getColumn() + ": ";
+ }
+ else {
+ message += " ";
+ }
+ return message + violation.getMessage();
})
.toArray(String[]::new);
}
@@ -205,7 +212,7 @@ private void testRecipe(String beforeCode, String expectedAfterCode,
assertDoesNotThrow(() -> {
rewriteRun(
spec -> spec.recipes(recipes),
- java(beforeCode, expectedAfterCode, spec -> spec.path(filePath))
+ java(beforeCode, expectedAfterCode, spec -> spec.path(filePath).noTrim())
);
});
}
diff --git a/src/test/java/org/checkstyle/autofix/recipe/NewLineAtEndOfFileTest.java b/src/test/java/org/checkstyle/autofix/recipe/NewLineAtEndOfFileTest.java
new file mode 100644
index 00000000..9a5d0736
--- /dev/null
+++ b/src/test/java/org/checkstyle/autofix/recipe/NewLineAtEndOfFileTest.java
@@ -0,0 +1,47 @@
+///////////////////////////////////////////////////////////////////////////////////////////////
+// checkstyle-openrewrite-recipes: Automatically fix Checkstyle violations with OpenRewrite.
+// Copyright (C) 2025 The Checkstyle OpenRewrite Recipes Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.checkstyle.autofix.recipe;
+
+import org.checkstyle.autofix.parser.ReportParser;
+
+public class NewLineAtEndOfFileTest extends AbstractRecipeTestSupport {
+ @Override
+ protected String getSubpackage() {
+ return "newlineatendoffile";
+ }
+
+ @RecipeTest
+ void newLineCrlf(ReportParser parser) throws Exception {
+ verify(parser, "NewlineAtEndOfFileCrlf");
+ }
+
+ @RecipeTest
+ void noNewLine(ReportParser parser) throws Exception {
+ verify(parser, "NewlineAtEndOfFileNoNewLine");
+ }
+
+ @RecipeTest
+ void newLineComment(ReportParser parser) throws Exception {
+ verify(parser, "NewlineAtEndOfFileComment");
+ }
+
+ @RecipeTest
+ void newLineLf(ReportParser parser) throws Exception {
+ verify(parser, "NewlineAtEndOfFileLf");
+ }
+}
diff --git a/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilecomment/DiffNewlineAtEndOfFileComment.diff b/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilecomment/DiffNewlineAtEndOfFileComment.diff
new file mode 100644
index 00000000..37ac71de
--- /dev/null
+++ b/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilecomment/DiffNewlineAtEndOfFileComment.diff
@@ -0,0 +1,15 @@
+--- src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilecomment/InputNewlineAtEndOfFileComment.java
++++ src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilecomment/OutputNewlineAtEndOfFileComment.java
+@@ -4,9 +4,8 @@
+ fileExtensions = (default)""
+
+ */
+-// violation 6 lines above 'File does not end with a newline.'
+
+ package org.checkstyle.autofix.recipe.newlineatendoffile.newlineatendoffilecomment;
+
+-public class InputNewlineAtEndOfFileComment {
+-} // trailing comment
+\ No newline at end of file
++public class OutputNewlineAtEndOfFileComment {
++} // trailing comment
diff --git a/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilecomment/InputNewlineAtEndOfFileComment.java b/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilecomment/InputNewlineAtEndOfFileComment.java
new file mode 100644
index 00000000..95665756
--- /dev/null
+++ b/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilecomment/InputNewlineAtEndOfFileComment.java
@@ -0,0 +1,12 @@
+/*
+com.puppycrawl.tools.checkstyle.checks.NewlineAtEndOfFileCheck
+lineSeparator = LF
+fileExtensions = (default)""
+
+*/
+// violation 6 lines above 'File does not end with a newline.'
+
+package org.checkstyle.autofix.recipe.newlineatendoffile.newlineatendoffilecomment;
+
+public class InputNewlineAtEndOfFileComment {
+} // trailing comment
\ No newline at end of file
diff --git a/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilecomment/OutputNewlineAtEndOfFileComment.java b/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilecomment/OutputNewlineAtEndOfFileComment.java
new file mode 100644
index 00000000..91bdf202
--- /dev/null
+++ b/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilecomment/OutputNewlineAtEndOfFileComment.java
@@ -0,0 +1,11 @@
+/*
+com.puppycrawl.tools.checkstyle.checks.NewlineAtEndOfFileCheck
+lineSeparator = LF
+fileExtensions = (default)""
+
+*/
+
+package org.checkstyle.autofix.recipe.newlineatendoffile.newlineatendoffilecomment;
+
+public class OutputNewlineAtEndOfFileComment {
+} // trailing comment
diff --git a/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilecrlf/DiffNewlineAtEndOfFileCrlf.diff b/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilecrlf/DiffNewlineAtEndOfFileCrlf.diff
new file mode 100644
index 00000000..dc06e3d5
--- /dev/null
+++ b/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilecrlf/DiffNewlineAtEndOfFileCrlf.diff
@@ -0,0 +1,14 @@
+--- src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilecrlf/InputNewlineAtEndOfFileCrlf.java
++++ src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilecrlf/OutputNewlineAtEndOfFileCrlf.java
+@@ -4,9 +4,8 @@
+ fileExtensions = (default)""
+
+ */
+-// violation 6 lines above 'Expected line ending for file is LF(\\n), but CRLF(\\r\\n) is detected.'
+
+ package org.checkstyle.autofix.recipe.newlineatendoffile.newlineatendoffilecrlf;
+
+-public class InputNewlineAtEndOfFileCrlf {
+-}
++public class OutputNewlineAtEndOfFileCrlf {
++}
diff --git a/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilecrlf/InputNewlineAtEndOfFileCrlf.java b/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilecrlf/InputNewlineAtEndOfFileCrlf.java
new file mode 100644
index 00000000..9efa4c33
--- /dev/null
+++ b/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilecrlf/InputNewlineAtEndOfFileCrlf.java
@@ -0,0 +1,12 @@
+/*
+com.puppycrawl.tools.checkstyle.checks.NewlineAtEndOfFileCheck
+lineSeparator = lf
+fileExtensions = (default)""
+
+*/
+// violation 6 lines above 'Expected line ending for file is LF(\\n), but CRLF(\\r\\n) is detected.'
+
+package org.checkstyle.autofix.recipe.newlineatendoffile.newlineatendoffilecrlf;
+
+public class InputNewlineAtEndOfFileCrlf {
+}
diff --git a/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilecrlf/OutputNewlineAtEndOfFileCrlf.java b/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilecrlf/OutputNewlineAtEndOfFileCrlf.java
new file mode 100644
index 00000000..e5eab490
--- /dev/null
+++ b/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilecrlf/OutputNewlineAtEndOfFileCrlf.java
@@ -0,0 +1,11 @@
+/*
+com.puppycrawl.tools.checkstyle.checks.NewlineAtEndOfFileCheck
+lineSeparator = lf
+fileExtensions = (default)""
+
+*/
+
+package org.checkstyle.autofix.recipe.newlineatendoffile.newlineatendoffilecrlf;
+
+public class OutputNewlineAtEndOfFileCrlf {
+}
diff --git a/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilelf/DiffNewlineAtEndOfFileLf.diff b/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilelf/DiffNewlineAtEndOfFileLf.diff
new file mode 100644
index 00000000..fa1a8191
--- /dev/null
+++ b/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilelf/DiffNewlineAtEndOfFileLf.diff
@@ -0,0 +1,14 @@
+--- src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilelf/InputNewlineAtEndOfFileLf.java
++++ src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilelf/OutputNewlineAtEndOfFileLf.java
+@@ -4,9 +4,8 @@
+ fileExtensions = (default)""
+
+ */
+-// violation 6 lines above 'File does not end with a newline.'
+
+ package org.checkstyle.autofix.recipe.newlineatendoffile.newlineatendoffilelf;
+
+-public class InputNewlineAtEndOfFileLf {
+-}
++public class OutputNewlineAtEndOfFileLf {
++}
diff --git a/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilelf/InputNewlineAtEndOfFileLf.java b/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilelf/InputNewlineAtEndOfFileLf.java
new file mode 100644
index 00000000..42a78e30
--- /dev/null
+++ b/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilelf/InputNewlineAtEndOfFileLf.java
@@ -0,0 +1,12 @@
+/*
+com.puppycrawl.tools.checkstyle.checks.NewlineAtEndOfFileCheck
+lineSeparator = crlf
+fileExtensions = (default)""
+
+*/
+// violation 6 lines above 'File does not end with a newline.'
+
+package org.checkstyle.autofix.recipe.newlineatendoffile.newlineatendoffilelf;
+
+public class InputNewlineAtEndOfFileLf {
+}
diff --git a/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilelf/OutputNewlineAtEndOfFileLf.java b/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilelf/OutputNewlineAtEndOfFileLf.java
new file mode 100644
index 00000000..0f7657d5
--- /dev/null
+++ b/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilelf/OutputNewlineAtEndOfFileLf.java
@@ -0,0 +1,11 @@
+/*
+com.puppycrawl.tools.checkstyle.checks.NewlineAtEndOfFileCheck
+lineSeparator = crlf
+fileExtensions = (default)""
+
+*/
+
+package org.checkstyle.autofix.recipe.newlineatendoffile.newlineatendoffilelf;
+
+public class OutputNewlineAtEndOfFileLf {
+}
diff --git a/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilenonewline/DiffNewlineAtEndOfFileNoNewLine.diff b/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilenonewline/DiffNewlineAtEndOfFileNoNewLine.diff
new file mode 100644
index 00000000..2cf2734b
--- /dev/null
+++ b/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilenonewline/DiffNewlineAtEndOfFileNoNewLine.diff
@@ -0,0 +1,16 @@
+--- src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilenonewline/InputNewlineAtEndOfFileNoNewLine.java
++++ src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilenonewline/OutputNewlineAtEndOfFileNoNewLine.java
+@@ -4,10 +4,9 @@
+ fileExtensions = (default)""
+
+ */
+-// violation 6 lines above 'File does not end with a newline.'
+
+ package org.checkstyle.autofix.recipe.newlineatendoffile.newlineatendoffilenonewline;
+
+-public interface InputNewlineAtEndOfFileNoNewLine
++public interface OutputNewlineAtEndOfFileNoNewLine
+ {
+-}
+\ No newline at end of file
++}
diff --git a/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilenonewline/InputNewlineAtEndOfFileNoNewLine.java b/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilenonewline/InputNewlineAtEndOfFileNoNewLine.java
new file mode 100644
index 00000000..b011ee15
--- /dev/null
+++ b/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilenonewline/InputNewlineAtEndOfFileNoNewLine.java
@@ -0,0 +1,13 @@
+/*
+com.puppycrawl.tools.checkstyle.checks.NewlineAtEndOfFileCheck
+lineSeparator = LF
+fileExtensions = (default)""
+
+*/
+// violation 6 lines above 'File does not end with a newline.'
+
+package org.checkstyle.autofix.recipe.newlineatendoffile.newlineatendoffilenonewline;
+
+public interface InputNewlineAtEndOfFileNoNewLine
+{
+}
\ No newline at end of file
diff --git a/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilenonewline/OutputNewlineAtEndOfFileNoNewLine.java b/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilenonewline/OutputNewlineAtEndOfFileNoNewLine.java
new file mode 100644
index 00000000..b384b3a0
--- /dev/null
+++ b/src/test/resources/org/checkstyle/autofix/recipe/newlineatendoffile/newlineatendoffilenonewline/OutputNewlineAtEndOfFileNoNewLine.java
@@ -0,0 +1,12 @@
+/*
+com.puppycrawl.tools.checkstyle.checks.NewlineAtEndOfFileCheck
+lineSeparator = LF
+fileExtensions = (default)""
+
+*/
+
+package org.checkstyle.autofix.recipe.newlineatendoffile.newlineatendoffilenonewline;
+
+public interface OutputNewlineAtEndOfFileNoNewLine
+{
+}