From e44435be4a3ee8d0363e2c6de701fc74910525d3 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Fri, 9 May 2025 22:24:52 +0900 Subject: [PATCH 01/19] feat: Implement conversion of diff content to ReviewDog format Related issue: #655 --- .../gradle/spotless/SpotlessDiff.java | 112 ++++++++++++++++++ .../gradle/spotless/SpotlessDiffTask.java | 88 ++++++++++++++ .../gradle/spotless/SpotlessExtension.java | 2 + .../spotless/SpotlessExtensionImpl.java | 14 ++- 4 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessDiff.java create mode 100644 plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessDiffTask.java diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessDiff.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessDiff.java new file mode 100644 index 0000000000..249ebba764 --- /dev/null +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessDiff.java @@ -0,0 +1,112 @@ +package com.diffplug.gradle.spotless; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.diffplug.spotless.Formatter; +import com.diffplug.spotless.extra.integration.DiffMessageFormatter; + +/** + * SpotlessDiff generates diffs between original and formatted code + * and converts them to a format compatible with ReviewDog. + */ +public class SpotlessDiff { + private final Formatter formatter; + private final Path rootDir; + + private static final Pattern HUNK_PATTERN = Pattern.compile("@@ -(\\d+),(\\d+) \\+(\\d+),(\\d+) @@"); + private static final Pattern LINE_PATTERN = Pattern.compile("^([+-])(.*)$", Pattern.MULTILINE); + + public SpotlessDiff(Formatter formatter) { + this.formatter = formatter; + this.rootDir = Paths.get("."); + } + + /** + * Generates a ReviewDog-compatible diff for a file + */ + public String generateDiff(File file) throws IOException { + Map.Entry diffResult = + DiffMessageFormatter.diff(rootDir, formatter, file); + + int firstDiffLine = diffResult.getKey(); + String diffContent = diffResult.getValue(); + + return convertToReviewDogFormat(file.getPath(), firstDiffLine, diffContent); + } + + /** + * Generates diffs for multiple files + */ + public String generateDiffs(List files) throws IOException { + List diagnostics = new ArrayList<>(); + for (File file : files) { + String diff = generateDiff(file); + if (diff.startsWith("[") && diff.endsWith("]")) { + diff = diff.substring(1, diff.length() - 1); + if (!diff.isEmpty()) { + diagnostics.add(diff); + } + } + } + return "[" + String.join(",", diagnostics) + "]"; + } + + /** + * Converts the diff content to ReviewDog format + */ + private String convertToReviewDogFormat(String filePath, int firstDiffLine, String diffContent) { + List diagnostics = new ArrayList<>(); + + Matcher hunkMatcher = HUNK_PATTERN.matcher(diffContent); + + while (hunkMatcher.find()) { + int beginLine = Integer.parseInt(hunkMatcher.group(1)); + int lineCount = Integer.parseInt(hunkMatcher.group(2)); + int endLine = beginLine + lineCount - 1; + + StringBuilder suggestionText = new StringBuilder(); + Matcher lineMatcher = LINE_PATTERN.matcher(diffContent); + + while (lineMatcher.find()) { + char type = lineMatcher.group(1).charAt(0); + String lineContent = lineMatcher.group(2); + + if (type == '+') { + if (suggestionText.length() > 0) { + suggestionText.append("\n"); + } + suggestionText.append(lineContent); + } + } + + String escapedSuggestion = escapeJson(suggestionText.toString()); + + String diagnostic = String.format( + "{\"message\":\"Code formatting suggestion\",\"location\":{\"path\":\"%s\",\"range\":{\"start\":{\"line\":%d,\"column\":1},\"end\":{\"line\":%d,\"column\":1}}},\"severity\":\"WARNING\",\"source\":{\"name\":\"spotless\"},\"suggestions\":[{\"range\":{\"start\":{\"line\":%d,\"column\":1},\"end\":{\"line\":%d,\"column\":1}},\"text\":\"%s\"}]}", + filePath, beginLine, endLine, beginLine, endLine, escapedSuggestion + ); + + diagnostics.add(diagnostic); + } + + return "[" + String.join(",", diagnostics) + "]"; + } + + private String escapeJson(String str) { + return str.replace("\\", "\\\\") + .replace("\"", "\\\"") + .replace("\b", "\\b") + .replace("\f", "\\f") + .replace("\n", "\\n") + .replace("\r", "\\r") + .replace("\t", "\\t"); + } +} diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessDiffTask.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessDiffTask.java new file mode 100644 index 0000000000..9057afac69 --- /dev/null +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessDiffTask.java @@ -0,0 +1,88 @@ +/* + * Copyright 2016-2024 DiffPlug + * + * 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 com.diffplug.gradle.spotless; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; +import java.util.stream.Collectors; + +import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; +import org.gradle.api.file.FileCollection; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.TaskAction; + +import com.diffplug.spotless.Formatter; + +/** + * Task for generating diffs in ReviewDog format + */ +public abstract class SpotlessDiffTask extends DefaultTask { + private final Formatter formatter; + private FileCollection sources; + private File outputFile; + + public SpotlessDiffTask(Formatter formatter) { + this.formatter = formatter; + setGroup("verification"); + setDescription("Generates diffs between original and formatted code for ReviewDog"); + } + + @InputFiles + public FileCollection getSources() { + return sources; + } + + public void setSources(FileCollection sources) { + this.sources = sources; + } + + @OutputFile + public File getOutputFile() { + return outputFile; + } + + public void setOutputFile(File outputFile) { + this.outputFile = outputFile; + } + + @TaskAction + public void generateDiff() { + try { + List files = sources.getFiles().stream() + .filter(File::isFile) + .collect(Collectors.toList()); + + SpotlessDiff spotlessDiff = new SpotlessDiff(formatter); + String diffs = spotlessDiff.generateDiffs(files); + + if (!outputFile.getParentFile().exists()) { + outputFile.getParentFile().mkdirs(); + } + + Files.writeString(outputFile.toPath(), diffs); + + getLogger().lifecycle("Generated ReviewDog compatible diff at: {}", + outputFile.getAbsolutePath()); + + } catch (IOException e) { + throw new GradleException("Failed to generate diff", e); + } + } +} diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java index e883953eaa..c27c974fb0 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java @@ -40,12 +40,14 @@ public abstract class SpotlessExtension { protected static final String TASK_GROUP = LifecycleBasePlugin.VERIFICATION_GROUP; protected static final String CHECK_DESCRIPTION = "Checks that sourcecode satisfies formatting steps."; protected static final String APPLY_DESCRIPTION = "Applies code formatting steps to sourcecode in-place."; + protected static final String DIFF_DESCRIPTION = "Shows the diff between the current source and the formatted source."; static final String EXTENSION = "spotless"; static final String EXTENSION_PREDECLARE = "spotlessPredeclare"; static final String CHECK = "Check"; static final String APPLY = "Apply"; static final String DIAGNOSE = "Diagnose"; + static final String DIFF = "Diff"; protected SpotlessExtension(Project project) { this.project = requireNonNull(project); diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionImpl.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionImpl.java index 75168f690a..82302b5e14 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionImpl.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionImpl.java @@ -23,7 +23,7 @@ import org.gradle.api.tasks.TaskProvider; public class SpotlessExtensionImpl extends SpotlessExtension { - final TaskProvider rootCheckTask, rootApplyTask, rootDiagnoseTask; + final TaskProvider rootCheckTask, rootApplyTask, rootDiagnoseTask, rootDiffTask; public SpotlessExtensionImpl(Project project) { super(project); @@ -39,6 +39,11 @@ public SpotlessExtensionImpl(Project project) { task.setGroup(TASK_GROUP); // no description on purpose }); + rootDiffTask = project.getTasks().register(EXTENSION + DIFF, task -> { + task.setGroup(TASK_GROUP); + task.setDescription(DIFF_DESCRIPTION); + }); + project.afterEvaluate(unused -> { if (enforceCheck) { project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME).configure(task -> task.dependsOn(rootCheckTask)); @@ -101,5 +106,12 @@ protected void createFormatTasks(String name, FormatExtension formatExtension) { task.mustRunAfter(BasePlugin.CLEAN_TASK_NAME); }); rootDiagnoseTask.configure(task -> task.dependsOn(diagnoseTask)); + + // TODO : This is initial version of the diff task, we should probably update the code + TaskProvider diffTask = tasks.register(taskName + DIFF, SpotlessDiffTask.class, task -> { + task.setGroup(TASK_GROUP); + task.mustRunAfter(BasePlugin.CLEAN_TASK_NAME); + }); + rootCheckTask.configure(task -> task.dependsOn(diffTask)); } } From 31ab0b9969f55f010da5b6699f93c4170350a898 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Fri, 9 May 2025 22:36:46 +0900 Subject: [PATCH 02/19] Add changes --- plugin-gradle/CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index d4658c1f8f..3d34da7595 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -6,6 +6,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ### Changed * Bump default `eclipse` version to latest `4.34` -> `4.35`. ([#2458](https://github.com/diffplug/spotless/pull/2458)) * Bump default `greclipse` version to latest `4.32` -> `4.35`. ([#2458](https://github.com/diffplug/spotless/pull/2458)) +* Implement conversion of diff content to ReviewDog format ([#2478](https://github.com/diffplug/spotless/pull/2478)) ## [7.0.3] - 2025-04-07 ### Changed From 10c23f45b80ee59ae5c93d5d4b3e3cfe789acfc6 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Fri, 9 May 2025 22:43:08 +0900 Subject: [PATCH 03/19] Add license --- .../diffplug/gradle/spotless/SpotlessDiff.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessDiff.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessDiff.java index 249ebba764..f6a7dab1ee 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessDiff.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessDiff.java @@ -1,3 +1,18 @@ +/* + * Copyright 2016-2024 DiffPlug + * + * 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 com.diffplug.gradle.spotless; import java.io.File; From 6d8b7e842dcf66568a7496d0fbf2cae60e593356 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Sun, 11 May 2025 12:53:30 +0900 Subject: [PATCH 04/19] Revert changes --- .../gradle/spotless/SpotlessDiff.java | 127 ------------------ .../gradle/spotless/SpotlessDiffTask.java | 88 ------------ .../gradle/spotless/SpotlessExtension.java | 2 - .../spotless/SpotlessExtensionImpl.java | 14 +- 4 files changed, 1 insertion(+), 230 deletions(-) delete mode 100644 plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessDiff.java delete mode 100644 plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessDiffTask.java diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessDiff.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessDiff.java deleted file mode 100644 index f6a7dab1ee..0000000000 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessDiff.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2016-2024 DiffPlug - * - * 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 com.diffplug.gradle.spotless; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import com.diffplug.spotless.Formatter; -import com.diffplug.spotless.extra.integration.DiffMessageFormatter; - -/** - * SpotlessDiff generates diffs between original and formatted code - * and converts them to a format compatible with ReviewDog. - */ -public class SpotlessDiff { - private final Formatter formatter; - private final Path rootDir; - - private static final Pattern HUNK_PATTERN = Pattern.compile("@@ -(\\d+),(\\d+) \\+(\\d+),(\\d+) @@"); - private static final Pattern LINE_PATTERN = Pattern.compile("^([+-])(.*)$", Pattern.MULTILINE); - - public SpotlessDiff(Formatter formatter) { - this.formatter = formatter; - this.rootDir = Paths.get("."); - } - - /** - * Generates a ReviewDog-compatible diff for a file - */ - public String generateDiff(File file) throws IOException { - Map.Entry diffResult = - DiffMessageFormatter.diff(rootDir, formatter, file); - - int firstDiffLine = diffResult.getKey(); - String diffContent = diffResult.getValue(); - - return convertToReviewDogFormat(file.getPath(), firstDiffLine, diffContent); - } - - /** - * Generates diffs for multiple files - */ - public String generateDiffs(List files) throws IOException { - List diagnostics = new ArrayList<>(); - for (File file : files) { - String diff = generateDiff(file); - if (diff.startsWith("[") && diff.endsWith("]")) { - diff = diff.substring(1, diff.length() - 1); - if (!diff.isEmpty()) { - diagnostics.add(diff); - } - } - } - return "[" + String.join(",", diagnostics) + "]"; - } - - /** - * Converts the diff content to ReviewDog format - */ - private String convertToReviewDogFormat(String filePath, int firstDiffLine, String diffContent) { - List diagnostics = new ArrayList<>(); - - Matcher hunkMatcher = HUNK_PATTERN.matcher(diffContent); - - while (hunkMatcher.find()) { - int beginLine = Integer.parseInt(hunkMatcher.group(1)); - int lineCount = Integer.parseInt(hunkMatcher.group(2)); - int endLine = beginLine + lineCount - 1; - - StringBuilder suggestionText = new StringBuilder(); - Matcher lineMatcher = LINE_PATTERN.matcher(diffContent); - - while (lineMatcher.find()) { - char type = lineMatcher.group(1).charAt(0); - String lineContent = lineMatcher.group(2); - - if (type == '+') { - if (suggestionText.length() > 0) { - suggestionText.append("\n"); - } - suggestionText.append(lineContent); - } - } - - String escapedSuggestion = escapeJson(suggestionText.toString()); - - String diagnostic = String.format( - "{\"message\":\"Code formatting suggestion\",\"location\":{\"path\":\"%s\",\"range\":{\"start\":{\"line\":%d,\"column\":1},\"end\":{\"line\":%d,\"column\":1}}},\"severity\":\"WARNING\",\"source\":{\"name\":\"spotless\"},\"suggestions\":[{\"range\":{\"start\":{\"line\":%d,\"column\":1},\"end\":{\"line\":%d,\"column\":1}},\"text\":\"%s\"}]}", - filePath, beginLine, endLine, beginLine, endLine, escapedSuggestion - ); - - diagnostics.add(diagnostic); - } - - return "[" + String.join(",", diagnostics) + "]"; - } - - private String escapeJson(String str) { - return str.replace("\\", "\\\\") - .replace("\"", "\\\"") - .replace("\b", "\\b") - .replace("\f", "\\f") - .replace("\n", "\\n") - .replace("\r", "\\r") - .replace("\t", "\\t"); - } -} diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessDiffTask.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessDiffTask.java deleted file mode 100644 index 9057afac69..0000000000 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessDiffTask.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2016-2024 DiffPlug - * - * 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 com.diffplug.gradle.spotless; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.List; -import java.util.stream.Collectors; - -import org.gradle.api.DefaultTask; -import org.gradle.api.GradleException; -import org.gradle.api.file.FileCollection; -import org.gradle.api.tasks.InputFiles; -import org.gradle.api.tasks.OutputFile; -import org.gradle.api.tasks.TaskAction; - -import com.diffplug.spotless.Formatter; - -/** - * Task for generating diffs in ReviewDog format - */ -public abstract class SpotlessDiffTask extends DefaultTask { - private final Formatter formatter; - private FileCollection sources; - private File outputFile; - - public SpotlessDiffTask(Formatter formatter) { - this.formatter = formatter; - setGroup("verification"); - setDescription("Generates diffs between original and formatted code for ReviewDog"); - } - - @InputFiles - public FileCollection getSources() { - return sources; - } - - public void setSources(FileCollection sources) { - this.sources = sources; - } - - @OutputFile - public File getOutputFile() { - return outputFile; - } - - public void setOutputFile(File outputFile) { - this.outputFile = outputFile; - } - - @TaskAction - public void generateDiff() { - try { - List files = sources.getFiles().stream() - .filter(File::isFile) - .collect(Collectors.toList()); - - SpotlessDiff spotlessDiff = new SpotlessDiff(formatter); - String diffs = spotlessDiff.generateDiffs(files); - - if (!outputFile.getParentFile().exists()) { - outputFile.getParentFile().mkdirs(); - } - - Files.writeString(outputFile.toPath(), diffs); - - getLogger().lifecycle("Generated ReviewDog compatible diff at: {}", - outputFile.getAbsolutePath()); - - } catch (IOException e) { - throw new GradleException("Failed to generate diff", e); - } - } -} diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java index c27c974fb0..e883953eaa 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java @@ -40,14 +40,12 @@ public abstract class SpotlessExtension { protected static final String TASK_GROUP = LifecycleBasePlugin.VERIFICATION_GROUP; protected static final String CHECK_DESCRIPTION = "Checks that sourcecode satisfies formatting steps."; protected static final String APPLY_DESCRIPTION = "Applies code formatting steps to sourcecode in-place."; - protected static final String DIFF_DESCRIPTION = "Shows the diff between the current source and the formatted source."; static final String EXTENSION = "spotless"; static final String EXTENSION_PREDECLARE = "spotlessPredeclare"; static final String CHECK = "Check"; static final String APPLY = "Apply"; static final String DIAGNOSE = "Diagnose"; - static final String DIFF = "Diff"; protected SpotlessExtension(Project project) { this.project = requireNonNull(project); diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionImpl.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionImpl.java index 82302b5e14..75168f690a 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionImpl.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionImpl.java @@ -23,7 +23,7 @@ import org.gradle.api.tasks.TaskProvider; public class SpotlessExtensionImpl extends SpotlessExtension { - final TaskProvider rootCheckTask, rootApplyTask, rootDiagnoseTask, rootDiffTask; + final TaskProvider rootCheckTask, rootApplyTask, rootDiagnoseTask; public SpotlessExtensionImpl(Project project) { super(project); @@ -39,11 +39,6 @@ public SpotlessExtensionImpl(Project project) { task.setGroup(TASK_GROUP); // no description on purpose }); - rootDiffTask = project.getTasks().register(EXTENSION + DIFF, task -> { - task.setGroup(TASK_GROUP); - task.setDescription(DIFF_DESCRIPTION); - }); - project.afterEvaluate(unused -> { if (enforceCheck) { project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME).configure(task -> task.dependsOn(rootCheckTask)); @@ -106,12 +101,5 @@ protected void createFormatTasks(String name, FormatExtension formatExtension) { task.mustRunAfter(BasePlugin.CLEAN_TASK_NAME); }); rootDiagnoseTask.configure(task -> task.dependsOn(diagnoseTask)); - - // TODO : This is initial version of the diff task, we should probably update the code - TaskProvider diffTask = tasks.register(taskName + DIFF, SpotlessDiffTask.class, task -> { - task.setGroup(TASK_GROUP); - task.mustRunAfter(BasePlugin.CLEAN_TASK_NAME); - }); - rootCheckTask.configure(task -> task.dependsOn(diffTask)); } } From 782bd945c3dd0b0128f29b02039325177d3b2678 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Sun, 11 May 2025 12:54:50 +0900 Subject: [PATCH 05/19] Apply comment --- plugin-gradle/CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index 3d34da7595..e7a1f192fb 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -6,6 +6,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ### Changed * Bump default `eclipse` version to latest `4.34` -> `4.35`. ([#2458](https://github.com/diffplug/spotless/pull/2458)) * Bump default `greclipse` version to latest `4.32` -> `4.35`. ([#2458](https://github.com/diffplug/spotless/pull/2458)) +### Added * Implement conversion of diff content to ReviewDog format ([#2478](https://github.com/diffplug/spotless/pull/2478)) ## [7.0.3] - 2025-04-07 From dc52b26fa9f66952d1a5371612c1dd809edbf376 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Sun, 11 May 2025 14:37:22 +0900 Subject: [PATCH 06/19] Add ReviewDogGenerator --- .../extra/middleware/ReviewDogGenerator.java | 322 ++++++++++++++++++ .../extra/middleware/package-info.java | 7 + .../java/com/diffplug/spotless/LintState.java | 4 + 3 files changed, 333 insertions(+) create mode 100644 lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/ReviewDogGenerator.java create mode 100644 lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/package-info.java diff --git a/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/ReviewDogGenerator.java b/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/ReviewDogGenerator.java new file mode 100644 index 0000000000..fcdac13d44 --- /dev/null +++ b/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/ReviewDogGenerator.java @@ -0,0 +1,322 @@ +/* + * Copyright 2016-2025 DiffPlug + * + * 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 com.diffplug.spotless.extra.middleware; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.diffplug.spotless.Formatter; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.Lint; +import com.diffplug.spotless.LineEnding; +import com.diffplug.spotless.LintState; +import com.diffplug.spotless.extra.integration.DiffMessageFormatter; + +/** + * ReviewDogGenerator generates ReviewDog formatted output for linting issues + * based on Spotless formatting results. + */ +public final class ReviewDogGenerator { + private final Formatter formatter; + private final File projectDir; + + /** + * Constructor for ReviewDogGenerator. + * + * @param projectDir the root directory of the project + * @param steps the list of FormatterStep to apply + */ + public ReviewDogGenerator(File projectDir, List steps) { + this.projectDir = projectDir; + this.formatter = Formatter.builder() + .encoding(StandardCharsets.UTF_8) + .lineEndingsPolicy(LineEnding.UNIX.createPolicy()) + .steps(steps) + .build(); + } + + /** + * Generates ReviewDog formatted output for the given list of files. + * + * @param files the list of files to check + * @return a String containing ReviewDog formatted lint messages with code suggestions + * @throws IOException if file reading fails + */ + public String generateReviewDogFormat(List files) throws IOException { + StringBuilder reviewDogOutput = new StringBuilder(); + + for (File file : files) { + LintState lintState = LintState.of(formatter, file); + + if (!lintState.getDirtyState().isClean()) { + String relativePath = getRelativePath(file); + + Map.Entry diffResult = DiffMessageFormatter.diff( + projectDir.toPath(), formatter, file); + + List hunks = parseDiffHunks(diffResult.getValue()); + List issues = processLints(lintState, hunks); + + for (ReviewDogIssue issue : issues) { + reviewDogOutput.append(formatReviewDogLine(relativePath, issue)); + } + } + } + + return reviewDogOutput.toString(); + } + + /** + * Converts an absolute file path to a relative path based on the project directory. + * + * @param file the file to convert + * @return relative path string + */ + private String getRelativePath(File file) { + return projectDir.toURI().relativize(file.toURI()).getPath(); + } + + /** + * Parses git-style diff content into structured diff hunks. + * + * @param diffContent the git-style diff content + * @return list of parsed DiffHunk objects + */ + private List parseDiffHunks(String diffContent) { + List hunks = new ArrayList<>(); + if (diffContent == null || diffContent.isEmpty()) { + return hunks; + } + + Pattern hunkHeaderPattern = Pattern.compile("@@ -(\\d+),(\\d+) \\+(\\d+),(\\d+) @@"); + String[] lines = diffContent.split("\n"); + + DiffHunk currentHunk = null; + StringBuilder hunkContent = new StringBuilder(); + + for (String line : lines) { + // Skip file header lines (--- and +++) + if (line.startsWith("---") || line.startsWith("+++")) { + continue; + } + + if (line.startsWith("@@")) { + if (currentHunk != null) { + currentHunk.content = hunkContent.toString().trim(); + hunks.add(currentHunk); + hunkContent = new StringBuilder(); + } + + Matcher matcher = hunkHeaderPattern.matcher(line); + if (matcher.find()) { + int originalStart = Integer.parseInt(matcher.group(1)); + int originalLength = Integer.parseInt(matcher.group(2)); + int newStart = Integer.parseInt(matcher.group(3)); + int newLength = Integer.parseInt(matcher.group(4)); + + currentHunk = new DiffHunk(originalStart, originalLength, newStart, newLength); + currentHunk.header = line; + } + hunkContent.append(line).append("\n"); + } else if (currentHunk != null) { + hunkContent.append(line).append("\n"); + } + } + + if (currentHunk != null) { + currentHunk.content = hunkContent.toString().trim(); + hunks.add(currentHunk); + } + + return hunks; + } + + + /** + * Processes lint information and associates them with appropriate diff hunks. + * + * @param lintState the LintState containing all lint information + * @param hunks list of diff hunks + * @return list of ReviewDogIssue + */ + private List processLints(LintState lintState, List hunks) { + List issues = new ArrayList<>(); + + List> lintsPerStep = lintState.getLintsPerStep(); + + for (List stepLints : Objects.requireNonNull(lintsPerStep)) { + for (Lint lint : stepLints) { + DiffHunk relevantHunk = findRelevantHunk(hunks, lint.getLineStart()); + + String suggestion = ""; + if (relevantHunk != null) { + suggestion = extractSuggestionFromHunk(relevantHunk); + } + + ReviewDogIssue issue = new ReviewDogIssue( + lint.getLineStart(), + 1, + lint.getShortCode() + ": " + lint.getDetail(), + suggestion); + + issues.add(issue); + } + } + + // If no specific lints were found but file is dirty, add a general formatting issue + if (issues.isEmpty() && !hunks.isEmpty()) { + DiffHunk firstHunk = hunks.get(0); + String suggestion = extractSuggestionFromHunk(firstHunk); + + issues.add(new ReviewDogIssue( + firstHunk.originalStart, + 1, + "General formatting issue: file needs to be reformatted.", + suggestion)); + } + + return issues; + } + + /** + * Finds the hunk that is relevant for a specific line number. + * + * @param hunks list of diff hunks + * @param lineNumber the line number to find (1-based) + * @return the relevant hunk or null if not found + */ + private DiffHunk findRelevantHunk(List hunks, int lineNumber) { + for (DiffHunk hunk : hunks) { + if (lineNumber >= hunk.originalStart && + lineNumber < hunk.originalStart + hunk.originalLength) { + return hunk; + } + } + + // If no exact match is found, find the closest hunk before the line number + DiffHunk closestHunk = null; + int closestDistance = Integer.MAX_VALUE; + + for (DiffHunk hunk : hunks) { + if (hunk.originalStart <= lineNumber) { + int distance = lineNumber - hunk.originalStart; + if (distance < closestDistance) { + closestDistance = distance; + closestHunk = hunk; + } + } + } + return closestHunk; + } + + /** + * Extracts suggestion content from a diff hunk. + * + * @param hunk the diff hunk + * @return the suggestion content + */ + private String extractSuggestionFromHunk(DiffHunk hunk) { + StringBuilder suggestion = new StringBuilder(); + + String[] lines = hunk.content.split("\n"); + boolean headerProcessed = false; + + for (String line : lines) { + if (!headerProcessed && line.startsWith("@@")) { + headerProcessed = true; + continue; + } + + if (headerProcessed) { + if (line.startsWith("+") && !line.startsWith("+++")) { + suggestion.append(line.substring(1)); + suggestion.append("\n"); + } else if (!line.startsWith("-") && !line.startsWith("---")) { + suggestion.append(line); + suggestion.append("\n"); + } + } + } + + return suggestion.toString().trim(); + } + + /** + * Formats a single ReviewDog issue line according to ReviewDog's expected format + * with suggestion support. + * + * @param filePath the relative file path + * @param issue the ReviewDogIssue object + * @return formatted string line with suggestion + */ + private String formatReviewDogLine(String filePath, ReviewDogIssue issue) { + StringBuilder builder = new StringBuilder(); + + builder.append(String.format("%s:%d:%d: %s\n", + filePath, issue.lineNumber, issue.column, issue.message)); + + if (issue.suggestion != null && !issue.suggestion.isEmpty()) { + builder.append("```suggestion\n"); + builder.append(issue.suggestion); + builder.append("\n```\n"); + } + + return builder.toString(); + } + + /** + * Inner class representing a diff hunk. + */ + private static class DiffHunk { + final int originalStart; + final int originalLength; + final int newStart; + final int newLength; + String header; + String content; + + DiffHunk(int originalStart, int originalLength, int newStart, int newLength) { + this.originalStart = originalStart; + this.originalLength = originalLength; + this.newStart = newStart; + this.newLength = newLength; + } + } + + /** + * Inner class representing a ReviewDog issue. + */ + private static class ReviewDogIssue { + final int lineNumber; + final int column; + final String message; + final String suggestion; + + ReviewDogIssue(int lineNumber, int column, String message, String suggestion) { + this.lineNumber = lineNumber; + this.column = column; + this.message = message; + this.suggestion = suggestion; + } + } +} diff --git a/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/package-info.java b/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/package-info.java new file mode 100644 index 0000000000..26cee1ce69 --- /dev/null +++ b/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@ReturnValuesAreNonnullByDefault +package com.diffplug.spotless.extra.middleware; + +import com.diffplug.spotless.annotations.ReturnValuesAreNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/lib/src/main/java/com/diffplug/spotless/LintState.java b/lib/src/main/java/com/diffplug/spotless/LintState.java index 3068872256..5aee8f33f1 100644 --- a/lib/src/main/java/com/diffplug/spotless/LintState.java +++ b/lib/src/main/java/com/diffplug/spotless/LintState.java @@ -34,6 +34,10 @@ public class LintState { this.lintsPerStep = lintsPerStep; } + @Nullable public List> getLintsPerStep() { + return lintsPerStep; + } + public DirtyState getDirtyState() { return dirtyState; } From 25a4c27098dfa25af22d0bfa1d6760a50b8663b5 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Fri, 16 May 2025 20:33:57 +0900 Subject: [PATCH 07/19] Apply spotless --- .../extra/middleware/ReviewDogGenerator.java | 33 +++++++++---------- .../extra/middleware/package-info.java | 4 +-- .../java/com/diffplug/spotless/LintState.java | 5 +-- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/ReviewDogGenerator.java b/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/ReviewDogGenerator.java index fcdac13d44..960b2d7476 100644 --- a/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/ReviewDogGenerator.java +++ b/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/ReviewDogGenerator.java @@ -27,8 +27,8 @@ import com.diffplug.spotless.Formatter; import com.diffplug.spotless.FormatterStep; -import com.diffplug.spotless.Lint; import com.diffplug.spotless.LineEnding; +import com.diffplug.spotless.Lint; import com.diffplug.spotless.LintState; import com.diffplug.spotless.extra.integration.DiffMessageFormatter; @@ -49,10 +49,10 @@ public final class ReviewDogGenerator { public ReviewDogGenerator(File projectDir, List steps) { this.projectDir = projectDir; this.formatter = Formatter.builder() - .encoding(StandardCharsets.UTF_8) - .lineEndingsPolicy(LineEnding.UNIX.createPolicy()) - .steps(steps) - .build(); + .encoding(StandardCharsets.UTF_8) + .lineEndingsPolicy(LineEnding.UNIX.createPolicy()) + .steps(steps) + .build(); } /** @@ -72,7 +72,7 @@ public String generateReviewDogFormat(List files) throws IOException { String relativePath = getRelativePath(file); Map.Entry diffResult = DiffMessageFormatter.diff( - projectDir.toPath(), formatter, file); + projectDir.toPath(), formatter, file); List hunks = parseDiffHunks(diffResult.getValue()); List issues = processLints(lintState, hunks); @@ -151,7 +151,6 @@ private List parseDiffHunks(String diffContent) { return hunks; } - /** * Processes lint information and associates them with appropriate diff hunks. * @@ -174,10 +173,10 @@ private List processLints(LintState lintState, List hu } ReviewDogIssue issue = new ReviewDogIssue( - lint.getLineStart(), - 1, - lint.getShortCode() + ": " + lint.getDetail(), - suggestion); + lint.getLineStart(), + 1, + lint.getShortCode() + ": " + lint.getDetail(), + suggestion); issues.add(issue); } @@ -189,10 +188,10 @@ private List processLints(LintState lintState, List hu String suggestion = extractSuggestionFromHunk(firstHunk); issues.add(new ReviewDogIssue( - firstHunk.originalStart, - 1, - "General formatting issue: file needs to be reformatted.", - suggestion)); + firstHunk.originalStart, + 1, + "General formatting issue: file needs to be reformatted.", + suggestion)); } return issues; @@ -208,7 +207,7 @@ private List processLints(LintState lintState, List hu private DiffHunk findRelevantHunk(List hunks, int lineNumber) { for (DiffHunk hunk : hunks) { if (lineNumber >= hunk.originalStart && - lineNumber < hunk.originalStart + hunk.originalLength) { + lineNumber < hunk.originalStart + hunk.originalLength) { return hunk; } } @@ -273,7 +272,7 @@ private String formatReviewDogLine(String filePath, ReviewDogIssue issue) { StringBuilder builder = new StringBuilder(); builder.append(String.format("%s:%d:%d: %s\n", - filePath, issue.lineNumber, issue.column, issue.message)); + filePath, issue.lineNumber, issue.column, issue.message)); if (issue.suggestion != null && !issue.suggestion.isEmpty()) { builder.append("```suggestion\n"); diff --git a/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/package-info.java b/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/package-info.java index 26cee1ce69..803126c322 100644 --- a/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/package-info.java +++ b/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/package-info.java @@ -2,6 +2,6 @@ @ReturnValuesAreNonnullByDefault package com.diffplug.spotless.extra.middleware; -import com.diffplug.spotless.annotations.ReturnValuesAreNonnullByDefault; - import javax.annotation.ParametersAreNonnullByDefault; + +import com.diffplug.spotless.annotations.ReturnValuesAreNonnullByDefault; diff --git a/lib/src/main/java/com/diffplug/spotless/LintState.java b/lib/src/main/java/com/diffplug/spotless/LintState.java index 5aee8f33f1..1762d44411 100644 --- a/lib/src/main/java/com/diffplug/spotless/LintState.java +++ b/lib/src/main/java/com/diffplug/spotless/LintState.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 DiffPlug + * Copyright 2024-2025 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,8 @@ public class LintState { this.lintsPerStep = lintsPerStep; } - @Nullable public List> getLintsPerStep() { + @Nullable + public List> getLintsPerStep() { return lintsPerStep; } From 17ef58ed919db8f184cbfe43f7ff2df64e2aa5f5 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Wed, 21 May 2025 13:15:22 +0900 Subject: [PATCH 08/19] Apply comment --- .../extra/middleware/ReviewDogGenerator.java | 324 ++++-------------- .../middleware/ReviewDogGeneratorTest.java | 134 ++++++++ 2 files changed, 205 insertions(+), 253 deletions(-) create mode 100644 lib-extra/src/test/java/com/diffplug/spotless/extra/middleware/ReviewDogGeneratorTest.java diff --git a/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/ReviewDogGenerator.java b/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/ReviewDogGenerator.java index 960b2d7476..204b32a17e 100644 --- a/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/ReviewDogGenerator.java +++ b/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/ReviewDogGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2025 DiffPlug + * Copyright 2025 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,307 +15,125 @@ */ package com.diffplug.spotless.extra.middleware; -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import com.diffplug.spotless.Formatter; import com.diffplug.spotless.FormatterStep; -import com.diffplug.spotless.LineEnding; import com.diffplug.spotless.Lint; -import com.diffplug.spotless.LintState; -import com.diffplug.spotless.extra.integration.DiffMessageFormatter; /** - * ReviewDogGenerator generates ReviewDog formatted output for linting issues - * based on Spotless formatting results. + * Utility class for generating ReviewDog compatible output in the rdjsonl format. + * This class provides methods to create diff and lint reports that can be used by ReviewDog. */ public final class ReviewDogGenerator { - private final Formatter formatter; - private final File projectDir; - /** - * Constructor for ReviewDogGenerator. - * - * @param projectDir the root directory of the project - * @param steps the list of FormatterStep to apply - */ - public ReviewDogGenerator(File projectDir, List steps) { - this.projectDir = projectDir; - this.formatter = Formatter.builder() - .encoding(StandardCharsets.UTF_8) - .lineEndingsPolicy(LineEnding.UNIX.createPolicy()) - .steps(steps) - .build(); + private ReviewDogGenerator() { + // Prevent instantiation } /** - * Generates ReviewDog formatted output for the given list of files. + * Generates a ReviewDog compatible JSON line (rdjsonl) for a diff between + * the actual content and the formatted content of a file. * - * @param files the list of files to check - * @return a String containing ReviewDog formatted lint messages with code suggestions - * @throws IOException if file reading fails + * @param path The file path + * @param actualContent The content as it currently exists in the file + * @param formattedContent The content after formatting is applied + * @return A string in rdjsonl format representing the diff */ - public String generateReviewDogFormat(List files) throws IOException { - StringBuilder reviewDogOutput = new StringBuilder(); - - for (File file : files) { - LintState lintState = LintState.of(formatter, file); - - if (!lintState.getDirtyState().isClean()) { - String relativePath = getRelativePath(file); - - Map.Entry diffResult = DiffMessageFormatter.diff( - projectDir.toPath(), formatter, file); - - List hunks = parseDiffHunks(diffResult.getValue()); - List issues = processLints(lintState, hunks); - - for (ReviewDogIssue issue : issues) { - reviewDogOutput.append(formatReviewDogLine(relativePath, issue)); - } - } + public static String rdjsonlDiff(String path, String actualContent, String formattedContent) { + if (actualContent.equals(formattedContent)) { + return ""; } - return reviewDogOutput.toString(); - } + String diff = createUnifiedDiff(path, actualContent, formattedContent); - /** - * Converts an absolute file path to a relative path based on the project directory. - * - * @param file the file to convert - * @return relative path string - */ - private String getRelativePath(File file) { - return projectDir.toURI().relativize(file.toURI()).getPath(); + return String.format( + "{\"message\":{\"path\":\"%s\",\"message\":\"File requires formatting\",\"diff\":\"%s\"}}", + escapeJson(path), + escapeJson(diff)); } /** - * Parses git-style diff content into structured diff hunks. + * Generates ReviewDog compatible JSON lines (rdjsonl) for lint issues + * identified by formatting steps. * - * @param diffContent the git-style diff content - * @return list of parsed DiffHunk objects + * @param path The file path + * @param formattedContent The content after formatting is applied + * @param steps The list of formatter steps applied + * @param lintsPerStep The list of lints produced by each step + * @return A string in rdjsonl format representing the lints */ - private List parseDiffHunks(String diffContent) { - List hunks = new ArrayList<>(); - if (diffContent == null || diffContent.isEmpty()) { - return hunks; + public static String rdjsonlLints(String path, String formattedContent, + List steps, List> lintsPerStep) { + if (lintsPerStep == null || lintsPerStep.isEmpty()) { + return ""; } - Pattern hunkHeaderPattern = Pattern.compile("@@ -(\\d+),(\\d+) \\+(\\d+),(\\d+) @@"); - String[] lines = diffContent.split("\n"); - - DiffHunk currentHunk = null; - StringBuilder hunkContent = new StringBuilder(); + StringBuilder builder = new StringBuilder(); - for (String line : lines) { - // Skip file header lines (--- and +++) - if (line.startsWith("---") || line.startsWith("+++")) { + for (int i = 0; i < lintsPerStep.size(); i++) { + List lints = lintsPerStep.get(i); + if (lints == null || lints.isEmpty()) { continue; } - if (line.startsWith("@@")) { - if (currentHunk != null) { - currentHunk.content = hunkContent.toString().trim(); - hunks.add(currentHunk); - hunkContent = new StringBuilder(); - } - - Matcher matcher = hunkHeaderPattern.matcher(line); - if (matcher.find()) { - int originalStart = Integer.parseInt(matcher.group(1)); - int originalLength = Integer.parseInt(matcher.group(2)); - int newStart = Integer.parseInt(matcher.group(3)); - int newLength = Integer.parseInt(matcher.group(4)); - - currentHunk = new DiffHunk(originalStart, originalLength, newStart, newLength); - currentHunk.header = line; - } - hunkContent.append(line).append("\n"); - } else if (currentHunk != null) { - hunkContent.append(line).append("\n"); - } - } - - if (currentHunk != null) { - currentHunk.content = hunkContent.toString().trim(); - hunks.add(currentHunk); - } - - return hunks; - } - - /** - * Processes lint information and associates them with appropriate diff hunks. - * - * @param lintState the LintState containing all lint information - * @param hunks list of diff hunks - * @return list of ReviewDogIssue - */ - private List processLints(LintState lintState, List hunks) { - List issues = new ArrayList<>(); - - List> lintsPerStep = lintState.getLintsPerStep(); - - for (List stepLints : Objects.requireNonNull(lintsPerStep)) { - for (Lint lint : stepLints) { - DiffHunk relevantHunk = findRelevantHunk(hunks, lint.getLineStart()); - - String suggestion = ""; - if (relevantHunk != null) { - suggestion = extractSuggestionFromHunk(relevantHunk); - } - - ReviewDogIssue issue = new ReviewDogIssue( - lint.getLineStart(), - 1, - lint.getShortCode() + ": " + lint.getDetail(), - suggestion); - - issues.add(issue); - } - } - - // If no specific lints were found but file is dirty, add a general formatting issue - if (issues.isEmpty() && !hunks.isEmpty()) { - DiffHunk firstHunk = hunks.get(0); - String suggestion = extractSuggestionFromHunk(firstHunk); - - issues.add(new ReviewDogIssue( - firstHunk.originalStart, - 1, - "General formatting issue: file needs to be reformatted.", - suggestion)); - } - - return issues; - } - - /** - * Finds the hunk that is relevant for a specific line number. - * - * @param hunks list of diff hunks - * @param lineNumber the line number to find (1-based) - * @return the relevant hunk or null if not found - */ - private DiffHunk findRelevantHunk(List hunks, int lineNumber) { - for (DiffHunk hunk : hunks) { - if (lineNumber >= hunk.originalStart && - lineNumber < hunk.originalStart + hunk.originalLength) { - return hunk; + String stepName = (i < steps.size()) ? steps.get(i).getName() : "unknown"; + for (Lint lint : lints) { + builder.append(formatLintAsJson(path, lint, stepName)).append('\n'); } } - // If no exact match is found, find the closest hunk before the line number - DiffHunk closestHunk = null; - int closestDistance = Integer.MAX_VALUE; - - for (DiffHunk hunk : hunks) { - if (hunk.originalStart <= lineNumber) { - int distance = lineNumber - hunk.originalStart; - if (distance < closestDistance) { - closestDistance = distance; - closestHunk = hunk; - } - } - } - return closestHunk; + return builder.toString().trim(); } /** - * Extracts suggestion content from a diff hunk. - * - * @param hunk the diff hunk - * @return the suggestion content + * Creates a unified diff between two text contents. */ - private String extractSuggestionFromHunk(DiffHunk hunk) { - StringBuilder suggestion = new StringBuilder(); + private static String createUnifiedDiff(String path, String actualContent, String formattedContent) { + String[] actualLines = actualContent.split("\\r?\\n", -1); + String[] formattedLines = formattedContent.split("\\r?\\n", -1); - String[] lines = hunk.content.split("\n"); - boolean headerProcessed = false; + StringBuilder diff = new StringBuilder(); + diff.append("--- a/").append(path).append('\n'); + diff.append("+++ b/").append(path).append('\n'); + diff.append("@@ -1,").append(actualLines.length).append(" +1,").append(formattedLines.length).append(" @@\n"); - for (String line : lines) { - if (!headerProcessed && line.startsWith("@@")) { - headerProcessed = true; - continue; - } - - if (headerProcessed) { - if (line.startsWith("+") && !line.startsWith("+++")) { - suggestion.append(line.substring(1)); - suggestion.append("\n"); - } else if (!line.startsWith("-") && !line.startsWith("---")) { - suggestion.append(line); - suggestion.append("\n"); - } - } + for (String line : actualLines) { + diff.append('-').append(line).append('\n'); } - return suggestion.toString().trim(); - } - - /** - * Formats a single ReviewDog issue line according to ReviewDog's expected format - * with suggestion support. - * - * @param filePath the relative file path - * @param issue the ReviewDogIssue object - * @return formatted string line with suggestion - */ - private String formatReviewDogLine(String filePath, ReviewDogIssue issue) { - StringBuilder builder = new StringBuilder(); - - builder.append(String.format("%s:%d:%d: %s\n", - filePath, issue.lineNumber, issue.column, issue.message)); - - if (issue.suggestion != null && !issue.suggestion.isEmpty()) { - builder.append("```suggestion\n"); - builder.append(issue.suggestion); - builder.append("\n```\n"); + for (String line : formattedLines) { + diff.append('+').append(line).append('\n'); } - return builder.toString(); + return diff.toString(); } /** - * Inner class representing a diff hunk. + * Formats a single lint issue as a JSON line. */ - private static class DiffHunk { - final int originalStart; - final int originalLength; - final int newStart; - final int newLength; - String header; - String content; - - DiffHunk(int originalStart, int originalLength, int newStart, int newLength) { - this.originalStart = originalStart; - this.originalLength = originalLength; - this.newStart = newStart; - this.newLength = newLength; - } + private static String formatLintAsJson(String path, Lint lint, String stepName) { + return String.format( + "{\"message\":{\"path\":\"%s\",\"line\":%d,\"column\":1,\"message\":\"%s: %s\"}}", + escapeJson(path), + lint.getLineStart(), + escapeJson(lint.getShortCode()), + escapeJson(lint.getDetail())); } /** - * Inner class representing a ReviewDog issue. + * Escapes special characters in a string for JSON compatibility. */ - private static class ReviewDogIssue { - final int lineNumber; - final int column; - final String message; - final String suggestion; - - ReviewDogIssue(int lineNumber, int column, String message, String suggestion) { - this.lineNumber = lineNumber; - this.column = column; - this.message = message; - this.suggestion = suggestion; + private static String escapeJson(String str) { + if (str == null) { + return ""; } + return str + .replace("\\", "\\\\") + .replace("\"", "\\\"") + .replace("\n", "\\n") + .replace("\r", "\\r") + .replace("\t", "\\t") + .replace("\b", "\\b") + .replace("\f", "\\f"); } } diff --git a/lib-extra/src/test/java/com/diffplug/spotless/extra/middleware/ReviewDogGeneratorTest.java b/lib-extra/src/test/java/com/diffplug/spotless/extra/middleware/ReviewDogGeneratorTest.java new file mode 100644 index 0000000000..96aa0e358c --- /dev/null +++ b/lib-extra/src/test/java/com/diffplug/spotless/extra/middleware/ReviewDogGeneratorTest.java @@ -0,0 +1,134 @@ +/* + * Copyright 2016-2025 DiffPlug + * + * 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 com.diffplug.spotless.extra.middleware; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.Lint; + +public class ReviewDogGeneratorTest { + + @Test + public void diffSingleLine() { + String result = ReviewDogGenerator.rdjsonlDiff("test.txt", "dirty", "clean"); + assertNotNull(result); + assertTrue(result.contains("\"path\":\"test.txt\"")); + assertTrue(result.contains("\"diff\":")); + assertTrue(result.contains("-dirty")); + assertTrue(result.contains("+clean")); + } + + @Test + public void diffNoChange() { + String result = ReviewDogGenerator.rdjsonlDiff("test.txt", "same", "same"); + assertEquals("", result); + } + + @Test + public void diffMultipleLines() { + String actual = "Line 1\nLine 2\nDirty line\nLine 4"; + String formatted = "Line 1\nLine 2\nClean line\nLine 4"; + + String result = ReviewDogGenerator.rdjsonlDiff("src/main.java", actual, formatted); + + assertNotNull(result); + assertTrue(result.contains("\"path\":\"src/main.java\"")); + assertTrue(result.contains("-Dirty line")); + assertTrue(result.contains("+Clean line")); + } + + @Test + public void lintsEmpty() { + List steps = new ArrayList<>(); + List> lintsPerStep = new ArrayList<>(); + + String result = ReviewDogGenerator.rdjsonlLints("test.txt", "content", steps, lintsPerStep); + assertEquals("", result); + } + + @Test + public void lintsSingleIssue() { + FormatterStep step = FormatterStep.create( + "testStep", + "formatter-state", + state -> rawUnix -> rawUnix); + List steps = Collections.singletonList(step); + + Lint lint = Lint.atLine(1, "TEST001", "Test lint message"); + List> lintsPerStep = Collections.singletonList(Collections.singletonList(lint)); + + String result = ReviewDogGenerator.rdjsonlLints("src/main.java", "content", steps, lintsPerStep); + + assertNotNull(result); + assertTrue(result.contains("\"path\":\"src/main.java\"")); + assertTrue(result.contains("\"line\":1")); + assertTrue(result.contains("TEST001: Test lint message")); + } + + @Test + public void lintsMultipleIssues() { + FormatterStep step1 = new FormatterStep() { + @Override + public String getName() { + return "step1"; + } + + @Override + public String format(String rawUnix, File file) { + return rawUnix; + } + + @Override + public void close() {} + }; + + FormatterStep step2 = FormatterStep.create( + "step2", + "formatter-state", + state -> rawUnix -> rawUnix); + + List steps = Arrays.asList(step1, step2); + + Lint lint1 = Lint.atLine(1, "RULE1", "First issue"); + Lint lint2 = Lint.atLine(5, "RULE2", "Second issue"); + + List> lintsPerStep = Arrays.asList( + Collections.singletonList(lint1), + Collections.singletonList(lint2)); + + String result = ReviewDogGenerator.rdjsonlLints("src/main.java", "content", steps, lintsPerStep); + + assertNotNull(result); + assertTrue(result.contains("RULE1")); + assertTrue(result.contains("RULE2")); + assertTrue(result.contains("First issue")); + assertTrue(result.contains("Second issue")); + + String[] lines = result.split("\n"); + assertEquals(2, lines.length); + } +} From b63253daf19099ef6c0b9cf5bca03a19f8246af8 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Wed, 21 May 2025 13:19:51 +0900 Subject: [PATCH 09/19] Update license header --- .../diffplug/spotless/extra/middleware/ReviewDogGenerator.java | 2 +- .../spotless/extra/middleware/ReviewDogGeneratorTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/ReviewDogGenerator.java b/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/ReviewDogGenerator.java index 204b32a17e..27c7ba99d1 100644 --- a/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/ReviewDogGenerator.java +++ b/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/ReviewDogGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2025 DiffPlug + * Copyright 2022-2025 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib-extra/src/test/java/com/diffplug/spotless/extra/middleware/ReviewDogGeneratorTest.java b/lib-extra/src/test/java/com/diffplug/spotless/extra/middleware/ReviewDogGeneratorTest.java index 96aa0e358c..d9cf2456bf 100644 --- a/lib-extra/src/test/java/com/diffplug/spotless/extra/middleware/ReviewDogGeneratorTest.java +++ b/lib-extra/src/test/java/com/diffplug/spotless/extra/middleware/ReviewDogGeneratorTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2025 DiffPlug + * Copyright 2022-2025 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From dfd422fc9bee0d6ba377415b3ce62a7b5b84e9b9 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Wed, 21 May 2025 13:45:37 +0900 Subject: [PATCH 10/19] Remove unused code --- lib/src/main/java/com/diffplug/spotless/LintState.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/src/main/java/com/diffplug/spotless/LintState.java b/lib/src/main/java/com/diffplug/spotless/LintState.java index 1762d44411..e4a003f97a 100644 --- a/lib/src/main/java/com/diffplug/spotless/LintState.java +++ b/lib/src/main/java/com/diffplug/spotless/LintState.java @@ -34,11 +34,6 @@ public class LintState { this.lintsPerStep = lintsPerStep; } - @Nullable - public List> getLintsPerStep() { - return lintsPerStep; - } - public DirtyState getDirtyState() { return dirtyState; } From fb5aa2d052f400341768c4a1263d81b3dc549994 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Wed, 21 May 2025 14:05:19 +0900 Subject: [PATCH 11/19] Refactor some codes --- .../extra/middleware/ReviewDogGenerator.java | 34 ++++++++++++------- .../middleware/ReviewDogGeneratorTest.java | 17 +++++----- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/ReviewDogGenerator.java b/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/ReviewDogGenerator.java index 27c7ba99d1..9aeb6df2c2 100644 --- a/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/ReviewDogGenerator.java +++ b/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/ReviewDogGenerator.java @@ -26,6 +26,8 @@ */ public final class ReviewDogGenerator { + private static final String SOURCE = "spotless"; + private ReviewDogGenerator() { // Prevent instantiation } @@ -34,8 +36,8 @@ private ReviewDogGenerator() { * Generates a ReviewDog compatible JSON line (rdjsonl) for a diff between * the actual content and the formatted content of a file. * - * @param path The file path - * @param actualContent The content as it currently exists in the file + * @param path The file path + * @param actualContent The content as it currently exists in the file * @param formattedContent The content after formatting is applied * @return A string in rdjsonl format representing the diff */ @@ -56,14 +58,12 @@ public static String rdjsonlDiff(String path, String actualContent, String forma * Generates ReviewDog compatible JSON lines (rdjsonl) for lint issues * identified by formatting steps. * - * @param path The file path - * @param formattedContent The content after formatting is applied - * @param steps The list of formatter steps applied - * @param lintsPerStep The list of lints produced by each step + * @param path The file path + * @param steps The list of formatter steps applied + * @param lintsPerStep The list of lints produced by each step * @return A string in rdjsonl format representing the lints */ - public static String rdjsonlLints(String path, String formattedContent, - List steps, List> lintsPerStep) { + public static String rdjsonlLints(String path, List steps, List> lintsPerStep) { if (lintsPerStep == null || lintsPerStep.isEmpty()) { return ""; } @@ -111,13 +111,23 @@ private static String createUnifiedDiff(String path, String actualContent, Strin /** * Formats a single lint issue as a JSON line. */ - private static String formatLintAsJson(String path, Lint lint, String stepName) { + private static String formatLintAsJson(String path, Lint lint, String ruleCode) { return String.format( - "{\"message\":{\"path\":\"%s\",\"line\":%d,\"column\":1,\"message\":\"%s: %s\"}}", + "{" + + "\"source\":\"%s\"," + + "\"code\":\"%s\"," + + "\"level\":\"warning\"," + + "\"message\":\"%s\"," + + "\"path\":\"%s\"," + + "\"line\":%d," + + "\"column\":%d" + + "}", + escapeJson(SOURCE), + escapeJson(ruleCode), + escapeJson(lint.getDetail()), escapeJson(path), lint.getLineStart(), - escapeJson(lint.getShortCode()), - escapeJson(lint.getDetail())); + 1); } /** diff --git a/lib-extra/src/test/java/com/diffplug/spotless/extra/middleware/ReviewDogGeneratorTest.java b/lib-extra/src/test/java/com/diffplug/spotless/extra/middleware/ReviewDogGeneratorTest.java index d9cf2456bf..bc8c4f0b52 100644 --- a/lib-extra/src/test/java/com/diffplug/spotless/extra/middleware/ReviewDogGeneratorTest.java +++ b/lib-extra/src/test/java/com/diffplug/spotless/extra/middleware/ReviewDogGeneratorTest.java @@ -66,7 +66,7 @@ public void lintsEmpty() { List steps = new ArrayList<>(); List> lintsPerStep = new ArrayList<>(); - String result = ReviewDogGenerator.rdjsonlLints("test.txt", "content", steps, lintsPerStep); + String result = ReviewDogGenerator.rdjsonlLints("test.txt", steps, lintsPerStep); assertEquals("", result); } @@ -81,12 +81,13 @@ public void lintsSingleIssue() { Lint lint = Lint.atLine(1, "TEST001", "Test lint message"); List> lintsPerStep = Collections.singletonList(Collections.singletonList(lint)); - String result = ReviewDogGenerator.rdjsonlLints("src/main.java", "content", steps, lintsPerStep); + String result = ReviewDogGenerator.rdjsonlLints("src/main.java", steps, lintsPerStep); assertNotNull(result); assertTrue(result.contains("\"path\":\"src/main.java\"")); assertTrue(result.contains("\"line\":1")); - assertTrue(result.contains("TEST001: Test lint message")); + assertTrue(result.contains("\"message\":\"Test lint message\"")); + assertTrue(result.contains("\"code\":\"testStep\"")); } @Test @@ -120,13 +121,13 @@ public void close() {} Collections.singletonList(lint1), Collections.singletonList(lint2)); - String result = ReviewDogGenerator.rdjsonlLints("src/main.java", "content", steps, lintsPerStep); + String result = ReviewDogGenerator.rdjsonlLints("src/main.java", steps, lintsPerStep); assertNotNull(result); - assertTrue(result.contains("RULE1")); - assertTrue(result.contains("RULE2")); - assertTrue(result.contains("First issue")); - assertTrue(result.contains("Second issue")); + assertTrue(result.contains("\"code\":\"step1\"")); + assertTrue(result.contains("\"code\":\"step2\"")); + assertTrue(result.contains("\"message\":\"First issue\"")); + assertTrue(result.contains("\"message\":\"Second issue\"")); String[] lines = result.split("\n"); assertEquals(2, lines.length); From 745810beb0f076f5ca883a009d084a85e0526ea3 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Wed, 21 May 2025 14:21:50 +0900 Subject: [PATCH 12/19] Refactor test codes - use Selfie.expectSelfie(result).toBe_TODO() --- .../middleware/ReviewDogGeneratorTest.java | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/lib-extra/src/test/java/com/diffplug/spotless/extra/middleware/ReviewDogGeneratorTest.java b/lib-extra/src/test/java/com/diffplug/spotless/extra/middleware/ReviewDogGeneratorTest.java index bc8c4f0b52..c39a0c177a 100644 --- a/lib-extra/src/test/java/com/diffplug/spotless/extra/middleware/ReviewDogGeneratorTest.java +++ b/lib-extra/src/test/java/com/diffplug/spotless/extra/middleware/ReviewDogGeneratorTest.java @@ -19,6 +19,8 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.diffplug.selfie.Selfie; + import java.io.File; import java.util.ArrayList; import java.util.Arrays; @@ -84,10 +86,7 @@ public void lintsSingleIssue() { String result = ReviewDogGenerator.rdjsonlLints("src/main.java", steps, lintsPerStep); assertNotNull(result); - assertTrue(result.contains("\"path\":\"src/main.java\"")); - assertTrue(result.contains("\"line\":1")); - assertTrue(result.contains("\"message\":\"Test lint message\"")); - assertTrue(result.contains("\"code\":\"testStep\"")); + Selfie.expectSelfie(result).toBe("{\"source\":\"spotless\",\"code\":\"testStep\",\"level\":\"warning\",\"message\":\"Test lint message\",\"path\":\"src/main.java\",\"line\":1,\"column\":1}"); } @Test @@ -122,14 +121,7 @@ public void close() {} Collections.singletonList(lint2)); String result = ReviewDogGenerator.rdjsonlLints("src/main.java", steps, lintsPerStep); - - assertNotNull(result); - assertTrue(result.contains("\"code\":\"step1\"")); - assertTrue(result.contains("\"code\":\"step2\"")); - assertTrue(result.contains("\"message\":\"First issue\"")); - assertTrue(result.contains("\"message\":\"Second issue\"")); - - String[] lines = result.split("\n"); - assertEquals(2, lines.length); + Selfie.expectSelfie(result).toBe("{\"source\":\"spotless\",\"code\":\"step1\",\"level\":\"warning\",\"message\":\"First issue\",\"path\":\"src/main.java\",\"line\":1,\"column\":1}", +"{\"source\":\"spotless\",\"code\":\"step2\",\"level\":\"warning\",\"message\":\"Second issue\",\"path\":\"src/main.java\",\"line\":5,\"column\":1}"); } } From 5724b7338f5da7ca740e82eeda6e1770b54f5995 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Wed, 21 May 2025 14:23:38 +0900 Subject: [PATCH 13/19] Refactor test codes - use Selfie.expectSelfie(result).toBe_TODO() Signed-off-by: yongjunhong --- .../middleware/ReviewDogGeneratorTest.java | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/lib-extra/src/test/java/com/diffplug/spotless/extra/middleware/ReviewDogGeneratorTest.java b/lib-extra/src/test/java/com/diffplug/spotless/extra/middleware/ReviewDogGeneratorTest.java index c39a0c177a..ae8f5bb027 100644 --- a/lib-extra/src/test/java/com/diffplug/spotless/extra/middleware/ReviewDogGeneratorTest.java +++ b/lib-extra/src/test/java/com/diffplug/spotless/extra/middleware/ReviewDogGeneratorTest.java @@ -17,7 +17,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; import com.diffplug.selfie.Selfie; @@ -37,17 +36,13 @@ public class ReviewDogGeneratorTest { @Test public void diffSingleLine() { String result = ReviewDogGenerator.rdjsonlDiff("test.txt", "dirty", "clean"); - assertNotNull(result); - assertTrue(result.contains("\"path\":\"test.txt\"")); - assertTrue(result.contains("\"diff\":")); - assertTrue(result.contains("-dirty")); - assertTrue(result.contains("+clean")); + Selfie.expectSelfie(result).toBe("{\"message\":{\"path\":\"test.txt\",\"message\":\"File requires formatting\",\"diff\":\"--- a/test.txt\\n+++ b/test.txt\\n@@ -1,1 +1,1 @@\\n-dirty\\n+clean\\n\"}}"); } @Test public void diffNoChange() { String result = ReviewDogGenerator.rdjsonlDiff("test.txt", "same", "same"); - assertEquals("", result); + Selfie.expectSelfie(result).toBe(""); } @Test @@ -56,11 +51,7 @@ public void diffMultipleLines() { String formatted = "Line 1\nLine 2\nClean line\nLine 4"; String result = ReviewDogGenerator.rdjsonlDiff("src/main.java", actual, formatted); - - assertNotNull(result); - assertTrue(result.contains("\"path\":\"src/main.java\"")); - assertTrue(result.contains("-Dirty line")); - assertTrue(result.contains("+Clean line")); + Selfie.expectSelfie(result).toBe("{\"message\":{\"path\":\"src/main.java\",\"message\":\"File requires formatting\",\"diff\":\"--- a/src/main.java\\n+++ b/src/main.java\\n@@ -1,4 +1,4 @@\\n-Line 1\\n-Line 2\\n-Dirty line\\n-Line 4\\n+Line 1\\n+Line 2\\n+Clean line\\n+Line 4\\n\"}}"); } @Test @@ -69,7 +60,7 @@ public void lintsEmpty() { List> lintsPerStep = new ArrayList<>(); String result = ReviewDogGenerator.rdjsonlLints("test.txt", steps, lintsPerStep); - assertEquals("", result); + Selfie.expectSelfie(result).toBe(""); } @Test @@ -84,8 +75,6 @@ public void lintsSingleIssue() { List> lintsPerStep = Collections.singletonList(Collections.singletonList(lint)); String result = ReviewDogGenerator.rdjsonlLints("src/main.java", steps, lintsPerStep); - - assertNotNull(result); Selfie.expectSelfie(result).toBe("{\"source\":\"spotless\",\"code\":\"testStep\",\"level\":\"warning\",\"message\":\"Test lint message\",\"path\":\"src/main.java\",\"line\":1,\"column\":1}"); } From 62eb8632eeaf021c4ab80896682bf1ab2e60e4bb Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Wed, 21 May 2025 14:39:40 +0900 Subject: [PATCH 14/19] Apply spotless Signed-off-by: yongjunhong --- .../spotless/extra/middleware/ReviewDogGeneratorTest.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib-extra/src/test/java/com/diffplug/spotless/extra/middleware/ReviewDogGeneratorTest.java b/lib-extra/src/test/java/com/diffplug/spotless/extra/middleware/ReviewDogGeneratorTest.java index ae8f5bb027..8b36f4bee7 100644 --- a/lib-extra/src/test/java/com/diffplug/spotless/extra/middleware/ReviewDogGeneratorTest.java +++ b/lib-extra/src/test/java/com/diffplug/spotless/extra/middleware/ReviewDogGeneratorTest.java @@ -15,11 +15,6 @@ */ package com.diffplug.spotless.extra.middleware; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import com.diffplug.selfie.Selfie; - import java.io.File; import java.util.ArrayList; import java.util.Arrays; @@ -28,6 +23,7 @@ import org.junit.jupiter.api.Test; +import com.diffplug.selfie.Selfie; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.Lint; @@ -111,6 +107,6 @@ public void close() {} String result = ReviewDogGenerator.rdjsonlLints("src/main.java", steps, lintsPerStep); Selfie.expectSelfie(result).toBe("{\"source\":\"spotless\",\"code\":\"step1\",\"level\":\"warning\",\"message\":\"First issue\",\"path\":\"src/main.java\",\"line\":1,\"column\":1}", -"{\"source\":\"spotless\",\"code\":\"step2\",\"level\":\"warning\",\"message\":\"Second issue\",\"path\":\"src/main.java\",\"line\":5,\"column\":1}"); + "{\"source\":\"spotless\",\"code\":\"step2\",\"level\":\"warning\",\"message\":\"Second issue\",\"path\":\"src/main.java\",\"line\":5,\"column\":1}"); } } From e7015dff40582a22f3ceb6b408bd46c2d3402915 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Wed, 21 May 2025 15:08:09 +0900 Subject: [PATCH 15/19] Update README.md Signed-off-by: yongjunhong --- plugin-gradle/README.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/plugin-gradle/README.md b/plugin-gradle/README.md index da51e31244..a2befaeae6 100644 --- a/plugin-gradle/README.md +++ b/plugin-gradle/README.md @@ -88,6 +88,7 @@ Spotless supports all of Gradle's built-in performance features (incremental bui - [Multiple (or custom) language-specific blocks](#multiple-or-custom-language-specific-blocks) - [Inception (languages within languages within...)](#inception-languages-within-languages-within) - [Disabling warnings and error messages](#disabling-warnings-and-error-messages) + - [Reviewdog integration for CI](#reviewdog-integration-for-ci) - [Dependency resolution modes](#dependency-resolution-modes) - [How do I preview what `spotlessApply` will do?](#how-do-i-preview-what-spotlessapply-will-do) - [Example configurations (from real-world projects)](#example-configurations-from-real-world-projects) @@ -1768,6 +1769,42 @@ spotless { ignoreErrorForPath('path/to/file.java') // ignore errors by all steps on this specific file ``` + + +## Reviewdog integration for CI + +Spotless can generate reports compatible with [Reviewdog](https://github.com/reviewdog/reviewdog), a tool that automates code review tasks by posting formatting issues as comments on pull requests. + +### Enabling Reviewdog integration + +To enable Reviewdog output: + +```gradle +spotless { + reviewdogOutput file("${buildDir}/spotless-reviewdog.json") +} +``` + +### Automatic report generation on check failure + +To generate reports when `spotlessCheck` fails: + +```gradle +tasks.named('spotlessCheck').configure { + ignoreFailures = true + finalizedBy tasks.register('generateReviewdogReport') { + doLast { + if (spotlessCheck.outcome.failure) { + logger.lifecycle("Spotless check failed - Reviewdog report generated") + } + } + } +} +``` + +For Reviewdog setup instructions, visit: https://github.com/reviewdog/reviewdog#installation + + ## Dependency resolution modes From a94b35765d829befcaf21479a90ffc63c31d2da0 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Wed, 21 May 2025 21:34:50 +0900 Subject: [PATCH 16/19] Update README.md Signed-off-by: yongjunhong --- plugin-gradle/README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/plugin-gradle/README.md b/plugin-gradle/README.md index a2befaeae6..dc30ac97d7 100644 --- a/plugin-gradle/README.md +++ b/plugin-gradle/README.md @@ -1773,6 +1773,7 @@ spotless { ## Reviewdog integration for CI +**CURRENTLY IN BETA** – bug reports are welcome! This is a challenging feature to test comprehensively, so we anticipate needing a few releases to get everything right. Spotless can generate reports compatible with [Reviewdog](https://github.com/reviewdog/reviewdog), a tool that automates code review tasks by posting formatting issues as comments on pull requests. ### Enabling Reviewdog integration @@ -1792,14 +1793,18 @@ To generate reports when `spotlessCheck` fails: ```gradle tasks.named('spotlessCheck').configure { ignoreFailures = true - finalizedBy tasks.register('generateReviewdogReport') { - doLast { - if (spotlessCheck.outcome.failure) { - logger.lifecycle("Spotless check failed - Reviewdog report generated") + doLast { + if (state.failure != null) { + logger.lifecycle("Spotless check failed – generating Reviewdog report") + + if (project.hasProperty('reviewdogOutput')) { + // Insert logic here to generate reviewdogOutput + // Example: file(project.reviewdogOutput).text = spotlessCheckOutput } } } } + ``` For Reviewdog setup instructions, visit: https://github.com/reviewdog/reviewdog#installation From 6688b33f2ecb586d353c7cfffedef4dcbfa12f0e Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Wed, 21 May 2025 22:55:38 +0900 Subject: [PATCH 17/19] Add `reviewDog property` and pipe it to `spotlessCheck` Signed-off-by: yongjunhong --- .../gradle/spotless/SpotlessCheck.java | 63 +++++++++++++------ .../gradle/spotless/SpotlessExtension.java | 20 +++++- .../spotless/SpotlessExtensionImpl.java | 5 +- 3 files changed, 67 insertions(+), 21 deletions(-) diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessCheck.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessCheck.java index 175a828a66..63c7c08c96 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessCheck.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessCheck.java @@ -38,6 +38,7 @@ import com.diffplug.spotless.FileSignature; import com.diffplug.spotless.ThrowingEx; import com.diffplug.spotless.extra.integration.DiffMessageFormatter; +import com.diffplug.spotless.extra.middleware.ReviewDogGenerator; @DisableCachingByDefault(because = "not worth caching") public abstract class SpotlessCheck extends SpotlessTaskService.ClientTask { @@ -47,6 +48,9 @@ public abstract class SpotlessCheck extends SpotlessTaskService.ClientTask { @Input public abstract Property getRunToFixMessage(); + @Input + public abstract Property getReviewDog(); + public void performActionTest() throws IOException { performAction(true); } @@ -61,29 +65,50 @@ private void performAction(boolean isTest) throws IOException { ConfigurableFileTree lintsFiles = getConfigCacheWorkaround().fileTree().from(getSpotlessLintsDirectory().get()); if (cleanFiles.isEmpty() && lintsFiles.isEmpty()) { getState().setDidWork(sourceDidWork()); - } else if (!isTest && applyHasRun()) { + return; + } + if (!isTest && applyHasRun()) { // if our matching apply has already run, then we don't need to do anything getState().setDidWork(false); - } else { - List unformattedFiles = getUncleanFiles(cleanFiles); - if (!unformattedFiles.isEmpty()) { - // if any files are unformatted, we show those - throw new GradleException(DiffMessageFormatter.builder() - .runToFix(getRunToFixMessage().get()) - .formatterFolder( - getProjectDir().get().getAsFile().toPath(), - getSpotlessCleanDirectory().get().toPath(), - getEncoding().get()) - .problemFiles(unformattedFiles) - .getMessage()); - } else { - // We only show lints if there are no unformatted files. - // This is because lint line numbers are relative to the - // formatted content, and formatting often fixes lints. - boolean detailed = false; - throw new GradleException(super.allLintsErrorMsgDetailed(lintsFiles, detailed)); + return; + } + + List unformattedFiles = getUncleanFiles(cleanFiles); + if (!unformattedFiles.isEmpty()) { + if (getReviewDog().get()) { + for (File file : unformattedFiles) { + String originalContent = new String(Files.readAllBytes(file.toPath()), getEncoding().get()); + File cleanFile = new File(getSpotlessCleanDirectory().get().getName(), getProjectDir().get().getAsFile().toPath().relativize(file.toPath()).toString()); + String formattedContent = new String(Files.readAllBytes(cleanFile.toPath()), getEncoding().get()); + + // TODO : send or save the output of ReviewDogGenerator.rdjsonlDiff + ReviewDogGenerator.rdjsonlDiff(file.getPath(), originalContent, formattedContent); + } + } + + throw new GradleException(DiffMessageFormatter.builder() + .runToFix(getRunToFixMessage().get()) + .formatterFolder( + getProjectDir().get().getAsFile().toPath(), + getSpotlessCleanDirectory().get().toPath(), + getEncoding().get()) + .problemFiles(unformattedFiles) + .getMessage()); + } + + if (getReviewDog().get()) { + for (File file : lintsFiles.getFiles()) { + String path = file.getPath(); + // TODO : send or save the output of ReviewDogGenerator.rdjsonlLints + ReviewDogGenerator.rdjsonlLints(path, null, null); } } + + // We only show lints if there are no unformatted files. + // This is because lint line numbers are relative to the + // formatted content, and formatting often fixes lints. + boolean detailed = false; + throw new GradleException(super.allLintsErrorMsgDetailed(lintsFiles, detailed)); } private @NotNull List getUncleanFiles(ConfigurableFileTree cleanFiles) { diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java index e883953eaa..f5412f3087 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 DiffPlug + * Copyright 2016-2025 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -267,6 +267,24 @@ public void setEnforceCheck(boolean enforceCheck) { this.enforceCheck = enforceCheck; } + boolean reviewDog = false; + + /** + * Returns {@code true} if ReviewDog output should be generated; {@code false} otherwise. + */ + public boolean isReviewDog() { + return reviewDog; + } + + /** + * Configures Spotless to generate ReviewDog output if {@code true}. + *

+ * {@code false} by default. + */ + public void setReviewDog(boolean reviewDog) { + this.reviewDog = reviewDog; + } + @SuppressWarnings("unchecked") public void format(String name, Class clazz, Action configure) { maybeCreate(name, clazz).lazyActions.add((Action) configure); diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionImpl.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionImpl.java index 75168f690a..c17d7972e6 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionImpl.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 DiffPlug + * Copyright 2016-2025 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -91,6 +91,9 @@ protected void createFormatTasks(String name, FormatExtension formatExtension) { // if the user runs both, make sure that apply happens first, task.mustRunAfter(applyTask); + + // if the user enables the review dog, spotlessCheck will return the review dog format output + task.getReviewDog().set(this.reviewDog); }); rootCheckTask.configure(task -> task.dependsOn(checkTask)); From 5ae21df73ad5b70c45a7c7a685d94d46450a6b66 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Tue, 27 May 2025 22:11:28 +0900 Subject: [PATCH 18/19] Implement configurable ReviewDog output directory Signed-off-by: yongjunhong --- .../gradle/spotless/SpotlessCheck.java | 19 ++++++++++++++-- .../gradle/spotless/SpotlessExtension.java | 22 +++++++++++++++++++ .../spotless/SpotlessExtensionImpl.java | 1 + 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessCheck.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessCheck.java index 63c7c08c96..b6448fac4b 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessCheck.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessCheck.java @@ -31,6 +31,7 @@ import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.TaskAction; import org.gradle.work.DisableCachingByDefault; import org.jetbrains.annotations.NotNull; @@ -51,6 +52,10 @@ public abstract class SpotlessCheck extends SpotlessTaskService.ClientTask { @Input public abstract Property getReviewDog(); + @Input + @Optional + public abstract Property getReviewDogOutputDir(); + public void performActionTest() throws IOException { performAction(true); } @@ -81,8 +86,18 @@ private void performAction(boolean isTest) throws IOException { File cleanFile = new File(getSpotlessCleanDirectory().get().getName(), getProjectDir().get().getAsFile().toPath().relativize(file.toPath()).toString()); String formattedContent = new String(Files.readAllBytes(cleanFile.toPath()), getEncoding().get()); - // TODO : send or save the output of ReviewDogGenerator.rdjsonlDiff - ReviewDogGenerator.rdjsonlDiff(file.getPath(), originalContent, formattedContent); + File outputDir = getReviewDogOutputDir().isPresent() ? getReviewDogOutputDir().get() : new File(getProject().getRootDir(), "build/reviewdog"); + + if (!outputDir.exists()) { + outputDir.mkdirs(); + } + + String relativePath = getProjectDir().get().getAsFile().toPath().relativize(file.toPath()).toString(); + File outputFile = new File(outputDir, relativePath + ".rdjsonl"); + outputFile.getParentFile().mkdirs(); + + String rdjsonl = ReviewDogGenerator.rdjsonlDiff(file.getPath(), originalContent, formattedContent); + Files.write(outputFile.toPath(), rdjsonl.getBytes(getEncoding().get())); } } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java index f5412f3087..7a27422d46 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java @@ -17,6 +17,7 @@ import static java.util.Objects.requireNonNull; +import java.io.File; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.LinkedHashMap; @@ -285,6 +286,27 @@ public void setReviewDog(boolean reviewDog) { this.reviewDog = reviewDog; } + @Nullable + File reviewDogOutputDir; + + /** + * Returns the directory where ReviewDog output will be written. + * If not set, defaults to {@code build/reviewdog} in the root project directory. + */ + public @Nullable File getReviewDogOutputDir() { + return reviewDogOutputDir; + } + + /** + * Sets the directory where ReviewDog output will be written. + * If not set, defaults to {@code build/reviewdog} in the root project directory. + *

+ * If the directory does not exist, it will be created. + */ + public void setReviewDogOutputDir(File reviewDogOutputDir) { + this.reviewDogOutputDir = reviewDogOutputDir; + } + @SuppressWarnings("unchecked") public void format(String name, Class clazz, Action configure) { maybeCreate(name, clazz).lazyActions.add((Action) configure); diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionImpl.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionImpl.java index c17d7972e6..8f1765f0e8 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionImpl.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionImpl.java @@ -94,6 +94,7 @@ protected void createFormatTasks(String name, FormatExtension formatExtension) { // if the user enables the review dog, spotlessCheck will return the review dog format output task.getReviewDog().set(this.reviewDog); + task.getReviewDogOutputDir().set(this.reviewDogOutputDir); }); rootCheckTask.configure(task -> task.dependsOn(checkTask)); From a7d515cac599fdcd6620ec725be8eb102868373a Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Tue, 27 May 2025 23:07:53 +0900 Subject: [PATCH 19/19] Improve Gradle caching by using String lists instead of FormatterStep objects Signed-off-by: yongjunhong --- .../extra/middleware/ReviewDogGenerator.java | 20 +++++++++++++++++-- .../middleware/ReviewDogGeneratorTest.java | 6 +++--- .../gradle/spotless/SpotlessCheck.java | 8 ++++++-- .../spotless/SpotlessExtensionImpl.java | 4 ++++ 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/ReviewDogGenerator.java b/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/ReviewDogGenerator.java index 9aeb6df2c2..ddcf3723b7 100644 --- a/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/ReviewDogGenerator.java +++ b/lib-extra/src/main/java/com/diffplug/spotless/extra/middleware/ReviewDogGenerator.java @@ -15,7 +15,9 @@ */ package com.diffplug.spotless.extra.middleware; +import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.Lint; @@ -63,7 +65,21 @@ public static String rdjsonlDiff(String path, String actualContent, String forma * @param lintsPerStep The list of lints produced by each step * @return A string in rdjsonl format representing the lints */ - public static String rdjsonlLints(String path, List steps, List> lintsPerStep) { + public static String rdjsonlLintsFromSteps(String path, List steps, List> lintsPerStep) { + if (steps == null || steps.isEmpty()) { + return rdjsonlLintsFromStrings(path, Collections.emptyList(), lintsPerStep); + } + List stepNames = steps.stream() + .map(FormatterStep::getName) + .collect(Collectors.toList()); + + if (lintsPerStep == null || lintsPerStep.isEmpty()) { + return rdjsonlLintsFromStrings(path, stepNames, Collections.emptyList()); + } + return rdjsonlLintsFromStrings(path, stepNames, lintsPerStep); + } + + private static String rdjsonlLintsFromStrings(String path, List stepNames, List> lintsPerStep) { if (lintsPerStep == null || lintsPerStep.isEmpty()) { return ""; } @@ -76,7 +92,7 @@ public static String rdjsonlLints(String path, List steps, List steps = new ArrayList<>(); List> lintsPerStep = new ArrayList<>(); - String result = ReviewDogGenerator.rdjsonlLints("test.txt", steps, lintsPerStep); + String result = ReviewDogGenerator.rdjsonlLintsFromSteps("test.txt", steps, lintsPerStep); Selfie.expectSelfie(result).toBe(""); } @@ -70,7 +70,7 @@ public void lintsSingleIssue() { Lint lint = Lint.atLine(1, "TEST001", "Test lint message"); List> lintsPerStep = Collections.singletonList(Collections.singletonList(lint)); - String result = ReviewDogGenerator.rdjsonlLints("src/main.java", steps, lintsPerStep); + String result = ReviewDogGenerator.rdjsonlLintsFromSteps("src/main.java", steps, lintsPerStep); Selfie.expectSelfie(result).toBe("{\"source\":\"spotless\",\"code\":\"testStep\",\"level\":\"warning\",\"message\":\"Test lint message\",\"path\":\"src/main.java\",\"line\":1,\"column\":1}"); } @@ -105,7 +105,7 @@ public void close() {} Collections.singletonList(lint1), Collections.singletonList(lint2)); - String result = ReviewDogGenerator.rdjsonlLints("src/main.java", steps, lintsPerStep); + String result = ReviewDogGenerator.rdjsonlLintsFromSteps("src/main.java", steps, lintsPerStep); Selfie.expectSelfie(result).toBe("{\"source\":\"spotless\",\"code\":\"step1\",\"level\":\"warning\",\"message\":\"First issue\",\"path\":\"src/main.java\",\"line\":1,\"column\":1}", "{\"source\":\"spotless\",\"code\":\"step2\",\"level\":\"warning\",\"message\":\"Second issue\",\"path\":\"src/main.java\",\"line\":5,\"column\":1}"); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessCheck.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessCheck.java index b6448fac4b..9285a28627 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessCheck.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessCheck.java @@ -37,6 +37,7 @@ import org.jetbrains.annotations.NotNull; import com.diffplug.spotless.FileSignature; +import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.ThrowingEx; import com.diffplug.spotless.extra.integration.DiffMessageFormatter; import com.diffplug.spotless.extra.middleware.ReviewDogGenerator; @@ -56,6 +57,10 @@ public abstract class SpotlessCheck extends SpotlessTaskService.ClientTask { @Optional public abstract Property getReviewDogOutputDir(); + @Input + @Optional + public abstract Property> getSteps(); + public void performActionTest() throws IOException { performAction(true); } @@ -114,8 +119,7 @@ private void performAction(boolean isTest) throws IOException { if (getReviewDog().get()) { for (File file : lintsFiles.getFiles()) { String path = file.getPath(); - // TODO : send or save the output of ReviewDogGenerator.rdjsonlLints - ReviewDogGenerator.rdjsonlLints(path, null, null); + ReviewDogGenerator.rdjsonlLintsFromSteps(path, getSteps().get(), null); } } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionImpl.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionImpl.java index 8f1765f0e8..4c84dce396 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionImpl.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionImpl.java @@ -15,6 +15,8 @@ */ package com.diffplug.gradle.spotless; +import java.util.ArrayList; + import org.gradle.api.Action; import org.gradle.api.Project; import org.gradle.api.plugins.BasePlugin; @@ -95,6 +97,8 @@ protected void createFormatTasks(String name, FormatExtension formatExtension) { // if the user enables the review dog, spotlessCheck will return the review dog format output task.getReviewDog().set(this.reviewDog); task.getReviewDogOutputDir().set(this.reviewDogOutputDir); + + task.getSteps().set(new ArrayList<>(source.getStepsInternalRoundtrip().getSteps())); }); rootCheckTask.configure(task -> task.dependsOn(checkTask));