This commit is contained in:
@@ -25,7 +25,7 @@ test {
|
|||||||
sourceSets["main"].runtimeClasspath.files +
|
sourceSets["main"].runtimeClasspath.files +
|
||||||
sourceSets["test"].resources.srcDirs
|
sourceSets["test"].resources.srcDirs
|
||||||
systemProperty("test.compilation.classpath",
|
systemProperty("test.compilation.classpath",
|
||||||
String.join(File.pathSeparator, testCompilationClassPath.collect {it.toString() }))
|
String.join(File.pathSeparator, testCompilationClassPath.collect { it.toString() }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
package net.woggioni.finalguard;
|
package net.woggioni.finalguard;
|
||||||
|
|
||||||
import com.sun.source.tree.AssignmentTree;
|
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.CompilationUnitTree;
|
||||||
import com.sun.source.tree.CompoundAssignmentTree;
|
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.IdentifierTree;
|
||||||
|
import com.sun.source.tree.LambdaExpressionTree;
|
||||||
import com.sun.source.tree.MethodTree;
|
import com.sun.source.tree.MethodTree;
|
||||||
import com.sun.source.tree.Tree;
|
import com.sun.source.tree.Tree;
|
||||||
|
import com.sun.source.tree.TryTree;
|
||||||
import com.sun.source.tree.UnaryTree;
|
import com.sun.source.tree.UnaryTree;
|
||||||
import com.sun.source.tree.VariableTree;
|
import com.sun.source.tree.VariableTree;
|
||||||
import com.sun.source.util.JavacTask;
|
import com.sun.source.util.JavacTask;
|
||||||
@@ -19,20 +25,86 @@ import com.sun.source.util.Trees;
|
|||||||
import javax.lang.model.element.Element;
|
import javax.lang.model.element.Element;
|
||||||
import javax.lang.model.element.Modifier;
|
import javax.lang.model.element.Modifier;
|
||||||
import javax.tools.Diagnostic;
|
import javax.tools.Diagnostic;
|
||||||
import java.util.HashMap;
|
import java.util.AbstractMap;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class FinalGuardPlugin implements Plugin {
|
public class FinalGuardPlugin implements Plugin {
|
||||||
|
|
||||||
public static final String DIAGNOSTIC_LEVEL_KEY = "net.woggioni.finalguard.diagnostic.level";
|
public static final String DIAGNOSTIC_LEVEL_KEY = "net.woggioni.finalguard.diagnostic.level";
|
||||||
|
|
||||||
private static final Diagnostic.Kind diagnosticLevel =
|
enum VariableType {
|
||||||
Optional.ofNullable(System.getProperty(DIAGNOSTIC_LEVEL_KEY))
|
LOCAL_VAR("net.woggioni.finalguard.diagnostic.local.variable.level"),
|
||||||
.map(Diagnostic.Kind::valueOf)
|
METHOD_PARAM("net.woggioni.finalguard.diagnostic.method.param.level"),
|
||||||
.orElse(Diagnostic.Kind.WARNING);
|
LOOP_PARAM("net.woggioni.finalguard.diagnostic.for.param.level"),
|
||||||
|
TRY_WITH_PARAM("net.woggioni.finalguard.diagnostic.try.param.level"),
|
||||||
|
CATCH_PARAM("net.woggioni.finalguard.diagnostic.catch.param.level"),
|
||||||
|
LAMBDA_PARAM("net.woggioni.finalguard.diagnostic.lambda.param.level");
|
||||||
|
|
||||||
|
private final String propertyKey;
|
||||||
|
|
||||||
|
VariableType(final String propertyKey) {
|
||||||
|
this.propertyKey = propertyKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPropertyKey() {
|
||||||
|
return propertyKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage(final String variableName) {
|
||||||
|
switch (this) {
|
||||||
|
case LOCAL_VAR:
|
||||||
|
return "Local variable '" + variableName + "' is never reassigned, so it should be declared final";
|
||||||
|
case METHOD_PARAM:
|
||||||
|
return "Method parameter '" + variableName + "' is never reassigned, so it should be declared final";
|
||||||
|
case LOOP_PARAM:
|
||||||
|
return "Loop parameter '" + variableName + "' is never reassigned, so it should be declared final";
|
||||||
|
case TRY_WITH_PARAM:
|
||||||
|
return "Try-with-resources parameter '" + variableName + "' is never reassigned, so it should be declared final";
|
||||||
|
case CATCH_PARAM:
|
||||||
|
return "Catch parameter '" + variableName + "' is never reassigned, so it should be declared final";
|
||||||
|
case LAMBDA_PARAM:
|
||||||
|
return "Lambda parameter '" + variableName + "' is never reassigned, so it should be declared final";
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class VariableInfo {
|
||||||
|
final VariableTree variableTree;
|
||||||
|
final VariableType variableType;
|
||||||
|
|
||||||
|
VariableInfo(VariableTree variableTree, VariableType variableType) {
|
||||||
|
this.variableTree = variableTree;
|
||||||
|
this.variableType = variableType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class Configuration {
|
||||||
|
private final Map<VariableType, Diagnostic.Kind> levels;
|
||||||
|
|
||||||
|
public Configuration() {
|
||||||
|
final Diagnostic.Kind defaultLevel =
|
||||||
|
Optional.ofNullable(System.getProperty(DIAGNOSTIC_LEVEL_KEY)).map(Diagnostic.Kind::valueOf).orElse(null);
|
||||||
|
this.levels = Arrays.stream(VariableType.values()).map(vt -> {
|
||||||
|
final Diagnostic.Kind level = Optional.ofNullable(System.getProperty(vt.getPropertyKey())).map(Diagnostic.Kind::valueOf).orElse(defaultLevel);
|
||||||
|
if (level != null) {
|
||||||
|
return new AbstractMap.SimpleEntry<>(vt, level);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}).filter(Objects::nonNull).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Configuration configuration = new Configuration();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
@@ -43,7 +115,8 @@ public class FinalGuardPlugin implements Plugin {
|
|||||||
public void init(JavacTask task, String... args) {
|
public void init(JavacTask task, String... args) {
|
||||||
task.addTaskListener(new TaskListener() {
|
task.addTaskListener(new TaskListener() {
|
||||||
@Override
|
@Override
|
||||||
public void started(TaskEvent e) {}
|
public void started(TaskEvent e) {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void finished(TaskEvent e) {
|
public void finished(TaskEvent e) {
|
||||||
@@ -57,7 +130,7 @@ public class FinalGuardPlugin implements Plugin {
|
|||||||
private void analyzeFinalVariables(CompilationUnitTree compilationUnit, JavacTask task, Element typeElement) {
|
private void analyzeFinalVariables(CompilationUnitTree compilationUnit, JavacTask task, Element typeElement) {
|
||||||
FinalVariableAnalyzer analyzer = new FinalVariableAnalyzer(compilationUnit, task);
|
FinalVariableAnalyzer analyzer = new FinalVariableAnalyzer(compilationUnit, task);
|
||||||
TreePath path = Trees.instance(task).getPath(typeElement);
|
TreePath path = Trees.instance(task).getPath(typeElement);
|
||||||
if(path != null) {
|
if (path != null) {
|
||||||
analyzer.scan(path, null);
|
analyzer.scan(path, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,7 +138,7 @@ public class FinalGuardPlugin implements Plugin {
|
|||||||
private static class FinalVariableAnalyzer extends TreePathScanner<Void, Void> {
|
private static class FinalVariableAnalyzer extends TreePathScanner<Void, Void> {
|
||||||
private final CompilationUnitTree compilationUnit;
|
private final CompilationUnitTree compilationUnit;
|
||||||
private final Trees trees;
|
private final Trees trees;
|
||||||
private final Map<String, VariableInfo> variableInfoMap = new HashMap<>();
|
private final Map<String, VariableInfo> variableInfoMap = new LinkedHashMap<>();
|
||||||
private final Set<String> reassignedVariables = new HashSet<>();
|
private final Set<String> reassignedVariables = new HashSet<>();
|
||||||
private String currentMethod;
|
private String currentMethod;
|
||||||
|
|
||||||
@@ -76,15 +149,15 @@ public class FinalGuardPlugin implements Plugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Void visitMethod(MethodTree node, Void p) {
|
public Void visitMethod(MethodTree node, Void p) {
|
||||||
String previousMethod = currentMethod;
|
final String previousMethod = currentMethod;
|
||||||
currentMethod = node.getName().toString();
|
currentMethod = node.getName().toString();
|
||||||
variableInfoMap.clear();
|
variableInfoMap.clear();
|
||||||
reassignedVariables.clear();
|
reassignedVariables.clear();
|
||||||
|
|
||||||
// Analyze parameters first
|
// Analyze parameters first
|
||||||
for (VariableTree param : node.getParameters()) {
|
for (VariableTree param : node.getParameters()) {
|
||||||
String varName = param.getName().toString();
|
final String varName = param.getName().toString();
|
||||||
variableInfoMap.put(varName, new VariableInfo(param, false));
|
variableInfoMap.put(varName, new VariableInfo(param, VariableType.METHOD_PARAM));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then analyze method body
|
// Then analyze method body
|
||||||
@@ -100,9 +173,25 @@ public class FinalGuardPlugin implements Plugin {
|
|||||||
@Override
|
@Override
|
||||||
public Void visitVariable(VariableTree node, Void p) {
|
public Void visitVariable(VariableTree node, Void p) {
|
||||||
if (currentMethod != null) {
|
if (currentMethod != null) {
|
||||||
String varName = node.getName().toString();
|
final String varName = node.getName().toString();
|
||||||
boolean isParameter = node.getKind() == Tree.Kind.METHOD;
|
final Tree parent = getCurrentPath().getParentPath().getLeaf();
|
||||||
variableInfoMap.put(varName, new VariableInfo(node, isParameter));
|
final VariableType type;
|
||||||
|
if (parent instanceof LambdaExpressionTree) {
|
||||||
|
type = VariableType.LAMBDA_PARAM;
|
||||||
|
} else if (parent instanceof ForLoopTree || parent instanceof EnhancedForLoopTree) {
|
||||||
|
type = VariableType.LOOP_PARAM;
|
||||||
|
} else if (parent instanceof CatchTree) {
|
||||||
|
type = VariableType.CATCH_PARAM;
|
||||||
|
} else if (parent instanceof TryTree) {
|
||||||
|
type = VariableType.TRY_WITH_PARAM;
|
||||||
|
} else if (parent instanceof MethodTree) {
|
||||||
|
type = VariableType.METHOD_PARAM;
|
||||||
|
} else if (parent instanceof BlockTree) {
|
||||||
|
type = VariableType.LOCAL_VAR;
|
||||||
|
} else {
|
||||||
|
type = VariableType.LOCAL_VAR;
|
||||||
|
}
|
||||||
|
variableInfoMap.put(varName, new VariableInfo(node, type));
|
||||||
}
|
}
|
||||||
return super.visitVariable(node, p);
|
return super.visitVariable(node, p);
|
||||||
}
|
}
|
||||||
@@ -110,7 +199,7 @@ public class FinalGuardPlugin implements Plugin {
|
|||||||
@Override
|
@Override
|
||||||
public Void visitAssignment(AssignmentTree node, Void p) {
|
public Void visitAssignment(AssignmentTree node, Void p) {
|
||||||
if (node.getVariable() instanceof IdentifierTree) {
|
if (node.getVariable() instanceof IdentifierTree) {
|
||||||
IdentifierTree ident = (IdentifierTree) node.getVariable();
|
final IdentifierTree ident = (IdentifierTree) node.getVariable();
|
||||||
reassignedVariables.add(ident.getName().toString());
|
reassignedVariables.add(ident.getName().toString());
|
||||||
}
|
}
|
||||||
return super.visitAssignment(node, p);
|
return super.visitAssignment(node, p);
|
||||||
@@ -124,7 +213,7 @@ public class FinalGuardPlugin implements Plugin {
|
|||||||
node.getKind() == Tree.Kind.POSTFIX_DECREMENT) &&
|
node.getKind() == Tree.Kind.POSTFIX_DECREMENT) &&
|
||||||
node.getExpression() instanceof IdentifierTree) {
|
node.getExpression() instanceof IdentifierTree) {
|
||||||
|
|
||||||
IdentifierTree ident = (IdentifierTree) node.getExpression();
|
final IdentifierTree ident = (IdentifierTree) node.getExpression();
|
||||||
reassignedVariables.add(ident.getName().toString());
|
reassignedVariables.add(ident.getName().toString());
|
||||||
}
|
}
|
||||||
return super.visitUnary(node, p);
|
return super.visitUnary(node, p);
|
||||||
@@ -133,16 +222,22 @@ public class FinalGuardPlugin implements Plugin {
|
|||||||
@Override
|
@Override
|
||||||
public Void visitCompoundAssignment(CompoundAssignmentTree node, Void p) {
|
public Void visitCompoundAssignment(CompoundAssignmentTree node, Void p) {
|
||||||
if (node.getVariable() instanceof IdentifierTree) {
|
if (node.getVariable() instanceof IdentifierTree) {
|
||||||
IdentifierTree ident = (IdentifierTree) node.getVariable();
|
final IdentifierTree ident = (IdentifierTree) node.getVariable();
|
||||||
reassignedVariables.add(ident.getName().toString());
|
reassignedVariables.add(ident.getName().toString());
|
||||||
}
|
}
|
||||||
return super.visitCompoundAssignment(node, p);
|
return super.visitCompoundAssignment(node, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkForFinalCandidates() {
|
private void checkForFinalCandidates() {
|
||||||
for (Map.Entry<String, VariableInfo> entry : variableInfoMap.entrySet()) {
|
for (final Map.Entry<String, VariableInfo> entry : variableInfoMap.entrySet()) {
|
||||||
String varName = entry.getKey();
|
final String varName = entry.getKey();
|
||||||
VariableInfo info = entry.getValue();
|
final VariableInfo info = entry.getValue();
|
||||||
|
|
||||||
|
Diagnostic.Kind level = configuration.levels.get(info.variableType);
|
||||||
|
// Skip if level is not configured
|
||||||
|
if (level == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Skip if already final
|
// Skip if already final
|
||||||
if (isFinal(info.variableTree)) {
|
if (isFinal(info.variableTree)) {
|
||||||
@@ -154,25 +249,16 @@ public class FinalGuardPlugin implements Plugin {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
String message = "Local variable '" + varName + "' is never reassigned, so it should be declared final";
|
trees.printMessage(level,
|
||||||
trees.printMessage(FinalGuardPlugin.diagnosticLevel,
|
info.variableType.getMessage(varName),
|
||||||
message,
|
|
||||||
info.variableTree,
|
info.variableTree,
|
||||||
compilationUnit);
|
compilationUnit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isFinal(VariableTree variableTree) {
|
private static boolean isFinal(VariableTree variableTree) {
|
||||||
Set<Modifier> modifiers = variableTree.getModifiers().getFlags();
|
final Set<Modifier> modifiers = variableTree.getModifiers().getFlags();
|
||||||
return modifiers.contains(Modifier.FINAL);
|
return modifiers.contains(Modifier.FINAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class VariableInfo {
|
|
||||||
final VariableTree variableTree;
|
|
||||||
|
|
||||||
VariableInfo(VariableTree variableTree, boolean isParameter) {
|
|
||||||
this.variableTree = variableTree;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package net.woggioni.finalguard;
|
package net.woggioni.finalguard;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.Arguments;
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
@@ -30,11 +29,17 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import java.util.stream.StreamSupport;
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
|
import static net.woggioni.finalguard.FinalGuardPlugin.VariableType.CATCH_PARAM;
|
||||||
|
import static net.woggioni.finalguard.FinalGuardPlugin.VariableType.LAMBDA_PARAM;
|
||||||
|
import static net.woggioni.finalguard.FinalGuardPlugin.VariableType.LOCAL_VAR;
|
||||||
|
import static net.woggioni.finalguard.FinalGuardPlugin.VariableType.LOOP_PARAM;
|
||||||
|
import static net.woggioni.finalguard.FinalGuardPlugin.VariableType.METHOD_PARAM;
|
||||||
|
import static net.woggioni.finalguard.FinalGuardPlugin.VariableType.TRY_WITH_PARAM;
|
||||||
|
|
||||||
public class PluginTest {
|
public class PluginTest {
|
||||||
|
|
||||||
private static class ClassFile extends SimpleJavaFileObject {
|
private static class ClassFile extends SimpleJavaFileObject {
|
||||||
@@ -67,7 +72,7 @@ public class PluginTest {
|
|||||||
char[] buffer = new char[0x1000];
|
char[] buffer = new char[0x1000];
|
||||||
while (true) {
|
while (true) {
|
||||||
int read = r.read(buffer);
|
int read = r.read(buffer);
|
||||||
if(read < 0) break;
|
if (read < 0) break;
|
||||||
sb.append(buffer, 0, read);
|
sb.append(buffer, 0, read);
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
@@ -106,7 +111,7 @@ public class PluginTest {
|
|||||||
.map(SourceFile::new).collect(Collectors.toList());
|
.map(SourceFile::new).collect(Collectors.toList());
|
||||||
List<String> arguments = Arrays.asList(
|
List<String> arguments = Arrays.asList(
|
||||||
"-classpath", System.getProperty("test.compilation.classpath"),
|
"-classpath", System.getProperty("test.compilation.classpath"),
|
||||||
"-Xplugin:" + FinalGuardPlugin.class.getSimpleName()
|
"-Xplugin:" + FinalGuardPlugin.class.getName()
|
||||||
);
|
);
|
||||||
final ArrayList<Diagnostic<? extends JavaFileObject>> compilerMessages = new ArrayList<>();
|
final ArrayList<Diagnostic<? extends JavaFileObject>> compilerMessages = new ArrayList<>();
|
||||||
System.setProperty(FinalGuardPlugin.DIAGNOSTIC_LEVEL_KEY, "ERROR");
|
System.setProperty(FinalGuardPlugin.DIAGNOSTIC_LEVEL_KEY, "ERROR");
|
||||||
@@ -118,7 +123,7 @@ public class PluginTest {
|
|||||||
null,
|
null,
|
||||||
compilationUnits
|
compilationUnits
|
||||||
);
|
);
|
||||||
if(task.call()) return Optional.empty();
|
if (task.call()) return Optional.empty();
|
||||||
else return Optional.of(compilerMessages);
|
else return Optional.of(compilerMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,22 +136,43 @@ public class PluginTest {
|
|||||||
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
|
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
|
||||||
String prefix = "net/woggioni/finalguard/test/";
|
String prefix = "net/woggioni/finalguard/test/";
|
||||||
return Stream.of(
|
return Stream.of(
|
||||||
Arguments.of(prefix + "TestCase1.java", CompilationResult.FAILURE),
|
Arguments.of(prefix + "TestCase1.java", Arrays.asList(
|
||||||
Arguments.of(prefix + "TestCase2.java", CompilationResult.SUCCESS),
|
LOOP_PARAM.getMessage("item"),
|
||||||
Arguments.of(prefix + "TestCase3.java", CompilationResult.FAILURE),
|
LAMBDA_PARAM.getMessage("s"),
|
||||||
Arguments.of(prefix + "TestCase4.java", CompilationResult.FAILURE),
|
LOCAL_VAR.getMessage("f"),
|
||||||
Arguments.of(prefix + "TestCase5.java", CompilationResult.FAILURE),
|
LOCAL_VAR.getMessage("localVar"),
|
||||||
Arguments.of(prefix + "TestCase6.java", CompilationResult.FAILURE),
|
LOCAL_VAR.getMessage("loopVar"),
|
||||||
Arguments.of(prefix + "TestCase7.java", CompilationResult.FAILURE),
|
LOOP_PARAM.getMessage("i"),
|
||||||
Arguments.of(prefix + "TestCase8.java", CompilationResult.FAILURE)
|
TRY_WITH_PARAM.getMessage("is"),
|
||||||
|
METHOD_PARAM.getMessage("param1"),
|
||||||
|
CATCH_PARAM.getMessage("ioe"),
|
||||||
|
LOCAL_VAR.getMessage("anotherVar"),
|
||||||
|
METHOD_PARAM.getMessage("param2")
|
||||||
|
)),
|
||||||
|
Arguments.of(prefix + "TestCase2.java", Collections.emptyList()),
|
||||||
|
Arguments.of(prefix + "TestCase3.java",
|
||||||
|
Arrays.asList(LOCAL_VAR.getMessage("n"))),
|
||||||
|
Arguments.of(prefix + "TestCase4.java",
|
||||||
|
Arrays.asList(LOOP_PARAM.getMessage("i"))),
|
||||||
|
Arguments.of(prefix + "TestCase5.java", Arrays.asList(LOCAL_VAR.getMessage("loopVar"))),
|
||||||
|
Arguments.of(prefix + "TestCase6.java", Arrays.asList(LOOP_PARAM.getMessage("item"))),
|
||||||
|
Arguments.of(prefix + "TestCase7.java", Arrays.asList(CATCH_PARAM.getMessage("re"))),
|
||||||
|
Arguments.of(prefix + "TestCase8.java", Arrays.asList(TRY_WITH_PARAM.getMessage("is"))),
|
||||||
|
Arguments.of(prefix + "TestCase9.java", Arrays.asList(LAMBDA_PARAM.getMessage("s"))),
|
||||||
|
Arguments.of(prefix + "TestCase10.java", Arrays.asList(METHOD_PARAM.getMessage("n"))),
|
||||||
|
Arguments.of(prefix + "TestCase11.java", Arrays.asList(
|
||||||
|
METHOD_PARAM.getMessage("n"),
|
||||||
|
LOCAL_VAR.getMessage("result"),
|
||||||
|
LOCAL_VAR.getMessage("size"),
|
||||||
|
METHOD_PARAM.getMessage("t1s")
|
||||||
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Disabled
|
@ParameterizedTest(name = "{0}")
|
||||||
@ParameterizedTest(name="{0}")
|
|
||||||
@ArgumentsSource(TestCaseProvider.class)
|
@ArgumentsSource(TestCaseProvider.class)
|
||||||
public void test(String sourceFilePath, CompilationResult expectedCompilationResult) {
|
public void test(String sourceFilePath, List<String> expectedErrorMessages) {
|
||||||
Optional<Iterable<Diagnostic<? extends JavaFileObject>>> result;
|
Optional<Iterable<Diagnostic<? extends JavaFileObject>>> result;
|
||||||
try {
|
try {
|
||||||
ClassLoader cl = getClass().getClassLoader();
|
ClassLoader cl = getClass().getClassLoader();
|
||||||
@@ -155,29 +181,27 @@ public class PluginTest {
|
|||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
result.ifPresent(diagnostics -> {
|
result.ifPresent(diagnostics -> {
|
||||||
for(Diagnostic<? extends JavaFileObject> diagnostic : diagnostics) {
|
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics) {
|
||||||
System.err.printf("%s:%s %s\n",
|
System.err.printf("%s:%s %s\n",
|
||||||
diagnostic.getSource().getName(),
|
diagnostic.getSource().getName(),
|
||||||
diagnostic.getLineNumber(),
|
diagnostic.getLineNumber(),
|
||||||
diagnostic.getMessage(Locale.getDefault()));
|
diagnostic.getMessage(Locale.getDefault()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
final Pattern errorMessage = Pattern.compile(
|
if (expectedErrorMessages.isEmpty()) {
|
||||||
"Local variable '[a-zA-Z0-9_]+' is never reassigned, so it should be declared final");
|
Assertions.assertFalse(result.isPresent(), "Compilation was expected to succeed but it has failed");
|
||||||
switch (expectedCompilationResult) {
|
} else {
|
||||||
case SUCCESS:
|
final List<String> compilationErrors = result
|
||||||
Assertions.assertFalse(result.isPresent(), "Compilation was expected to succeed but it has failed");
|
.map(it -> StreamSupport.stream(it.spliterator(), false))
|
||||||
break;
|
.orElse(Stream.empty())
|
||||||
case FAILURE:
|
.map(it -> it.getMessage(Locale.ENGLISH))
|
||||||
Assertions.assertTrue(result.isPresent(), "Compilation was expected to fail but it succeeded");
|
.collect(Collectors.toList());
|
||||||
|
for (String expectedErrorMessage : expectedErrorMessages) {
|
||||||
Optional<Diagnostic<? extends JavaFileObject>> illegalSuspendableInvocationMessage = StreamSupport.stream(
|
int index = compilationErrors.indexOf(expectedErrorMessage);
|
||||||
result.get().spliterator(), false)
|
Assertions.assertTrue(index >= 0, String.format("Expected compilation error `%s` not found in output", expectedErrorMessage));
|
||||||
.filter( diagnostic ->
|
compilationErrors.remove(index);
|
||||||
errorMessage.matcher(diagnostic.getMessage(Locale.ENGLISH)).matches()
|
}
|
||||||
).findFirst();
|
Assertions.assertTrue(compilationErrors.isEmpty(), "Unexpected compilation errors found in the output");
|
||||||
Assertions.assertTrue(illegalSuspendableInvocationMessage.isPresent());
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
public class TestCase1 {
|
public class TestCase1 {
|
||||||
|
|
||||||
@@ -9,7 +15,7 @@ public class TestCase1 {
|
|||||||
|
|
||||||
param2 = "modified"; // No warning for param2 (reassigned)
|
param2 = "modified"; // No warning for param2 (reassigned)
|
||||||
|
|
||||||
for (int i = 0; i < 10; i++) { // Warning: i could be final
|
for (int i = 0; i < 10; ) { // Warning: i could be final
|
||||||
String loopVar = "constant"; // Warning: loopVar could be final
|
String loopVar = "constant"; // Warning: loopVar could be final
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,6 +23,14 @@ public class TestCase1 {
|
|||||||
for (String item : Arrays.asList("a", "b")) {
|
for (String item : Arrays.asList("a", "b")) {
|
||||||
// item is effectively final in each iteration
|
// item is effectively final in each iteration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try (InputStream is = Files.newInputStream(Path.of("/tmp/file.txt"))) {
|
||||||
|
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
throw new UncheckedIOException(ioe);
|
||||||
|
}
|
||||||
|
|
||||||
|
Function<String, String> f = s -> s.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void finalMethod(final String param1, String param2) { // Warning only for param2
|
public void finalMethod(final String param1, String param2) { // Warning only for param2
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
public class TestCase10 {
|
||||||
|
public void testMethod(int n) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public abstract class TestCase11<T1> {
|
||||||
|
|
||||||
|
abstract T1 get(int n);
|
||||||
|
|
||||||
|
public T1[] toArray(T1[] t1s) {
|
||||||
|
int size = 42;
|
||||||
|
T1[] result = Arrays.copyOf(t1s, size);
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
result[i] = get(i);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
public class TestCase4 {
|
public class TestCase4 {
|
||||||
|
|
||||||
public void testMethod() {
|
public void testMethod() {
|
||||||
for (int i = 0; i < 10;) { // Error: i could be final
|
for (int i = 0; i < 10; ) { // Error: i could be final
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ import java.nio.file.Path;
|
|||||||
public class TestCase8 {
|
public class TestCase8 {
|
||||||
|
|
||||||
public void testMethod() throws IOException {
|
public void testMethod() throws IOException {
|
||||||
try(InputStream is = Files.newInputStream(Path.of("some-path"))) { // Error: is could be final
|
try (InputStream is = Files.newInputStream(Path.of("some-path"))) { // Error: is could be final
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class TestCase9 {
|
||||||
|
|
||||||
|
public void testMethod() {
|
||||||
|
final Function<String, String> d = s -> s.toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,5 +6,17 @@ import javax.tools.Diagnostic;
|
|||||||
|
|
||||||
|
|
||||||
public interface FinalGuardExtension {
|
public interface FinalGuardExtension {
|
||||||
Property<Diagnostic.Kind> getDiagnosticKind();
|
Property<Diagnostic.Kind> getDefaultLevel();
|
||||||
|
|
||||||
|
Property<Diagnostic.Kind> getLocalVariableLevel();
|
||||||
|
|
||||||
|
Property<Diagnostic.Kind> getLambdaParameterLevel();
|
||||||
|
|
||||||
|
Property<Diagnostic.Kind> getForLoopParameterLevel();
|
||||||
|
|
||||||
|
Property<Diagnostic.Kind> getTryWithResourceLevel();
|
||||||
|
|
||||||
|
Property<Diagnostic.Kind> getMethodParameterLevel();
|
||||||
|
|
||||||
|
Property<Diagnostic.Kind> getCatchParameterLevel();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import org.gradle.api.artifacts.Configuration;
|
|||||||
import org.gradle.api.artifacts.DependencySet;
|
import org.gradle.api.artifacts.DependencySet;
|
||||||
import org.gradle.api.model.ObjectFactory;
|
import org.gradle.api.model.ObjectFactory;
|
||||||
import org.gradle.api.plugins.ExtensionContainer;
|
import org.gradle.api.plugins.ExtensionContainer;
|
||||||
|
import org.gradle.api.provider.Property;
|
||||||
import org.gradle.api.tasks.TaskContainer;
|
import org.gradle.api.tasks.TaskContainer;
|
||||||
import org.gradle.api.tasks.compile.CompileOptions;
|
import org.gradle.api.tasks.compile.CompileOptions;
|
||||||
import org.gradle.api.tasks.compile.JavaCompile;
|
import org.gradle.api.tasks.compile.JavaCompile;
|
||||||
@@ -50,10 +51,57 @@ public class FinalGuardPlugin implements Plugin<Project> {
|
|||||||
extensionContainer.add("finalGuard", finalGuardExtension);
|
extensionContainer.add("finalGuard", finalGuardExtension);
|
||||||
tasks.withType(JavaCompile.class, javaCompileTask -> {
|
tasks.withType(JavaCompile.class, javaCompileTask -> {
|
||||||
javaCompileTask.doFirst(t -> {
|
javaCompileTask.doFirst(t -> {
|
||||||
final Diagnostic.Kind diagnosticKind = finalGuardExtension.getDiagnosticKind().getOrElse(Diagnostic.Kind.WARNING);
|
|
||||||
final CompileOptions options = javaCompileTask.getOptions();
|
final CompileOptions options = javaCompileTask.getOptions();
|
||||||
options.setAnnotationProcessorPath(options.getAnnotationProcessorPath().plus(javacPluginConfiguration));
|
options.setAnnotationProcessorPath(options.getAnnotationProcessorPath().plus(javacPluginConfiguration));
|
||||||
options.getForkOptions().getJvmArgs().add("-Dnet.woggioni.finalguard.diagnostic.level=" + diagnosticKind);
|
{
|
||||||
|
final Property<Diagnostic.Kind> defaultLevel = finalGuardExtension.getDefaultLevel();
|
||||||
|
if (defaultLevel.isPresent()) {
|
||||||
|
final Diagnostic.Kind diagnosticKind = defaultLevel.get();
|
||||||
|
options.getForkOptions().getJvmArgs().add("-Dnet.woggioni.finalguard.diagnostic.level=" + diagnosticKind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
final Property<Diagnostic.Kind> localVariableLevel = finalGuardExtension.getLocalVariableLevel();
|
||||||
|
if (localVariableLevel.isPresent()) {
|
||||||
|
final Diagnostic.Kind diagnosticKind = localVariableLevel.get();
|
||||||
|
options.getForkOptions().getJvmArgs().add("-Dnet.woggioni.finalguard.diagnostic.local.variable.level=" + diagnosticKind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
final Property<Diagnostic.Kind> methodParameterLevel = finalGuardExtension.getMethodParameterLevel();
|
||||||
|
if (methodParameterLevel.isPresent()) {
|
||||||
|
final Diagnostic.Kind diagnosticKind = methodParameterLevel.get();
|
||||||
|
options.getForkOptions().getJvmArgs().add("-Dnet.woggioni.finalguard.diagnostic.method.param.level=" + diagnosticKind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
final Property<Diagnostic.Kind> forLoopParameterLevel = finalGuardExtension.getForLoopParameterLevel();
|
||||||
|
if (forLoopParameterLevel.isPresent()) {
|
||||||
|
final Diagnostic.Kind diagnosticKind = forLoopParameterLevel.get();
|
||||||
|
options.getForkOptions().getJvmArgs().add("-Dnet.woggioni.finalguard.diagnostic.for.param.level=" + diagnosticKind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
final Property<Diagnostic.Kind> tryParameterLevel = finalGuardExtension.getTryWithResourceLevel();
|
||||||
|
if (tryParameterLevel.isPresent()) {
|
||||||
|
final Diagnostic.Kind diagnosticKind = tryParameterLevel.get();
|
||||||
|
options.getForkOptions().getJvmArgs().add("-Dnet.woggioni.finalguard.diagnostic.try.param.level=" + diagnosticKind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
final Property<Diagnostic.Kind> catchParameterLevel = finalGuardExtension.getCatchParameterLevel();
|
||||||
|
if (catchParameterLevel.isPresent()) {
|
||||||
|
final Diagnostic.Kind diagnosticKind = catchParameterLevel.get();
|
||||||
|
options.getForkOptions().getJvmArgs().add("-Dnet.woggioni.finalguard.diagnostic.catch.param.level=" + diagnosticKind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
final Property<Diagnostic.Kind> lambdaParameterLevel = finalGuardExtension.getLambdaParameterLevel();
|
||||||
|
if (lambdaParameterLevel.isPresent()) {
|
||||||
|
final Diagnostic.Kind diagnosticKind = lambdaParameterLevel.get();
|
||||||
|
options.getForkOptions().getJvmArgs().add("-Dnet.woggioni.finalguard.diagnostic.lambda.param.level=" + diagnosticKind);
|
||||||
|
}
|
||||||
|
}
|
||||||
options.getCompilerArgs().add("-Xplugin:net.woggioni.finalguard.FinalGuardPlugin");
|
options.getCompilerArgs().add("-Xplugin:net.woggioni.finalguard.FinalGuardPlugin");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
lys.catalog.version=2025.09.30
|
lys.catalog.version=2025.09.30
|
||||||
version.myGradlePlugins=2025.11.17
|
version.myGradlePlugins=2025.11.18
|
||||||
version.gradle=8.12
|
version.gradle=8.12
|
||||||
|
|
||||||
gitea.maven.url = https://gitea.woggioni.net/api/packages/woggioni/maven
|
gitea.maven.url = https://gitea.woggioni.net/api/packages/woggioni/maven
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import org.gradle.api.Project;
|
|||||||
import org.gradle.api.Task;
|
import org.gradle.api.Task;
|
||||||
import org.gradle.api.artifacts.Configuration;
|
import org.gradle.api.artifacts.Configuration;
|
||||||
import org.gradle.api.artifacts.dsl.DependencyHandler;
|
import org.gradle.api.artifacts.dsl.DependencyHandler;
|
||||||
import org.gradle.api.model.ObjectFactory;
|
|
||||||
import org.gradle.api.plugins.ExtraPropertiesExtension;
|
import org.gradle.api.plugins.ExtraPropertiesExtension;
|
||||||
import org.gradle.api.plugins.JavaPlugin;
|
import org.gradle.api.plugins.JavaPlugin;
|
||||||
import org.gradle.api.plugins.JavaPluginExtension;
|
import org.gradle.api.plugins.JavaPluginExtension;
|
||||||
|
|||||||
Reference in New Issue
Block a user