Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions src/main/java/org/checkstyle/autofix/CheckstyleCheck.java
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}

Expand Down
11 changes: 3 additions & 8 deletions src/main/java/org/checkstyle/autofix/recipe/Header.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<CheckstyleViolation> violations;
private final CheckConfiguration config;
Expand Down Expand Up @@ -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",
Expand All @@ -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<ExecutionContext> {
private final List<CheckstyleViolation> violations;
private final String licenseHeader;
Expand Down Expand Up @@ -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(""));
}
Expand Down
154 changes: 154 additions & 0 deletions src/main/java/org/checkstyle/autofix/recipe/NewlineAtEndOfFile.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
///////////////////////////////////////////////////////////////////////////////////////////////
// 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<CheckstyleViolation> violations;
private final CheckConfiguration config;

public NewlineAtEndOfFile(List<CheckstyleViolation> 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<ExecutionContext> {
private final List<CheckstyleViolation> violations;
private final String lineSeparatorConfig;

NewLineAtEndOfFileVisitor(List<CheckstyleViolation> violations,
String lineSeparatorConfig) {
this.violations = violations;
this.lineSeparatorConfig = lineSeparatorConfig;
}

@Override
public J visit(Tree tree, ExecutionContext executionContext) {
J result = (J) tree;

if (tree instanceof JavaSourceFile) {
final JavaSourceFile sourceFile = (JavaSourceFile) tree;

final Path filePath = sourceFile.getSourcePath().toAbsolutePath();

if (hasViolation(filePath)) {
final String lineEnding = determineLineEnding(sourceFile);

final Space eof = sourceFile.getEof();
final String lastWhitespace = eof.getLastWhitespace();

if (!lineEnding.equals(lastWhitespace)) {
final List<Comment> comments = eof.getComments();
if (comments.isEmpty()) {
result = sourceFile.withEof(Space.format(lineEnding));
}
else {
result = sourceFile.withEof(sourceFile.getEof().withComments(
mapLast(comments, comment -> comment.withSuffix(lineEnding))));
}
}
}
}

return result;
}

private String determineLineEnding(JavaSourceFile sourceFile) {
return switch (lineSeparatorConfig.toLowerCase()) {
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.toLowerCase());
}
};
}

private String getAutodetectedLineEnding(JavaSourceFile sourceFile) {
final GeneralFormatStyle generalFormatStyle =
Style.from(GeneralFormatStyle.class, sourceFile, () -> {
return AutodetectGeneralFormatStyle
.autodetectGeneralFormatStyle(sourceFile);
});
return generalFormatStyle.newLine();
}

private static List<Comment> mapLast(List<Comment> comments,
UnaryOperator<Comment> mapper) {
List<Comment> 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.removeIf(violation -> {
return violation.getFilePath().endsWith(filePath);
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -194,8 +195,14 @@ private Configuration getCheckConfigurations(String inputPath) throws Exception
private String[] convertToExpectedMessages(List<CheckstyleViolation> 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);
}
Expand All @@ -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())
);
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
///////////////////////////////////////////////////////////////////////////////////////////////
// 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");
}
}
Original file line number Diff line number Diff line change
@@ -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 {
+}
Original file line number Diff line number Diff line change
@@ -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 {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
com.puppycrawl.tools.checkstyle.checks.NewlineAtEndOfFileCheck
lineSeparator = lf
fileExtensions = (default)""

*/

package org.checkstyle.autofix.recipe.newlineatendoffile.newlineatendoffilecrlf;

public class OutputNewlineAtEndOfFileCrlf {
}
Original file line number Diff line number Diff line change
@@ -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
+}
Original file line number Diff line number Diff line change
@@ -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
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
com.puppycrawl.tools.checkstyle.checks.NewlineAtEndOfFileCheck
lineSeparator = LF
fileExtensions = (default)""

*/

package org.checkstyle.autofix.recipe.newlineatendoffile.newlineatendoffilenonewline;

public interface OutputNewlineAtEndOfFileNoNewLine
{
}