added finalguard plugin in aplha state
All checks were successful
CI / build (push) Successful in 3m17s
All checks were successful
CI / build (push) Successful in 3m17s
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
package net.woggioni.finalguard;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.ArgumentsProvider;
|
||||
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.FileObject;
|
||||
import javax.tools.ForwardingJavaFileManager;
|
||||
import javax.tools.JavaCompiler;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.SimpleJavaFileObject;
|
||||
import javax.tools.StandardJavaFileManager;
|
||||
import javax.tools.ToolProvider;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.StringWriter;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
public class PluginTest {
|
||||
|
||||
private static class ClassFile extends SimpleJavaFileObject {
|
||||
|
||||
private ByteArrayOutputStream out;
|
||||
|
||||
public ClassFile(URI uri) {
|
||||
super(uri, Kind.CLASS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream openOutputStream() {
|
||||
return out = new ByteArrayOutputStream();
|
||||
}
|
||||
|
||||
public byte[] getCompiledBinaries() {
|
||||
return out.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
private static class SourceFile extends SimpleJavaFileObject {
|
||||
public SourceFile(URI uri) {
|
||||
super(uri, Kind.SOURCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
|
||||
Reader r = new InputStreamReader(uri.toURL().openStream());
|
||||
StringBuilder sb = new StringBuilder();
|
||||
char[] buffer = new char[0x1000];
|
||||
while (true) {
|
||||
int read = r.read(buffer);
|
||||
if(read < 0) break;
|
||||
sb.append(buffer, 0, read);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static class FileManager extends ForwardingJavaFileManager<StandardJavaFileManager> {
|
||||
|
||||
private final List<ClassFile> compiled = new ArrayList<>();
|
||||
|
||||
protected FileManager(StandardJavaFileManager fileManager) {
|
||||
super(fileManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaFileObject getJavaFileForOutput(Location location,
|
||||
String className,
|
||||
JavaFileObject.Kind kind,
|
||||
FileObject sibling) {
|
||||
ClassFile result = new ClassFile(URI.create("string://" + className));
|
||||
compiled.add(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<ClassFile> getCompiled() {
|
||||
return compiled;
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<Iterable<Diagnostic<? extends JavaFileObject>>> compile(Iterable<URI> sources) {
|
||||
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());
|
||||
List<String> arguments = Arrays.asList(
|
||||
"-classpath", System.getProperty("test.compilation.classpath"),
|
||||
"-Xplugin:" + FinalGuardPlugin.class.getSimpleName()
|
||||
);
|
||||
final ArrayList<Diagnostic<? extends JavaFileObject>> compilerMessages = new ArrayList<>();
|
||||
System.setProperty(FinalGuardPlugin.DIAGNOSTIC_LEVEL_KEY, "ERROR");
|
||||
JavaCompiler.CompilationTask task = compiler.getTask(
|
||||
output,
|
||||
fileManager,
|
||||
compilerMessages::add,
|
||||
arguments,
|
||||
null,
|
||||
compilationUnits
|
||||
);
|
||||
if(task.call()) return Optional.empty();
|
||||
else return Optional.of(compilerMessages);
|
||||
}
|
||||
|
||||
private enum CompilationResult {
|
||||
SUCCESS, FAILURE
|
||||
}
|
||||
|
||||
private static class TestCaseProvider implements ArgumentsProvider {
|
||||
@Override
|
||||
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
|
||||
String prefix = "net/woggioni/finalguard/test/";
|
||||
return Stream.of(
|
||||
Arguments.of(prefix + "TestCase1.java", CompilationResult.FAILURE),
|
||||
Arguments.of(prefix + "TestCase2.java", CompilationResult.SUCCESS),
|
||||
Arguments.of(prefix + "TestCase3.java", CompilationResult.FAILURE),
|
||||
Arguments.of(prefix + "TestCase4.java", CompilationResult.FAILURE),
|
||||
Arguments.of(prefix + "TestCase5.java", CompilationResult.FAILURE),
|
||||
Arguments.of(prefix + "TestCase6.java", CompilationResult.FAILURE),
|
||||
Arguments.of(prefix + "TestCase7.java", CompilationResult.FAILURE),
|
||||
Arguments.of(prefix + "TestCase8.java", CompilationResult.FAILURE)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Disabled
|
||||
@ParameterizedTest(name="{0}")
|
||||
@ArgumentsSource(TestCaseProvider.class)
|
||||
public void test(String sourceFilePath, CompilationResult expectedCompilationResult) {
|
||||
Optional<Iterable<Diagnostic<? extends JavaFileObject>>> result;
|
||||
try {
|
||||
ClassLoader cl = getClass().getClassLoader();
|
||||
result = compile(Collections.singletonList(cl.getResource(sourceFilePath).toURI()));
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
result.ifPresent(diagnostics -> {
|
||||
for(Diagnostic<? extends JavaFileObject> diagnostic : diagnostics) {
|
||||
System.err.printf("%s:%s %s\n",
|
||||
diagnostic.getSource().getName(),
|
||||
diagnostic.getLineNumber(),
|
||||
diagnostic.getMessage(Locale.getDefault()));
|
||||
}
|
||||
});
|
||||
final Pattern errorMessage = Pattern.compile(
|
||||
"Local variable '[a-zA-Z0-9_]+' is never reassigned, so it should be declared final");
|
||||
switch (expectedCompilationResult) {
|
||||
case SUCCESS:
|
||||
Assertions.assertFalse(result.isPresent(), "Compilation was expected to succeed but it has failed");
|
||||
break;
|
||||
case FAILURE:
|
||||
Assertions.assertTrue(result.isPresent(), "Compilation was expected to fail but it succeeded");
|
||||
|
||||
Optional<Diagnostic<? extends JavaFileObject>> illegalSuspendableInvocationMessage = StreamSupport.stream(
|
||||
result.get().spliterator(), false)
|
||||
.filter( diagnostic ->
|
||||
errorMessage.matcher(diagnostic.getMessage(Locale.ENGLISH)).matches()
|
||||
).findFirst();
|
||||
Assertions.assertTrue(illegalSuspendableInvocationMessage.isPresent());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import java.util.Arrays;
|
||||
|
||||
public class TestCase1 {
|
||||
|
||||
public void testMethod(String param1, String param2) { // Warning: param1 could be final
|
||||
String localVar = "hello"; // Warning: localVar could be final
|
||||
String reassignedVar = "initial"; // No warning (reassigned below)
|
||||
reassignedVar = "changed";
|
||||
|
||||
param2 = "modified"; // No warning for param2 (reassigned)
|
||||
|
||||
for (int i = 0; i < 10; i++) { // Warning: i could be final
|
||||
String loopVar = "constant"; // Warning: loopVar could be final
|
||||
}
|
||||
|
||||
// Enhanced for loop - no warning for loop variable
|
||||
for (String item : Arrays.asList("a", "b")) {
|
||||
// item is effectively final in each iteration
|
||||
}
|
||||
}
|
||||
|
||||
public void finalMethod(final String param1, String param2) { // Warning only for param2
|
||||
final String localVar = "hello"; // No warning (already final)
|
||||
String anotherVar = "world"; // Warning: anotherVar could be final
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
public class TestCase2 {
|
||||
|
||||
public void testMethod(int a, int b, int c, int d, int e, int f, int g, int h, int j, int k, int l, int m, int n) {
|
||||
a++;
|
||||
b--;
|
||||
++c;
|
||||
--d;
|
||||
e |= 2;
|
||||
f &= 1;
|
||||
g <<= 1;
|
||||
h >>= 1;
|
||||
j ^= 1;
|
||||
k += 1;
|
||||
l -= 1;
|
||||
m *= 1;
|
||||
n /= 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
public class TestCase3 {
|
||||
|
||||
public void testMethod() {
|
||||
int n = 5; // Error: n could be final
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
public class TestCase4 {
|
||||
|
||||
public void testMethod() {
|
||||
for (int i = 0; i < 10;) { // Error: i could be final
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
public class TestCase5 {
|
||||
|
||||
public void testMethod() {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
String loopVar = "constant"; // Error: loopVar should be final
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import java.util.Arrays;
|
||||
|
||||
public class TestCase6 {
|
||||
|
||||
public void testMethod() {
|
||||
for (String item : Arrays.asList("a", "b")) { // Error: item should be final
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
public class TestCase7 {
|
||||
|
||||
public void testMethod() {
|
||||
try {
|
||||
|
||||
} catch (RuntimeException re) { // Error: re should be final
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class TestCase8 {
|
||||
|
||||
public void testMethod() throws IOException {
|
||||
try(InputStream is = Files.newInputStream(Path.of("some-path"))) { // Error: is could be final
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user