Skip to content

Commit

Permalink
Add option to generate html report
Browse files Browse the repository at this point in the history
  • Loading branch information
funbiscuit committed Jul 8, 2023
1 parent f9f2946 commit 4853719
Show file tree
Hide file tree
Showing 12 changed files with 267 additions and 85 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.funbiscuit.idea.plugin.formatter;

import com.funbiscuit.idea.plugin.formatter.report.FileInfo;
import com.intellij.codeInsight.actions.AbstractLayoutCodeProcessor;
import com.intellij.codeInsight.actions.RearrangeCodeProcessor;
import com.intellij.codeInsight.actions.ReformatCodeProcessor;
Expand All @@ -12,35 +13,27 @@
import java.util.Objects;

public class FileFormatter implements FileProcessor {
private static final String PROCESS_RESULT_OK = "OK";
private static final String PROCESS_RESULT_READ_ONLY = "Skipped, read only";

private final FormatStatistics statistics;

public FileFormatter(FormatStatistics statistics) {
this.statistics = statistics;
}

@Override
public String processFile(PsiFile originalFile) {
public void processFile(PsiFile originalFile, FileInfo fileInfo) {
String originalContent = originalFile.getText();
FileDocumentManager documentManager = FileDocumentManager.getInstance();
VirtualFile virtualFile = PsiUtilCore.getVirtualFile(originalFile);
var document = documentManager.getDocument(Objects.requireNonNull(virtualFile));
if (!documentManager.requestWriting(Objects.requireNonNull(document), null)) {
return PROCESS_RESULT_READ_ONLY;
fileInfo.addWarning(ProcessStatuses.SKIPPED_READ_ONLY);
return;
}

AbstractLayoutCodeProcessor processor = new ReformatCodeProcessor(originalFile, false);
processor = new RearrangeCodeProcessor(processor);
NonProjectFileWritingAccessProvider.disableChecksDuring(processor::run);
FileDocumentManager.getInstance().saveDocument(document);

statistics.fileProcessed(true);
return PROCESS_RESULT_OK;
}

@Override
public String actionMessage() {
return "Formatting";
if (originalFile.getText().equals(originalContent)) {
fileInfo.addInfo(ProcessStatuses.FORMATTED_WELL);
} else {
fileInfo.addInfo(ProcessStatuses.FORMATTED);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package com.funbiscuit.idea.plugin.formatter;

import com.funbiscuit.idea.plugin.formatter.report.FileInfo;
import com.intellij.psi.PsiFile;

public interface FileProcessor {
String processFile(PsiFile originalFile);

String actionMessage();
void processFile(PsiFile originalFile, FileInfo fileInfo);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.funbiscuit.idea.plugin.formatter;

import com.funbiscuit.idea.plugin.formatter.report.FileInfo;
import com.intellij.codeInsight.actions.AbstractLayoutCodeProcessor;
import com.intellij.codeInsight.actions.RearrangeCodeProcessor;
import com.intellij.codeInsight.actions.ReformatCodeProcessor;
Expand All @@ -16,16 +17,12 @@
import java.util.UUID;

public class FileVerifier implements FileProcessor {
private static final String PROCESS_RESULT_DRY_OK = "Formatted well";
private static final String PROCESS_RESULT_DRY_FAIL = "Needs formatting";

private final Project project;
private final PsiDirectory projectPsiDir;
private final FormatStatistics statistics;

public FileVerifier(Project project, FormatStatistics statistics) {
public FileVerifier(Project project) {
this.project = project;
this.statistics = statistics;

VirtualFile baseDir = ProjectUtil.guessProjectDir(project);
if (baseDir == null) {
Expand All @@ -38,7 +35,7 @@ public FileVerifier(Project project, FormatStatistics statistics) {
}

@Override
public String processFile(PsiFile originalFile) {
public void processFile(PsiFile originalFile, FileInfo fileInfo) {
String originalContent = originalFile.getText();

PsiFile processedFile = createFileCopy(originalFile, originalContent);
Expand All @@ -48,19 +45,12 @@ public String processFile(PsiFile originalFile) {
processor.run();

if (processedFile.getText().equals(originalContent)) {
statistics.fileProcessed(true);
return PROCESS_RESULT_DRY_OK;
fileInfo.addInfo(ProcessStatuses.FORMATTED_WELL);
} else {
statistics.fileProcessed(false);
return PROCESS_RESULT_DRY_FAIL;
fileInfo.addError(ProcessStatuses.NEEDS_FORMATTING);
}
}

@Override
public String actionMessage() {
return "Checking";
}

private PsiFile createFileCopy(PsiFile originalFile, String content) {
return ApplicationManager.getApplication().runWriteAction((Computable<PsiFile>) () -> {
var psiCopy = PsiFileFactory.getInstance(project).createFileFromText(
Expand Down
106 changes: 79 additions & 27 deletions src/main/java/com/funbiscuit/idea/plugin/formatter/FormatCommand.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.funbiscuit.idea.plugin.formatter;

import com.funbiscuit.idea.plugin.formatter.report.*;
import com.intellij.application.options.CodeStyle;
import com.intellij.formatting.commandLine.StdIoMessageOutput;
import com.intellij.ide.RecentProjectsManager;
Expand All @@ -11,7 +12,6 @@
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.impl.source.codeStyle.CodeStyleSettingsLoader;
import picocli.CommandLine;
Expand All @@ -22,6 +22,8 @@
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;
Expand All @@ -35,9 +37,6 @@ public class FormatCommand implements Callable<Integer> {
private static final StdIoMessageOutput messageOutput = StdIoMessageOutput.INSTANCE;
private static final String PROJECT_DIR_PREFIX = "idea.reformat.";
private static final String PROJECT_DIR_SUFFIX = ".tmp";
private static final String PROCESS_RESULT_BINARY_FILE = "Skipped, binary file.";
private static final String PROCESS_RESULT_FAILED_OPEN = "Failed to open.";
private static final String PROCESS_RESULT_FAILED_TO_PROCESS = "Failed to process.";

@Option(names = {"-s", "--style"}, required = true, description = "A path to Intellij IDEA code style settings .xml file")
private Path style;
Expand All @@ -52,6 +51,9 @@ public class FormatCommand implements Callable<Integer> {
@Option(names = {"-d", "--dry"}, description = "Perform a dry run: no file modifications, only exit status")
private boolean dry;

@Option(names = {"--report"}, description = "Where to save report (by default not saved). Supported extensions: '.html'")
private Path report;

@Parameters(index = "1..*", paramLabel = "<file>", description = "A path to a file or a directory")
private List<Path> files = List.of();

Expand All @@ -62,6 +64,17 @@ public class FormatCommand implements Callable<Integer> {
private Project project;
private FileProcessor fileProcessor;

private static Reporter createReporterForFile(Path file) {
if (file == null) {
return null;
}
if (file.toString().endsWith(".html")) {
return new HtmlReporter();
} else {
return null;
}
}

@Override
public Integer call() throws Exception {
Predicate<String> fileNamePredicate = masks.stream()
Expand All @@ -75,74 +88,113 @@ public Integer call() throws Exception {
.reduce(Predicate::or)
.orElse(p -> true);

var reporter = createReporterForFile(report);
if (report != null && reporter == null) {
messageOutput.info("Given report file extension is not supported\n");
return CommandLine.ExitCode.SOFTWARE;
}

createTempProject();

loadSettings(style);

FormatStatistics statistics = new FormatStatistics();
if (dry) {
fileProcessor = new FileVerifier(project, statistics);
fileProcessor = new FileVerifier(project);
} else {
fileProcessor = new FileFormatter(statistics);
fileProcessor = new FileFormatter();
}

List<Path> allFiles = new ArrayList<>();
messageOutput.info("Counting files...\n");
for (var path : files) {
try (var stream = Files.walk(path, recursive ? Integer.MAX_VALUE : 1)) {
stream
List<Path> dirFiles = stream
.filter(Files::isRegularFile)
.filter(p -> fileNamePredicate.test(p.toString()))
.forEach(this::processPath);
.toList();
allFiles.addAll(dirFiles);
}
}
messageOutput.info("Processing %d files%n".formatted(allFiles.size()));
List<FileInfo> fileInfos = new ArrayList<>();

var lastPrintTime = System.currentTimeMillis();
for (int i = 0; i < allFiles.size(); i++) {
Path file = allFiles.get(i);
fileInfos.add(processPath(file));

var now = System.currentTimeMillis();
if (now - lastPrintTime > 1000L) {
lastPrintTime = now;
messageOutput.info("Processed %d/%d files%n".formatted(i + 1, allFiles.size()));
}
}
messageOutput.info("Processed %d files%n".formatted(allFiles.size()));

RecentProjectsManager.getInstance().removePath(projectPath.toString());

messageOutput.info("Processed: %d%n".formatted(statistics.getProcessed()));

return statistics.allValid() ? CommandLine.ExitCode.OK : CommandLine.ExitCode.SOFTWARE;
var withErrors = fileInfos.stream().anyMatch(FileInfo::hasErrors);


List<ProcessResult> processResults = fileInfos.stream()
.flatMap(info -> info.getProcessLog().stream()
.map(entry -> new ProcessResult(info.getFilename(), entry.level(), entry.message())))
.sorted(Comparator.comparing(ProcessResult::level).reversed().thenComparing(ProcessResult::filename))
.toList();

if (reporter != null) {
reporter.generate(report, processResults);
} else {
// print report to stdout only when no report file is given
processResults.stream()
.filter(status -> status.level() != Level.INFO)
.forEach(status -> messageOutput.info(
"%s %s: %s%n".formatted(status.filename(), status.level(), status.status())
));
}

return withErrors ? CommandLine.ExitCode.SOFTWARE : CommandLine.ExitCode.OK;
}

private void processPath(Path filePath) {
messageOutput.info("%s %s... ".formatted(fileProcessor.actionMessage(), filePath));
String result;
private FileInfo processPath(Path filePath) {
Exception ex = null;
var fileInfo = new FileInfo(filePath.toString());
try {
result = processPathInternal(filePath);
processPathInternal(filePath, fileInfo);
} catch (Exception e) {
result = PROCESS_RESULT_FAILED_TO_PROCESS;
ex = e;
fileInfo.addWarning(ProcessStatuses.FAILED_TO_PROCESS);
}
messageOutput.info("%s%n".formatted(result));
if (ex != null) {
ex.printStackTrace();
}
return fileInfo;
}

private String processPathInternal(Path filePath) {
private void processPathInternal(Path filePath, FileInfo fileInfo) {
PsiManager psiManager = PsiManager.getInstance(project);

var virtualFile = VirtualFileManager.getInstance().refreshAndFindFileByNioPath(filePath);
if (virtualFile == null) {
return PROCESS_RESULT_FAILED_OPEN;
fileInfo.addWarning(ProcessStatuses.FAILED_TO_OPEN);
return;
}

virtualFile.refresh(false, false);

if (virtualFile.getFileType().isBinary()) {
return PROCESS_RESULT_BINARY_FILE;
fileInfo.addWarning(ProcessStatuses.SKIPPED_BINARY_FILE);
return;
}

var psiFile = psiManager.findFile(virtualFile);
if (psiFile == null) {
return PROCESS_RESULT_FAILED_OPEN;
fileInfo.addWarning(ProcessStatuses.FAILED_TO_OPEN);
return;
}

return processPsiFile(psiFile);
}


private String processPsiFile(PsiFile originalFile) {
return fileProcessor.processFile(originalFile);
fileProcessor.processFile(psiFile, fileInfo);
}

private void loadSettings(Path stylePath) throws SchemeImportException {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.funbiscuit.idea.plugin.formatter;

/**
* @author skokurin
* @since 08.07.2023
*/
public final class ProcessStatuses {
public static final String SKIPPED_BINARY_FILE = "Skipped, binary file";
public static final String FAILED_TO_OPEN = "Failed to open";
public static final String FAILED_TO_PROCESS = "Failed to process";
public static final String SKIPPED_READ_ONLY = "Skipped, read only";
public static final String FORMATTED = "Formatted";
public static final String FORMATTED_WELL = "Formatted well";
public static final String NEEDS_FORMATTING = "Needs formatting";

private ProcessStatuses() {
}
}
Loading

0 comments on commit 4853719

Please sign in to comment.