Excluded generated sources from finalguard plugin
Some checks failed
CI / build (push) Has been cancelled

Also added optional list of exclusion paths
This commit is contained in:
2026-02-19 19:41:05 +08:00
parent c5dcee554e
commit 727e6ad896
7 changed files with 81 additions and 32 deletions

View File

@@ -1,35 +1,18 @@
package net.woggioni.finalguard;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.CatchTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.source.tree.ForLoopTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TryTree;
import com.sun.source.tree.UnaryTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.JavacTask;
import com.sun.source.util.Plugin;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskListener;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreePathScanner;
import com.sun.source.util.Trees;
import com.sun.source.tree.*;
import com.sun.source.util.*;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.tools.Diagnostic;
import java.util.*;
import java.util.stream.Collectors;
public class FinalGuardPlugin implements Plugin {
public static final String DEFAULT_LEVEL_KEY = "default.level";
public static final String EXCLUDE_KEY = "exclude";
public static final String IGNORE_ABSTRACT_METHOD_PARAMS_KEY = "net.woggioni.finalguard.ignore.abstract.method.params";
enum VariableType {
LOCAL_VAR("local.variable.level"),
@@ -83,15 +66,22 @@ public class FinalGuardPlugin implements Plugin {
private static final class Configuration {
private final Map<VariableType, Diagnostic.Kind> levels;
private final List<String> excludedPaths;
public Configuration(final String... args) {
final Map<String, String> props = new HashMap<>();
final List<String> excluded = new ArrayList<>();
for (final String arg : args) {
final String[] parts = arg.split("=", 2);
if (parts.length == 2) {
props.put(parts[0], parts[1]);
if (EXCLUDE_KEY.equals(parts[0])) {
excluded.add(parts[1]);
} else {
props.put(parts[0], parts[1]);
}
}
}
this.excludedPaths = Collections.unmodifiableList(excluded);
final Diagnostic.Kind defaultLevel =
Optional.ofNullable(props.get(DEFAULT_LEVEL_KEY)).map(Diagnostic.Kind::valueOf).orElse(null);
this.levels = Arrays.stream(VariableType.values()).map(vt -> {
@@ -103,6 +93,15 @@ public class FinalGuardPlugin implements Plugin {
}
}).filter(Objects::nonNull).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
public boolean isExcluded(final String sourcePath) {
for (final String excludedPath : excludedPaths) {
if (sourcePath.startsWith(excludedPath)) {
return true;
}
}
return false;
}
}
private static boolean isJava17OrHigher() {
@@ -134,6 +133,10 @@ public class FinalGuardPlugin implements Plugin {
}
private void analyzeFinalVariables(CompilationUnitTree compilationUnit, JavacTask task, Element typeElement, Configuration configuration) {
final String sourcePath = compilationUnit.getSourceFile().toUri().getPath();
if (sourcePath != null && configuration.isExcluded(sourcePath)) {
return;
}
FinalVariableAnalyzer analyzer = new FinalVariableAnalyzer(compilationUnit, task, configuration);
TreePath path = Trees.instance(task).getPath(typeElement);
if (path != null) {

View File

@@ -1,6 +1,7 @@
package net.woggioni.finalguard;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
@@ -81,15 +82,23 @@ public class PluginTest {
}
private Optional<Iterable<Diagnostic<? extends JavaFileObject>>> compile(Iterable<URI> sources) {
return compile(sources, "");
}
private Optional<Iterable<Diagnostic<? extends JavaFileObject>>> compile(Iterable<URI> sources, String extraPluginArgs) {
StringWriter output = new StringWriter();
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
FileManager fileManager =
new FileManager(compiler.getStandardFileManager(null, null, null));
List<JavaFileObject> compilationUnits = StreamSupport.stream(sources.spliterator(), false)
.map(SourceFile::new).collect(Collectors.toList());
String pluginArg = "-Xplugin:" + FinalGuardPlugin.class.getName() + " " + FinalGuardPlugin.DEFAULT_LEVEL_KEY + "=ERROR";
if (!extraPluginArgs.isEmpty()) {
pluginArg += " " + extraPluginArgs;
}
List<String> arguments = Arrays.asList(
"-classpath", System.getProperty("test.compilation.classpath"),
"-Xplugin:" + FinalGuardPlugin.class.getName() + " " + FinalGuardPlugin.DEFAULT_LEVEL_KEY + "=ERROR"
pluginArg
);
final ArrayList<Diagnostic<? extends JavaFileObject>> compilerMessages = new ArrayList<>();
JavaCompiler.CompilationTask task = compiler.getTask(
@@ -184,4 +193,25 @@ public class PluginTest {
Assertions.assertTrue(compilationErrors.isEmpty(), "Unexpected compilation errors found in the output");
}
}
@Test
public void testExcludedSourceIsSkipped() throws Exception {
// TestCase3 normally produces an error (local var 'n' not final).
// When we exclude the directory containing TestCase3, it should compile without errors.
ClassLoader cl = getClass().getClassLoader();
URI sourceUri = cl.getResource("net/woggioni/finalguard/test/TestCase3.java").toURI();
String sourcePath = new File(sourceUri).getParent();
// Compile WITH exclude — should succeed (no errors)
Optional<Iterable<Diagnostic<? extends JavaFileObject>>> resultWithExclude =
compile(Collections.singletonList(sourceUri), FinalGuardPlugin.EXCLUDE_KEY + "=" + sourcePath);
Assertions.assertFalse(resultWithExclude.isPresent(),
"Compilation should succeed when source directory is excluded");
// Compile WITHOUT exclude — should fail (TestCase3 has errors)
Optional<Iterable<Diagnostic<? extends JavaFileObject>>> resultWithoutExclude =
compile(Collections.singletonList(sourceUri));
Assertions.assertTrue(resultWithoutExclude.isPresent(),
"Compilation should fail when source directory is NOT excluded");
}
}

View File

@@ -1,4 +1,2 @@
import java.util.Arrays;
public record TestCase12<T, U>(double x, double y) {
}

View File

@@ -1,5 +1,3 @@
import java.util.Arrays;
public interface TestCase13 {
void foo(double x, double y);
}

View File

@@ -1,5 +1,3 @@
import java.util.Arrays;
public abstract class TestCase14 {
abstract public void foo(double x, double y);
}

View File

@@ -1,5 +1,6 @@
package net.woggioni.gradle.finalguard;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
import javax.tools.Diagnostic;
@@ -21,4 +22,8 @@ public interface FinalGuardExtension {
Property<Diagnostic.Kind> getAbstractMethodParameterLevel();
Property<Diagnostic.Kind> getCatchParameterLevel();
Property<Boolean> getSkipGeneratedSources();
ListProperty<String> getExcludedPrefixes();
}

View File

@@ -14,6 +14,7 @@ import org.gradle.api.tasks.compile.CompileOptions;
import org.gradle.api.tasks.compile.JavaCompile;
import javax.tools.Diagnostic;
import java.io.File;
import java.net.URL;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
@@ -21,6 +22,7 @@ import java.util.jar.Manifest;
public class FinalGuardPlugin implements Plugin<Project> {
private static final String FINALGUARD_PLUGIN_CONFIGURATION = "finalguard_plugin";
private static final String JAVAC_PLUGIN_NAME = "net.woggioni.finalguard.FinalGuardPlugin";
private static final String EXCLUDE_KEY = "exclude";
@Override
public void apply(final Project project) {
@@ -49,10 +51,12 @@ public class FinalGuardPlugin implements Plugin<Project> {
});
final FinalGuardExtension finalGuardExtension = objects.newInstance(FinalGuardExtension.class);
extensionContainer.add("finalGuard", finalGuardExtension);
finalGuardExtension.getSkipGeneratedSources().convention(true);
extensionContainer.add("finalguard", finalGuardExtension);
tasks.withType(JavaCompile.class, javaCompileTask -> {
javaCompileTask.doFirst(t -> {
final CompileOptions options = javaCompileTask.getOptions();
options.setAnnotationProcessorPath(options.getAnnotationProcessorPath().plus(javacPluginConfiguration));
final StringBuilder xpluginArg = new StringBuilder("-Xplugin:").append(JAVAC_PLUGIN_NAME);
appendOption(xpluginArg, "default.level", finalGuardExtension.getDefaultLevel());
appendOption(xpluginArg, "local.variable.level", finalGuardExtension.getLocalVariableLevel());
@@ -62,14 +66,27 @@ public class FinalGuardPlugin implements Plugin<Project> {
appendOption(xpluginArg, "try.param.level", finalGuardExtension.getTryWithResourceLevel());
appendOption(xpluginArg, "catch.param.level", finalGuardExtension.getCatchParameterLevel());
appendOption(xpluginArg, "lambda.param.level", finalGuardExtension.getLambdaParameterLevel());
if (finalGuardExtension.getSkipGeneratedSources().getOrElse(true)) {
final File generatedSourceDir = options.getGeneratedSourceOutputDirectory().getAsFile().getOrNull();
if (generatedSourceDir != null) {
appendOption(xpluginArg, EXCLUDE_KEY, generatedSourceDir.getAbsolutePath());
}
}
for(final String excludedPrefix : finalGuardExtension.getExcludedPrefixes().get()) {
appendOption(xpluginArg, EXCLUDE_KEY, excludedPrefix);
}
options.getCompilerArgs().add(xpluginArg.toString());
});
});
}
private static void appendOption(StringBuilder sb, String key, String value) {
sb.append(' ').append(key).append('=').append(value);
}
private static void appendOption(StringBuilder sb, String key, Property<Diagnostic.Kind> property) {
if (property.isPresent()) {
sb.append(' ').append(key).append('=').append(property.get());
appendOption(sb, key, property.get().toString());
}
}
}