Skip to content

Commit

Permalink
fail if formatting is "required"#23
Browse files Browse the repository at this point in the history
  • Loading branch information
omar salem committed Jul 30, 2023
1 parent 2034133 commit b21d2a2
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 177 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<groupId>com.theoryinpractise</groupId>
<artifactId>googleformatter-maven-plugin</artifactId>
<version>1.7.6-SNAPSHOT</version>
<version>1.8.0-SNAPSHOT</version>
<packaging>maven-plugin</packaging>

<name>googleformatter-maven-plugin Maven Mojo</name>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package com.theoryinpractise.googleformatter;

import com.google.common.base.MoreObjects;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
import com.google.common.io.CharStreams;
import com.google.googlejavaformat.java.Formatter;
import com.google.googlejavaformat.java.JavaFormatterOptions;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.scm.ScmException;
import org.apache.maven.scm.ScmFileSet;
import org.apache.maven.scm.manager.ScmManager;
import org.apache.maven.scm.repository.ScmRepository;
import org.codehaus.plexus.compiler.util.scan.InclusionScanException;
import org.codehaus.plexus.compiler.util.scan.SimpleSourceInclusionScanner;
import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner;
import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.*;
import java.util.stream.Collectors;

import static com.theoryinpractise.googleformatter.Constants.DIRECTORY_MISSING;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;

public abstract class AbstractFormatter extends AbstractMojo {

public static final SuffixMapping SOURCE_MAPPING = new SuffixMapping(".java", new HashSet<>(Arrays.asList(".java", ".class")));
@Component
ScmManager scmManager;

@Parameter(required = true, readonly = true, property = "session")
protected MavenSession session;

@Parameter(required = true, readonly = true, property = "project")
protected MavenProject project;

@Parameter(required = true, readonly = true, property = "project.build.sourceDirectory")
protected File sourceDirectory;

@Parameter(required = true, readonly = true, property = "project.build.testSourceDirectory")
protected File testSourceDirectory;

@Parameter(required = true, readonly = true, property = "project.build.outputDirectory")
protected File outputDirectory;

@Parameter(required = true, readonly = true, property = "project.build.testOutputDirectory")
protected File testOutputDirectory;

@Parameter(defaultValue = "false")
protected boolean includeStale;

@Parameter(defaultValue = "GOOGLE")
protected JavaFormatterOptions.Style style;

@Parameter(defaultValue = "false", property = "formatter.skip")
protected boolean skip;

@Parameter(defaultValue = "true", property = "formatter.main")
protected boolean formatMain;

@Parameter(defaultValue = "true", property = "formatter.test")
protected boolean formatTest;

@Parameter(defaultValue = "false", property = "formatter.modified")
protected boolean filterModified;

@Parameter(defaultValue = "false", property = "formatter.fixImports")
protected boolean fixImports;

@Parameter(defaultValue = "100", property = "formatter.maxLineLength")
protected int maxLineLength;

abstract void handleFormattedSource(File file, String formattedSource) throws MojoExecutionException;

@Override
public void execute() throws MojoExecutionException {

if ("pom".equals(project.getPackaging())) {
getLog().info("Project packaging is POM, skipping...");
return;
}

if (skip) {
getLog().info("Skipping source reformatting due to plugin configuration.");
return;
}

try {
Set<File> sourceFiles = new HashSet<>();

if (formatMain) {
sourceFiles.addAll(findFilesToReformat(sourceDirectory, outputDirectory));
}
if (formatTest) {
sourceFiles.addAll(findFilesToReformat(testSourceDirectory, testOutputDirectory));
}

Set<File> sourceFilesToProcess = filterModified ? filterUnchangedFiles(sourceFiles) : sourceFiles;

JavaFormatterOptions options = getJavaFormatterOptions();

for (File file : sourceFilesToProcess) {
String source = CharStreams.toString(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8));

com.google.googlejavaformat.java.Formatter formatter = new Formatter(options);
String formattedSource = fixImports ? formatter.formatSourceAndFixImports(source) : formatter.formatSource(source);

HashCode sourceHash = Hashing.sha256().hashString(source, StandardCharsets.UTF_8);
HashCode formattedHash = Hashing.sha256().hashString(formattedSource, StandardCharsets.UTF_8);

if (!formattedHash.equals(sourceHash)) {
handleFormattedSource(file, formattedSource);
}
}
} catch (Exception e) {
throw new MojoExecutionException(e.getMessage(), e);
}

}

private JavaFormatterOptions getJavaFormatterOptions() {
JavaFormatterOptions options = spy(JavaFormatterOptions.builder().style(style).build());
doReturn(maxLineLength).when(options).maxLineLength();
return options;
}

private Set<File> filterUnchangedFiles(Set<File> originalFiles) throws MojoExecutionException {
MavenProject topLevelProject = session.getTopLevelProject();
try {
if (topLevelProject.getScm().getConnection() == null && topLevelProject.getScm().getDeveloperConnection() == null) {
throw new MojoExecutionException(
"You must supply at least one of scm.connection or scm.developerConnection in your POM file if you " +
"specify the filterModified or filter.modified option.");
}
String connectionUrl = MoreObjects.firstNonNull(topLevelProject.getScm().getConnection(), topLevelProject.getScm().getDeveloperConnection());
ScmRepository repository = scmManager.makeScmRepository(connectionUrl);
ScmFileSet scmFileSet = new ScmFileSet(topLevelProject.getBasedir());
String basePath = topLevelProject.getBasedir().getAbsoluteFile().getPath();
List<String> changedFiles =
scmManager.status(repository, scmFileSet).getChangedFiles().stream()
.map(f -> new File(basePath, f.getPath()).toString())
.collect(Collectors.toList());

return originalFiles.stream().filter(f -> changedFiles.contains(f.getPath())).collect(Collectors.toSet());

} catch (ScmException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
}

private Set<File> findFilesToReformat(File sourceDirectory, File outputDirectory) throws MojoExecutionException {
if (sourceDirectory.exists()) {
try {
SourceInclusionScanner scanner = getSourceInclusionScanner(includeStale);
scanner.addSourceMapping(SOURCE_MAPPING);
Set<File> sourceFiles = scanner.getIncludedSources(sourceDirectory, outputDirectory);
getLog().info(String.format(Constants.FOUND_UNCOMPILED, sourceFiles.size(), sourceDirectory.getPath()));
return sourceFiles;
} catch (InclusionScanException e) {
throw new MojoExecutionException(String.format(Constants.ERROR_SCANNING_PATH, sourceDirectory.getPath()), e);
}
} else {
getLog().info(String.format(DIRECTORY_MISSING, sourceDirectory.getPath()));
return Collections.emptySet();
}
}

protected SourceInclusionScanner getSourceInclusionScanner(boolean includeStale) {
return includeStale ? new SimpleSourceInclusionScanner(Collections.singleton("**/*"), Collections.emptySet()) : new StaleSourceScanner(1024);
}
}
Original file line number Diff line number Diff line change
@@ -1,192 +1,27 @@
package com.theoryinpractise.googleformatter;

import com.google.common.base.MoreObjects;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
import com.google.common.io.CharStreams;
import com.google.googlejavaformat.java.Formatter;
import com.google.googlejavaformat.java.JavaFormatterOptions;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.scm.ScmException;
import org.apache.maven.scm.ScmFileSet;
import org.apache.maven.scm.manager.ScmManager;
import org.apache.maven.scm.repository.ScmRepository;
import org.codehaus.plexus.compiler.util.scan.InclusionScanException;
import org.codehaus.plexus.compiler.util.scan.SimpleSourceInclusionScanner;
import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner;
import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static com.google.googlejavaformat.java.JavaFormatterOptions.Style;
import static com.theoryinpractise.googleformatter.Constants.DIRECTORY_MISSING;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;

/** Reformat all source files using the Google Code Formatter */
@Mojo(name = "format", defaultPhase = LifecyclePhase.PROCESS_SOURCES)
public class GoogleFormatterMojo extends AbstractMojo {

public static final SuffixMapping SOURCE_MAPPING = new SuffixMapping(".java", new HashSet<>(Arrays.asList(".java", ".class")));
@Component ScmManager scmManager;

@Parameter(required = true, readonly = true, property = "session")
protected MavenSession session;

@Parameter(required = true, readonly = true, property = "project")
protected MavenProject project;

@Parameter(required = true, readonly = true, property = "project.build.sourceDirectory")
protected File sourceDirectory;

@Parameter(required = true, readonly = true, property = "project.build.testSourceDirectory")
protected File testSourceDirectory;

@Parameter(required = true, readonly = true, property = "project.build.outputDirectory")
protected File outputDirectory;

@Parameter(required = true, readonly = true, property = "project.build.testOutputDirectory")
protected File testOutputDirectory;

@Parameter(defaultValue = "false")
protected boolean includeStale;

@Parameter(defaultValue = "GOOGLE")
protected Style style;

@Parameter(defaultValue = "false", property = "formatter.skip")
protected boolean skip;

@Parameter(defaultValue = "true", property = "formatter.main")
protected boolean formatMain;

@Parameter(defaultValue = "true", property = "formatter.test")
protected boolean formatTest;

@Parameter(defaultValue = "false", property = "formatter.modified")
protected boolean filterModified;

@Parameter(defaultValue = "false", property = "formatter.fixImports")
protected boolean fixImports;

@Parameter(defaultValue = "100", property = "formatter.maxLineLength")
protected int maxLineLength;

@Override
public void execute() throws MojoExecutionException {

if ("pom".equals(project.getPackaging())) {
getLog().info("Project packaging is POM, skipping...");
return;
}

if (skip) {
getLog().info("Skipping source reformatting due to plugin configuration.");
return;
}

try {
Set<File> sourceFiles = new HashSet<>();

if (formatMain) {
sourceFiles.addAll(findFilesToReformat(sourceDirectory, outputDirectory));
}
if (formatTest) {
sourceFiles.addAll(findFilesToReformat(testSourceDirectory, testOutputDirectory));
}

Set<File> sourceFilesToProcess = filterModified ? filterUnchangedFiles(sourceFiles) : sourceFiles;

JavaFormatterOptions options = getJavaFormatterOptions();

for (File file : sourceFilesToProcess) {
String source = CharStreams.toString(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8));

Formatter formatter = new Formatter(options);
String formattedSource = fixImports ? formatter.formatSourceAndFixImports(source) : formatter.formatSource(source);

HashCode sourceHash = Hashing.sha256().hashString(source, StandardCharsets.UTF_8);
HashCode formattedHash = Hashing.sha256().hashString(formattedSource, StandardCharsets.UTF_8);

if (!formattedHash.equals(sourceHash)) {
// overwrite existing file
Files.write(file.toPath(), formattedSource.getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);

getLog().info(String.format("Reformatted file %s", file.getPath()));
public class GoogleFormatterMojo extends AbstractFormatter {

@Override
public void handleFormattedSource(File file, String formattedSource) throws MojoExecutionException {
// overwrite existing file
try {
Files.write(file.toPath(), formattedSource.getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
} catch (IOException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
}
} catch (Exception e) {
throw new MojoExecutionException(e.getMessage(), e);
getLog().info(String.format("Reformatted file %s", file.getPath()));
}
}

private JavaFormatterOptions getJavaFormatterOptions() {
JavaFormatterOptions options = spy(JavaFormatterOptions.builder().style(style).build());
doReturn(maxLineLength).when(options).maxLineLength();
return options;
}

private Set<File> filterUnchangedFiles(Set<File> originalFiles) throws MojoExecutionException {
MavenProject topLevelProject = session.getTopLevelProject();
try {
if (topLevelProject.getScm().getConnection() == null && topLevelProject.getScm().getDeveloperConnection() == null) {
throw new MojoExecutionException(
"You must supply at least one of scm.connection or scm.developerConnection in your POM file if you " +
"specify the filterModified or filter.modified option.");
}
String connectionUrl = MoreObjects.firstNonNull(topLevelProject.getScm().getConnection(), topLevelProject.getScm().getDeveloperConnection());
ScmRepository repository = scmManager.makeScmRepository(connectionUrl);
ScmFileSet scmFileSet = new ScmFileSet(topLevelProject.getBasedir());
String basePath = topLevelProject.getBasedir().getAbsoluteFile().getPath();
List<String> changedFiles =
scmManager.status(repository, scmFileSet).getChangedFiles().stream()
.map(f -> new File(basePath, f.getPath()).toString())
.collect(Collectors.toList());

return originalFiles.stream().filter(f -> changedFiles.contains(f.getPath())).collect(Collectors.toSet());

} catch (ScmException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
}

private Set<File> findFilesToReformat(File sourceDirectory, File outputDirectory) throws MojoExecutionException {
if (sourceDirectory.exists()) {
try {
SourceInclusionScanner scanner = getSourceInclusionScanner(includeStale);
scanner.addSourceMapping(SOURCE_MAPPING);
Set<File> sourceFiles = scanner.getIncludedSources(sourceDirectory, outputDirectory);
getLog().info(String.format(Constants.FOUND_UNCOMPILED, sourceFiles.size(), sourceDirectory.getPath()));
return sourceFiles;
} catch (InclusionScanException e) {
throw new MojoExecutionException(String.format(Constants.ERROR_SCANNING_PATH, sourceDirectory.getPath()), e);
}
} else {
getLog().info(String.format(DIRECTORY_MISSING, sourceDirectory.getPath()));
return Collections.emptySet();
}
}

protected SourceInclusionScanner getSourceInclusionScanner(boolean includeStale) {
return includeStale ? new SimpleSourceInclusionScanner(Collections.singleton("**/*"), Collections.emptySet()) : new StaleSourceScanner(1024);
}
}
Loading

0 comments on commit b21d2a2

Please sign in to comment.