downgrade to gradle 8.14.5
CI / build (push) Successful in 2m35s

This commit is contained in:
2026-06-05 17:28:36 +08:00
parent 65525fdd09
commit 6dc946d71b
26 changed files with 227 additions and 27 deletions
@@ -4,12 +4,6 @@ plugins {
group = "net.woggioni.finalguard"
java {
toolchain {
languageVersion = JavaLanguageVersion.of(25)
}
}
java {
sourceCompatibility(JavaVersion.VERSION_1_8.toString())
targetCompatibility(JavaVersion.VERSION_1_8.toString())
@@ -21,12 +21,12 @@ 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.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeInfo;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -44,8 +44,6 @@ import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import static com.sun.tools.javac.code.Flags.RECORD;
public class FinalGuardPlugin implements Plugin {
public static final String DEFAULT_LEVEL_KEY = "default.level";
public static final String EXCLUDE_KEY = "exclude";
@@ -185,6 +183,7 @@ public class FinalGuardPlugin implements Plugin {
private final Configuration configuration;
private final CompilationUnitTree compilationUnit;
private final Trees trees;
private final Elements elements;
private final Map<String, VariableInfo> variableInfoMap = new LinkedHashMap<>();
private final Set<String> reassignedVariables = new HashSet<>();
@@ -192,6 +191,7 @@ public class FinalGuardPlugin implements Plugin {
this.configuration = configuration;
this.compilationUnit = compilationUnit;
this.trees = Trees.instance(task);
this.elements = task.getElements();
}
@Override
@@ -203,6 +203,8 @@ public class FinalGuardPlugin implements Plugin {
return null; // skip implicit constructor of anonymous class
}
}
variableInfoMap.clear();
reassignedVariables.clear();
super.visitMethod(node, p);
// Check for variables that could be final
checkForFinalCandidates();
@@ -232,9 +234,13 @@ public class FinalGuardPlugin implements Plugin {
type = VariableType.ABSTRACT_METHOD_PARAM;
} else {
type = VariableType.METHOD_PARAM;
if (isJava17OrHigher && ((MethodTree) parent).getName().contentEquals("<init>")) {
if(TreeInfo.isCanonicalConstructor((JCTree) parent)) {
return super.visitVariable(node, p);
final MethodTree methodTree = (MethodTree) parent;
if (isJava17OrHigher && (methodTree.getName().contentEquals("<init>"))) {
final TreePath grandParentPath = parentPath.getParentPath();
if(grandParentPath.getLeaf().getKind() == Tree.Kind.RECORD) {
if(RecordUtils.isCanonicalConstructor(trees, elements, (TypeElement) trees.getElement(grandParentPath), parentPath)) {
return super.visitVariable(node, p);
}
}
}
}
@@ -317,5 +323,7 @@ public class FinalGuardPlugin implements Plugin {
final Set<Modifier> modifiers = variableTree.getModifiers().getFlags();
return modifiers.contains(Modifier.FINAL);
}
}
}
@@ -0,0 +1,95 @@
package net.woggioni.finalguard;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import java.util.List;
public class RecordUtils {
private static class RecordConstructorDetector {
private final Trees trees;
public RecordConstructorDetector(Trees trees) {
this.trees = trees;
}
public enum ConstructorKind {
CANONICAL_FULL, // Record(int x, String y) { ... }
CANONICAL_COMPACT, // Record { ... }
SECONDARY // Record(String s) { this(0, s); }
}
public ConstructorKind detect(TreePath parent, MethodTree method, TypeElement recordType) {
// 1. Verify it's a constructor
if (method.getReturnType() != null || !method.getName().contentEquals("<init>")) {
throw new IllegalArgumentException("Not a constructor: " + method.getName());
}
List<? extends VariableTree> params = method.getParameters();
List<? extends javax.lang.model.element.RecordComponentElement> components =
recordType.getRecordComponents();
// 2. Compact constructor: no explicit parameters
if (params.isEmpty()) {
return ConstructorKind.CANONICAL_COMPACT;
}
// 3. Check if parameters match record components exactly
if (params.size() == components.size()) {
boolean allMatch = true;
for (int i = 0; i < components.size(); i++) {
TypeMirror paramType = trees.getTypeMirror(new TreePath(parent, params.get(i)));
TypeMirror componentType = components.get(i).asType();
if (paramType == null || !paramType.toString().equals(componentType.toString())) {
allMatch = false;
break;
}
}
if (allMatch) {
return ConstructorKind.CANONICAL_FULL;
}
}
// 4. Otherwise it's a secondary constructor
return ConstructorKind.SECONDARY;
}
}
public static boolean isCanonicalConstructor(Trees trees, Elements elements, TypeElement recordType, TreePath method) {
final RecordConstructorDetector.ConstructorKind ctorKind = new RecordConstructorDetector(trees).detect(method, (MethodTree) method.getLeaf(), recordType);
return ctorKind == RecordConstructorDetector.ConstructorKind.CANONICAL_COMPACT || ctorKind == RecordConstructorDetector.ConstructorKind.CANONICAL_FULL;
}
public static boolean isCompactConstructor(Trees trees, Elements elements, TypeElement recordType, TreePath method) {
final RecordConstructorDetector.ConstructorKind ctorKind = new RecordConstructorDetector(trees).detect(method, (MethodTree) method.getLeaf(), recordType);
return ctorKind == RecordConstructorDetector.ConstructorKind.CANONICAL_COMPACT;
}
public static boolean isCanonicalConstructor(Trees trees, Elements elements, TreePath method) {
Element element = trees.getElement(method);
if(element instanceof ExecutableElement) {
return elements.isCanonicalConstructor((ExecutableElement) element);
} else {
return false;
}
}
public static boolean isCompactConstructor(Trees trees, Elements elements, TreePath method) {
Element element = trees.getElement(method);
if(element instanceof ExecutableElement) {
return elements.isCompactConstructor((ExecutableElement) element);
} else {
return false;
}
}
}
@@ -171,6 +171,12 @@ public class PluginTest {
Arrays.asList(
METHOD_PARAM.getMessage("a")
)
),
Arguments.of(prefix + "TestCase18.java",
Collections.emptyList()
),
Arguments.of(prefix + "TestCase19.java",
Collections.emptyList()
)
);
}
@@ -1,7 +1,3 @@
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
public record TestCase17(String s) {
TestCase17(double a) {
this("");
@@ -0,0 +1,7 @@
import java.util.Set;
public class TestCase18 {
private String name;
private String password;
private Set<Integer> roles;
}
@@ -0,0 +1,49 @@
public class TestCase19 {
private long id;
@java.lang.SuppressWarnings("all")
public TestCase19() {
}
@java.lang.SuppressWarnings("all")
public long getId() {
return this.id;
}
@java.lang.SuppressWarnings("all")
public void setId(final long id) {
this.id = id;
}
@java.lang.Override
@java.lang.SuppressWarnings("all")
public boolean equals(final java.lang.Object o) {
if (o == this) return true;
if (!(o instanceof TestCase19)) return false;
final TestCase19 other = (TestCase19) o;
if (!other.canEqual((java.lang.Object) this)) return false;
if (this.getId() != other.getId()) return false;
return true;
}
@java.lang.SuppressWarnings("all")
protected boolean canEqual(final java.lang.Object other) {
return other instanceof TestCase19;
}
@java.lang.Override
@java.lang.SuppressWarnings("all")
public int hashCode() {
final int PRIME = 59;
int result = 1;
final long $id = this.getId();
result = result * PRIME + (int) ($id >>> 32 ^ $id);
return result;
}
@java.lang.Override
@java.lang.SuppressWarnings("all")
public java.lang.String toString() {
return "TestCase19(id=" + this.getId() + ")";
}
}