Repository: FabricMC/fabric
Pull request #4618: Ensure all mixin methods and fields are marked Unique as required.
Author: login: modmuss50
Labels: name: cleanup
Created: 2025-05-06T21:43:07Z | Merged: 2025-05-11T09:34:30Z

Added checkstyle rules for Unique usage, and also prevent using local capture for Injects.


Diff:
diff --git a/checkstyle.xml b/checkstyle.xml
index e265b13f31..2ac51a7bcc 100644
--- a/checkstyle.xml
+++ b/checkstyle.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
-<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.2//EN" "http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
+<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN" "http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
 <module name="Checker">
 	<property name="charset" value="UTF-8"/>
 	<property name="fileExtensions" value="java"/>
@@ -207,11 +207,77 @@
         <!-- Prevent var for all cases other than new instance creation -->
         <module name="MatchXpath">
             <property name="query" value="//VARIABLE_DEF[./TYPE/IDENT[@text='var'] and not(./ASSIGN/EXPR/LITERAL_NEW)]"/>
+            <message key="matchxpath.match" value="var is only allowed before new instance creation"/>
         </module>
 
         <!-- Prevent public records in API packages -->
         <module name="MatchXpath">
             <property name="query" value="//RECORD_DEF[./MODIFIERS/LITERAL_PUBLIC][//PACKAGE_DEF//DOT/IDENT[@text='api']]"/>
+            <message key="matchxpath.match" value="public records are not allowed in API packages"/>
+        </module>
+
+        <!-- Prevent non-@Unique fields in Mixin classes -->
+        <module name="MatchXpath">
+            <property name="query" value="
+                //OBJBLOCK/VARIABLE_DEF[
+                    not(
+                        ./MODIFIERS/ANNOTATION/IDENT[
+                            @text='Unique'
+                            or @text='Shadow'
+                        ]
+                    )
+                    and
+                    ../../../CLASS_DEF[
+                        ./MODIFIERS/ANNOTATION/IDENT[@text='Mixin']
+                    ]
+                ]
+            "/>
+            <message key="matchxpath.match" value="Mixin fields must be annotated with @Unique or @Shadow"/>
+        </module>
+
+        <!-- Prevent non-@Unique methods in Mixin classes -->
+        <!-- All Mixin and MixinExtras annotations must be listed here -->
+        <module name="MatchXpath">
+            <property name="query" value="
+                //METHOD_DEF[
+                    not(
+                        ./MODIFIERS/ANNOTATION/IDENT[
+                            @text='Unique'
+                            or @text='Inject'
+                            or @text='ModifyArg'
+                            or @text='ModifyExpressionValue'
+                            or @text='ModifyReturnValue'
+                            or @text='ModifyVariable'
+                            or @text='Override'
+                            or @text='Overwrite'
+                            or @text='Redirect'
+                            or @text='Shadow'
+                            or @text='WrapMethod'
+                            or @text='WrapOperation'
+                            or @text='WrapWithCondition'
+                        ]
+                    )
+                    and
+                    ../../../CLASS_DEF[
+                        ./MODIFIERS/ANNOTATION/IDENT[@text='Mixin']
+                    ]
+                ]
+            "/>
+            <message key="matchxpath.match" value="Mixin methods must be annotated with @Unique or one of the Mixin annotations"/>
+        </module>
+
+        <!-- Prevent @Inject local capture, @Local is preferred -->
+        <module name="MatchXpath">
+            <property name="query" value="
+                //METHOD_DEF[
+                    ./MODIFIERS/ANNOTATION[
+                        ./IDENT[@text='Inject']
+                        and
+                        ./ANNOTATION_MEMBER_VALUE_PAIR/IDENT[@text='locals']
+                    ]
+                ]
+            "/>
+            <message key="matchxpath.match" value="Inject local capture is not allowed, use @Local instead"/>
         </module>
 	</module>
 </module>
diff --git a/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/mixin/biome/BiomeSourceMixin.java b/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/mixin/biome/BiomeSourceMixin.java
index 117d2dca5e..87a63f1dd8 100644
--- a/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/mixin/biome/BiomeSourceMixin.java
+++ b/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/mixin/biome/BiomeSourceMixin.java
@@ -20,6 +20,7 @@
 import java.util.function.Supplier;
 
 import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Unique;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Redirect;
 
@@ -31,10 +32,11 @@
 public class BiomeSourceMixin {
 	@Redirect(method = "getBiomes", at = @At(value = "INVOKE", target = "Ljava/util/function/Supplier;get()Ljava/lang/Object;"))
 	private Object getBiomes(Supplier<Set<RegistryEntry<Biome>>> instance) {
-		return fabric_modifyBiomeSet(instance.get());
+		return modifyBiomeSet(instance.get());
 	}
 
-	protected Set<RegistryEntry<Biome>> fabric_modifyBiomeSet(Set<RegistryEntry<Biome>> biomes) {
+	@Unique
+	protected Set<RegistryEntry<Biome>> modifyBiomeSet(Set<RegistryEntry<Biome>> biomes) {
 		return biomes;
 	}
 }
diff --git a/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/mixin/biome/TheEndBiomeSourceMixin.java b/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/mixin/biome/TheEndBiomeSourceMixin.java
index 35270e000f..dcf0acf9ad 100644
--- a/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/mixin/biome/TheEndBiomeSourceMixin.java
+++ b/fabric-biome-api-v1/src/main/java/net/fabricmc/fabric/mixin/biome/TheEndBiomeSourceMixin.java
@@ -110,7 +110,7 @@ private void getWeightedEndBiome(int biomeX, int biomeY, int biomeZ, MultiNoiseU
 	}
 
 	@Override
-	protected Set<RegistryEntry<Biome>> fabric_modifyBiomeSet(Set<RegistryEntry<Biome>> biomes) {
+	protected Set<RegistryEntry<Biome>> modifyBiomeSet(Set<RegistryEntry<Biome>> biomes) {
 		if (!hasCheckedForModifiedSet) {
 			hasCheckedForModifiedSet = true;
 			biomeSetModified = !overrides.get().customBiomes.isEmpty();
diff --git a/fabric-block-view-api-v2/src/client/java/net/fabricmc/fabric/mixin/blockview/client/ChunkRendererRegionBuilderMixin.java b/fabric-block-view-api-v2/src/client/java/net/fabricmc/fabric/mixin/blockview/client/ChunkRendererRegionBuilderMixin.java
index a0949f513b..126fdb3b07 100644
--- a/fabric-block-view-api-v2/src/client/java/net/fabricmc/fabric/mixin/blockview/client/ChunkRendererRegionBuilderMixin.java
+++ b/fabric-block-view-api-v2/src/client/java/net/fabricmc/fabric/mixin/blockview/client/ChunkRendererRegionBuilderMixin.java
@@ -44,7 +44,9 @@
 
 @Mixin(ChunkRendererRegionBuilder.class)
 public abstract class ChunkRendererRegionBuilderMixin {
+	@Unique
 	private static final AtomicInteger ERROR_COUNTER = new AtomicInteger();
+	@Unique
 	private static final Logger LOGGER = LoggerFactory.getLogger(ChunkRendererRegionBuilderMixin.class);
 
 	@Inject(method = "build", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/chunk/ChunkRendererRegionBuilder$ClientChunk;getRenderedChunk()Lnet/minecraft/client/render/chunk/RenderedChunk;"))
diff --git a/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/mixin/content/registry/FireBlockMixin.java b/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/mixin/content/registry/FireBlockMixin.java
index 82e5ff4bec..baa758b1c2 100644
--- a/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/mixin/content/registry/FireBlockMixin.java
+++ b/fabric-content-registries-v0/src/main/java/net/fabricmc/fabric/mixin/content/registry/FireBlockMixin.java
@@ -18,6 +18,7 @@
 
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.Unique;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@@ -34,7 +35,8 @@
 
 @Mixin(FireBlock.class)
 public class FireBlockMixin implements FireBlockHooks {
-	private FlammableBlockRegistryImpl fabric_registry;
+	@Unique
+	private FlammableBlockRegistryImpl registry;
 
 	@Shadow
 	private int getSpreadChance(BlockState block_1) {
@@ -48,12 +50,12 @@ private int getBurnChance(BlockState block_1) {
 
 	@Inject(at = @At("RETURN"), method = "<init>")
 	private void afterConstruct(Block.Settings settings, CallbackInfo info) {
-		fabric_registry = FlammableBlockRegistryImpl.getInstance((Block) (Object) this);
+		registry = FlammableBlockRegistryImpl.getInstance((Block) (Object) this);
 	}
 
 	@Inject(at = @At("HEAD"), method = "getBurnChance", cancellable = true)
 	private void getFabricBurnChance(BlockState block, CallbackInfoReturnable info) {
-		FlammableBlockRegistry.Entry entry = fabric_registry.getFabric(block.getBlock());
+		FlammableBlockRegistry.Entry entry = registry.getFabric(block.getBlock());
 
 		if (entry != null) {
 			// TODO: use a (BlockState -> int) with this as the default impl
@@ -67,7 +69,7 @@ private void getFabricBurnChance(BlockState block, CallbackInfoReturnable info)
 
 	@Inject(at = @At("HEAD"), method = "getSpreadChance", cancellable = true)
 	private void getFabricSpreadChance(BlockState block, CallbackInfoReturnable info) {
-		FlammableBlockRegistry.Entry entry = fabric_registry.getFabric(block.getBlock());
+		FlammableBlockRegistry.Entry entry = registry.getFabric(block.getBlock());
 
 		if (entry != null) {
 			// TODO: use a (BlockState -> int) with this as the default impl
diff --git a/fabric-crash-report-info-v1/src/main/java/net/fabricmc/fabric/mixin/crash/report/info/SystemDetailsMixin.java b/fabric-crash-report-info-v1/src/main/java/net/fabricmc/fabric/mixin/crash/report/info/SystemDetailsMixin.java
index c85752b7e0..a01c278128 100644
--- a/fabric-crash-report-info-v1/src/main/java/net/fabricmc/fabric/mixin/crash/report/info/SystemDetailsMixin.java
+++ b/fabric-crash-report-info-v1/src/main/java/net/fabricmc/fabric/mixin/crash/report/info/SystemDetailsMixin.java
@@ -22,6 +22,7 @@
 
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.Unique;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@@ -55,6 +56,7 @@ private void fillSystemDetails(CallbackInfo info) {
 		});
 	}
 
+	@Unique
 	private static void appendMods(StringBuilder modString, int depth, ArrayList<ModContainer> mods) {
 		mods.sort(Comparator.comparing(mod -> mod.getMetadata().getId()));
 
diff --git a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/AttachmentTargetsMixin.java b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/AttachmentTargetsMixin.java
index 84e693de95..0573f638a2 100644
--- a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/AttachmentTargetsMixin.java
+++ b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/AttachmentTargetsMixin.java
@@ -47,18 +47,21 @@
 
 @Mixin({BlockEntity.class, Entity.class, World.class, Chunk.class})
 abstract class AttachmentTargetsMixin implements AttachmentTargetImpl {
+	@Unique
 	@Nullable
-	private IdentityHashMap<AttachmentType<?>, Object> fabric_dataAttachments = null;
+	private IdentityHashMap<AttachmentType<?>, Object> dataAttachments = null;
+	@Unique
 	@Nullable
-	private IdentityHashMap<AttachmentType<?>, AttachmentChange> fabric_syncedAttachments = null;
+	private IdentityHashMap<AttachmentType<?>, AttachmentChange> syncedAttachments = null;
+	@Unique
 	@Nullable
-	private IdentityHashMap<AttachmentType<?>, Event<OnAttachedSet<?>>> fabric_attachedChangedListeners = null;
+	private IdentityHashMap<AttachmentType<?>, Event<OnAttachedSet<?>>> attachedChangedListeners = null;
 
 	@SuppressWarnings("unchecked")
 	@Override
 	@Nullable
 	public <T> T getAttached(AttachmentType<T> type) {
-		return fabric_dataAttachments == null ? null : (T) fabric_dataAttachments.get(type);
+		return dataAttachments == null ? null : (T) dataAttachments.get(type);
 	}
 
 	@SuppressWarnings("unchecked")
@@ -76,17 +79,17 @@ public <T> T setAttached(AttachmentType<T> type, @Nullable T value) {
 		T oldValue;
 
 		if (value == null) {
-			oldValue = fabric_dataAttachments == null ? null : (T) fabric_dataAttachments.remove(type);
+			oldValue = dataAttachments == null ? null : (T) dataAttachments.remove(type);
 		} else {
-			if (fabric_dataAttachments == null) {
-				fabric_dataAttachments = new IdentityHashMap<>();
+			if (dataAttachments == null) {
+				dataAttachments = new IdentityHashMap<>();
 			}
 
-			oldValue = (T) fabric_dataAttachments.put(type, value);
+			oldValue = (T) dataAttachments.put(type, value);
 		}
 
-		if (fabric_attachedChangedListeners != null) {
-			Event<OnAttachedSet<T>> event = (Event<OnAttachedSet<T>>) (Event<?>) fabric_attachedChangedListeners.get(type);
+		if (attachedChangedListeners != null) {
+			Event<OnAttachedSet<T>> event = (Event<OnAttachedSet<T>>) (Event<?>) attachedChangedListeners.get(type);
 
 			if (event != null) {
 				event.invoker().onAttachedSet(oldValue, value);
@@ -98,16 +101,16 @@ public <T> T setAttached(AttachmentType<T> type, @Nullable T value) {
 
 	@Override
 	public boolean hasAttached(AttachmentType<?> type) {
-		return fabric_dataAttachments != null && fabric_dataAttachments.containsKey(type);
+		return dataAttachments != null && dataAttachments.containsKey(type);
 	}
 
 	@Override
 	public <A> Event<OnAttachedSet<A>> onAttachedSet(AttachmentType<A> type) {
-		if (fabric_attachedChangedListeners == null) {
-			fabric_attachedChangedListeners = new IdentityHashMap<>();
+		if (attachedChangedListeners == null) {
+			attachedChangedListeners = new IdentityHashMap<>();
 		}
 
-		return (Event<OnAttachedSet<A>>) (Event<?>) fabric_attachedChangedListeners.computeIfAbsent(type, t -> {
+		return (Event<OnAttachedSet<A>>) (Event<?>) attachedChangedListeners.computeIfAbsent(type, t -> {
 			return (Event<OnAttachedSet<?>>) (Event<?>) EventFactory.createArrayBacked(OnAttachedSet.class, (Function<OnAttachedSet<A>[], OnAttachedSet<A>>) listeners -> (oldValue, newValue) -> {
 				for (OnAttachedSet<A> listener : listeners) {
 					listener.onAttachedSet(oldValue, newValue);
@@ -118,17 +121,17 @@ public <A> Event<OnAttachedSet<A>> onAttachedSet(AttachmentType<A> type) {
 
 	@Override
 	public void fabric_writeAttachmentsToNbt(WriteView view) {
-		AttachmentSerializingImpl.serializeAttachmentData(view, fabric_dataAttachments);
+		AttachmentSerializingImpl.serializeAttachmentData(view, dataAttachments);
 	}
 
 	@Override
 	public void fabric_readAttachmentsFromNbt(ReadView view) {
 		// Note on player targets: no syncing can happen here as the networkHandler is still null
 		// Instead it is done on player join (see AttachmentSync)
-		this.fabric_dataAttachments = AttachmentSerializingImpl.deserializeAttachmentData(view);
+		this.dataAttachments = AttachmentSerializingImpl.deserializeAttachmentData(view);
 
-		if (this.fabric_shouldTryToSync() && this.fabric_dataAttachments != null) {
-			this.fabric_dataAttachments.forEach((type, value) -> {
+		if (this.fabric_shouldTryToSync() && this.dataAttachments != null) {
+			this.dataAttachments.forEach((type, value) -> {
 				if (type.isSynced()) {
 					acknowledgeSynced(type, value, view.getRegistries());
 				}
@@ -138,12 +141,12 @@ public void fabric_readAttachmentsFromNbt(ReadView view) {
 
 	@Override
 	public boolean fabric_hasPersistentAttachments() {
-		return AttachmentSerializingImpl.hasPersistentAttachments(fabric_dataAttachments);
+		return AttachmentSerializingImpl.hasPersistentAttachments(dataAttachments);
 	}
 
 	@Override
 	public Map<AttachmentType<?>, ?> fabric_getAttachments() {
-		return fabric_dataAttachments;
+		return dataAttachments;
 	}
 
 	@Unique
@@ -155,27 +158,27 @@ private void acknowledgeSynced(AttachmentType<?> type, Object value, RegistryWra
 	@Unique
 	private void acknowledgeSyncedEntry(AttachmentType<?> type, @Nullable AttachmentChange change) {
 		if (change == null) {
-			if (fabric_syncedAttachments == null) {
+			if (syncedAttachments == null) {
 				return;
 			}
 
-			fabric_syncedAttachments.remove(type);
+			syncedAttachments.remove(type);
 		} else {
-			if (fabric_syncedAttachments == null) {
-				fabric_syncedAttachments = new IdentityHashMap<>();
+			if (syncedAttachments == null) {
+				syncedAttachments = new IdentityHashMap<>();
 			}
 
-			fabric_syncedAttachments.put(type, change);
+			syncedAttachments.put(type, change);
 		}
 	}
 
 	@Override
 	public void fabric_computeInitialSyncChanges(ServerPlayerEntity player, Consumer<AttachmentChange> changeOutput) {
-		if (fabric_syncedAttachments == null) {
+		if (syncedAttachments == null) {
 			return;
 		}
 
-		for (Map.Entry<AttachmentType<?>, AttachmentChange> entry : fabric_syncedAttachments.entrySet()) {
+		for (Map.Entry<AttachmentType<?>, AttachmentChange> entry : syncedAttachments.entrySet()) {
 			if (((AttachmentTypeImpl<?>) entry.getKey()).syncPredicate().test(this, player)) {
 				changeOutput.accept(entry.getValue());
 			}
diff --git a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/ClientConnectionMixin.java b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/ClientConnectionMixin.java
index 187ca29213..5659835ce6 100644
--- a/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/ClientConnectionMixin.java
+++ b/fabric-data-attachment-api-v1/src/main/java/net/fabricmc/fabric/mixin/attachment/ClientConnectionMixin.java
@@ -20,6 +20,7 @@
 import java.util.Set;
 
 import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Unique;
 
 import net.minecraft.network.ClientConnection;
 import net.minecraft.util.Identifier;
@@ -28,15 +29,16 @@
 
 @Mixin(ClientConnection.class)
 public class ClientConnectionMixin implements SupportedAttachmentsClientConnection {
-	private Set<Identifier> fabric_supportedAttachments = new HashSet<>();
+	@Unique
+	private Set<Identifier> supportedAttachments = new HashSet<>();
 
 	@Override
 	public void fabric_setSupportedAttachments(Set<Identifier> supportedAttachments) {
-		fabric_supportedAttachments = supportedAttachments;
+		this.supportedAttachments = supportedAttachments;
 	}
 
 	@Override
 	public Set<Identifier> fabric_getSupportedAttachments() {
-		return fabric_supportedAttachments;
+		return supportedAttachments;
 	}
 }
diff --git a/fabric-events-interaction-v0/src/client/java/net/fabricmc/fabric/mixin/event/interaction/client/MinecraftClientMixin.java b/fabric-events-interaction-v0/src/client/java/net/fabricmc/fabric/mixin/event/interaction/client/MinecraftClientMixin.java
index ddc63aabc0..c83f915913 100644
--- a/fabric-events-interaction-v0/src/client/java/net/fabricmc/fabric/mixin/event/interaction/client/MinecraftClientMixin.java
+++ b/fabric-events-interaction-v0/src/client/java/net/fabricmc/fabric/mixin/event/interaction/client/MinecraftClientMixin.java
@@ -21,6 +21,7 @@
 import org.spongepowered.asm.mixin.Final;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.Unique;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@@ -44,7 +45,8 @@
 
 @Mixin(MinecraftClient.class)
 public abstract class MinecraftClientMixin {
-	private boolean fabric_attackCancelled;
+	@Unique
+	private boolean attackCancelled;
 
 	@Shadow
 	private ClientPlayerEntity player;
@@ -103,24 +105,24 @@ private void injectHandleInputEventsForPreAttackCallback(CallbackInfo ci) {
 		int attackKeyPressCount = ((KeyBindingAccessor) options.attackKey).fabric_getTimesPressed();
 
 		if (options.attackKey.isPressed() || attackKeyPressCount != 0) {
-			fabric_attackCancelled = ClientPreAttackCallback.EVENT.invoker().onClientPlayerPreAttack(
+			attackCancelled = ClientPreAttackCallback.EVENT.invoker().onClientPlayerPreAttack(
 					(MinecraftClient) (Object) this, player, attackKeyPressCount
 			);
 		} else {
-			fabric_attackCancelled = false;
+			attackCancelled = false;
 		}
 	}
 
 	@Inject(method = "doAttack", at = @At("HEAD"), cancellable = true)
 	private void injectDoAttackForCancelling(CallbackInfoReturnable<Boolean> cir) {
-		if (fabric_attackCancelled) {
+		if (attackCancelled) {
 			cir.setReturnValue(false);
 		}
 	}
 
 	@Inject(method = "handleBlockBreaking", at = @At("HEAD"), cancellable = true)
 	private void injectHandleBlockBreakingForCancelling(boolean breaking, CallbackInfo ci) {
-		if (fabric_attackCancelled) {
+		if (attackCancelled) {
 			if (interactionManager != null) {
 				interactionManager.cancelBlockBreaking();
 			}
diff --git a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/StructureTemplateManagerMixin.java b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/StructureTemplateManagerMixin.java
index 3f14ca42ee..9351bef2d8 100644
--- a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/StructureTemplateManagerMixin.java
+++ b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/StructureTemplateManagerMixin.java
@@ -27,6 +27,7 @@
 import org.apache.commons.io.IOUtils;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.Unique;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@@ -53,6 +54,7 @@ public abstract class StructureTemplateManagerMixin {
 	@Shadow
 	public abstract StructureTemplate createTemplate(NbtCompound nbt);
 
+	@Unique
 	private Optional<StructureTemplate> fabric_loadSnbtFromResource(Identifier id) {
 		Identifier path = FabricGameTestRunner.GAMETEST_STRUCTURE_FINDER.toResourcePath(id);
 		Optional<Resource> resource = this.resourceManager.getResource(path);
@@ -70,13 +72,14 @@ private Optional<StructureTemplate> fabric_loadSnbtFromResource(Identifier id) {
 		return Optional.empty();
 	}
 
-	private Stream<Identifier> fabric_streamTemplatesFromResource() {
+	@Unique
+	private Stream<Identifier> streamTemplatesFromResource() {
 		ResourceFinder finder = FabricGameTestRunner.GAMETEST_STRUCTURE_FINDER;
 		return finder.findResources(this.resourceManager).keySet().stream().map(finder::toResourceId);
 	}
 
 	@Inject(method = "<init>", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/ImmutableList$Builder;add(Ljava/lang/Object;)Lcom/google/common/collect/ImmutableList$Builder;", ordinal = 2, shift = At.Shift.AFTER, remap = false))
 	private void addFabricTemplateProvider(ResourceManager resourceManager, LevelStorage.Session session, DataFixer dataFixer, RegistryEntryLookup<Block> blockLookup, CallbackInfo ci, @Local ImmutableList.Builder<StructureTemplateManager.Provider> builder) {
-		builder.add(new StructureTemplateManager.Provider(this::fabric_loadSnbtFromResource, this::fabric_streamTemplatesFromResource));
+		builder.add(new StructureTemplateManager.Provider(this::fabric_loadSnbtFromResource, this::streamTemplatesFromResource));
 	}
 }
diff --git a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/mixin/renderer/client/SpriteAtlasTextureMixin.java b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/mixin/renderer/client/SpriteAtlasTextureMixin.java
index c88ce708f6..ba657486ba 100644
--- a/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/mixin/renderer/client/SpriteAtlasTextureMixin.java
+++ b/fabric-renderer-api-v1/src/client/java/net/fabricmc/fabric/mixin/renderer/client/SpriteAtlasTextureMixin.java
@@ -21,6 +21,7 @@
 import org.spongepowered.asm.mixin.Final;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.Unique;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@@ -38,20 +39,21 @@ public class SpriteAtlasTextureMixin implements SpriteFinderImpl.SpriteFinderAcc
 	@Shadow
 	private Map<Identifier, Sprite> sprites;
 
-	private SpriteFinderImpl fabric_spriteFinder = null;
+	@Unique
+	private SpriteFinderImpl spriteFinder = null;
 
 	@Inject(at = @At("RETURN"), method = "upload")
 	private void uploadHook(SpriteLoader.StitchResult arg, CallbackInfo ci) {
-		fabric_spriteFinder = null;
+		spriteFinder = null;
 	}
 
 	@Override
 	public SpriteFinderImpl fabric_spriteFinder() {
-		SpriteFinderImpl result = fabric_spriteFinder;
+		SpriteFinderImpl result = spriteFinder;
 
 		if (result == null) {
 			result = new SpriteFinderImpl(sprites, (SpriteAtlasTexture) (Object) this);
-			fabric_spriteFinder = result;
+			spriteFinder = result;
 		}
 
 		return result;
diff --git a/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/mixin/client/rendering/fluid/FluidRendererMixin.java b/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/mixin/client/rendering/fluid/FluidRendererMixin.java
index 2c1b580865..28731bed7a 100644
--- a/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/mixin/client/rendering/fluid/FluidRendererMixin.java
+++ b/fabric-rendering-fluids-v1/src/client/java/net/fabricmc/fabric/mixin/client/rendering/fluid/FluidRendererMixin.java
@@ -20,6 +20,7 @@
 import org.spongepowered.asm.mixin.Final;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.Unique;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.ModifyVariable;
@@ -52,7 +53,8 @@ public class FluidRendererMixin {
 	@Shadow
 	private Sprite waterOverlaySprite;
 
-	private final ThreadLocal<Block> fabric_neighborBlock = new ThreadLocal<>();
+	@Unique
+	private final ThreadLocal<Block> neighborBlock = new ThreadLocal<>();
 
 	@Inject(at = @At("RETURN"), method = "onResourceReload")
 	public void onResourceReloadReturn(CallbackInfo info) {
@@ -110,7 +112,7 @@ public Sprite modWaterOverlaySprite(FluidRenderer self) {
 	@Redirect(at = @At(value = "INVOKE", target = "Lnet/minecraft/block/BlockState;getBlock()Lnet/minecraft/block/Block;"), method = "render")
 	public Block getOverlayBlock(BlockState state) {
 		Block block = state.getBlock();
-		fabric_neighborBlock.set(block);
+		neighborBlock.set(block);
 
 		// An if-statement follows, we don't want this anymore and 'null' makes
 		// its condition always false (due to instanceof)
@@ -119,7 +121,7 @@ public Block getOverlayBlock(BlockState state) {
 
 	@ModifyVariable(at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/block/BlockState;getBlock()Lnet/minecraft/block/Block;"), method = "render", ordinal = 0)
 	public Sprite modSideSpriteForOverlay(Sprite chk) {
-		Block block = fabric_neighborBlock.get();
+		Block block = neighborBlock.get();
 
 		if (FluidRenderHandlerRegistry.INSTANCE.isBlockTransparent(block)) {
 			FluidRenderHandlerInfo info = FluidRenderingImpl.getCurrentInfo();
diff --git a/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/KeyedResourceReloadListenerClientMixin.java b/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/KeyedResourceReloadListenerClientMixin.java
index e05569017a..c3298db30f 100644
--- a/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/KeyedResourceReloadListenerClientMixin.java
+++ b/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/KeyedResourceReloadListenerClientMixin.java
@@ -21,6 +21,7 @@
 import java.util.Locale;
 
 import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Unique;
 
 import net.minecraft.client.render.WorldRenderer;
 import net.minecraft.client.render.block.BlockRenderManager;
@@ -41,45 +42,47 @@
 		WorldRenderer.class, BlockRenderManager.class, ItemRenderer.class
 })
 public abstract class KeyedResourceReloadListenerClientMixin implements IdentifiableResourceReloadListener {
-	private Identifier fabric$id;
-	private Collection<Identifier> fabric$dependencies;
+	@Unique
+	private Identifier id;
+	@Unique
+	private Collection<Identifier> dependencies;
 
 	@Override
 	public Identifier getFabricId() {
-		if (this.fabric$id == null) {
+		if (this.id == null) {
 			Object self = this;
 
 			if (self instanceof SoundManager) {
-				this.fabric$id = ResourceReloadListenerKeys.SOUNDS;
+				this.id = ResourceReloadListenerKeys.SOUNDS;
 			} else if (self instanceof BakedModelManager) {
-				this.fabric$id = ResourceReloadListenerKeys.MODELS;
+				this.id = ResourceReloadListenerKeys.MODELS;
 			} else if (self instanceof LanguageManager) {
-				this.fabric$id = ResourceReloadListenerKeys.LANGUAGES;
+				this.id = ResourceReloadListenerKeys.LANGUAGES;
 			} else if (self instanceof TextureManager) {
-				this.fabric$id = ResourceReloadListenerKeys.TEXTURES;
+				this.id = ResourceReloadListenerKeys.TEXTURES;
 			} else {
-				this.fabric$id = Identifier.ofVanilla("private/" + self.getClass().getSimpleName().toLowerCase(Locale.ROOT));
+				this.id = Identifier.ofVanilla("private/" + self.getClass().getSimpleName().toLowerCase(Locale.ROOT));
 			}
 		}
 
-		return this.fabric$id;
+		return this.id;
 	}
 
 	@Override
 	@SuppressWarnings({"ConstantConditions"})
 	public Collection<Identifier> getFabricDependencies() {
-		if (this.fabric$dependencies == null) {
+		if (this.dependencies == null) {
 			Object self = this;
 
 			if (self instanceof BakedModelManager || self instanceof WorldRenderer) {
-				this.fabric$dependencies = Collections.singletonList(ResourceReloadListenerKeys.TEXTURES);
+				this.dependencies = Collections.singletonList(ResourceReloadListenerKeys.TEXTURES);
 			} else if (self instanceof ItemRenderer || self instanceof BlockRenderManager) {
-				this.fabric$dependencies = Collections.singletonList(ResourceReloadListenerKeys.MODELS);
+				this.dependencies = Collections.singletonList(ResourceReloadListenerKeys.MODELS);
 			} else {
-				this.fabric$dependencies = Collections.emptyList();
+				this.dependencies = Collections.emptyList();
 			}
 		}
 
-		return this.fabric$dependencies;
+		return this.dependencies;
 	}
 }
diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/KeyedResourceReloadListenerMixin.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/KeyedResourceReloadListenerMixin.java
index f32b8726b5..ffcdd640ea 100644
--- a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/KeyedResourceReloadListenerMixin.java
+++ b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/KeyedResourceReloadListenerMixin.java
@@ -21,6 +21,7 @@
 import java.util.Locale;
 
 import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Unique;
 
 import net.minecraft.recipe.ServerRecipeManager;
 import net.minecraft.server.ServerAdvancementLoader;
@@ -36,36 +37,38 @@
 		/* private */
 })
 public abstract class KeyedResourceReloadListenerMixin implements IdentifiableResourceReloadListener {
-	private Identifier fabric$id;
-	private Collection<Identifier> fabric$dependencies;
+	@Unique
+	private Identifier id;
+	@Unique
+	private Collection<Identifier> dependencies;
 
 	@Override
 	@SuppressWarnings({"ConstantConditions", "RedundantCast"})
 	public Identifier getFabricId() {
-		if (this.fabric$id == null) {
+		if (this.id == null) {
 			Object self = this;
 
 			if (self instanceof ServerRecipeManager) {
-				this.fabric$id = ResourceReloadListenerKeys.RECIPES;
+				this.id = ResourceReloadListenerKeys.RECIPES;
 			} else if (self instanceof ServerAdvancementLoader) {
-				this.fabric$id = ResourceReloadListenerKeys.ADVANCEMENTS;
+				this.id = ResourceReloadListenerKeys.ADVANCEMENTS;
 			} else if (self instanceof FunctionLoader) {
-				this.fabric$id = ResourceReloadListenerKeys.FUNCTIONS;
+				this.id = ResourceReloadListenerKeys.FUNCTIONS;
 			} else {
-				this.fabric$id = Identifier.ofVanilla("private/" + self.getClass().getSimpleName().toLowerCase(Locale.ROOT));
+				this.id = Identifier.ofVanilla("private/" + self.getClass().getSimpleName().toLowerCase(Locale.ROOT));
 			}
 		}
 
-		return this.fabric$id;
+		return this.id;
 	}
 
 	@Override
 	@SuppressWarnings({"ConstantConditions", "RedundantCast"})
 	public Collection<Identifier> getFabricDependencies() {
-		if (this.fabric$dependencies == null) {
-			this.fabric$dependencies = Collections.emptyList();
+		if (this.dependencies == null) {
+			this.dependencies = Collections.emptyList();
 		}
 
-		return this.fabric$dependencies;
+		return this.dependencies;
 	}
 }
diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/server/LanguageMixin.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/server/LanguageMixin.java
index 8c6e99c6db..c4baebf180 100644
--- a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/server/LanguageMixin.java
+++ b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/server/LanguageMixin.java
@@ -29,6 +29,7 @@
 import org.spongepowered.asm.mixin.Final;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.Unique;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Redirect;
 
@@ -65,6 +66,7 @@ private static InputStream readCorrectVanillaResource(Class instance, String pat
 		}
 	}
 
+	@Unique
 	private static void loadFromPath(Path path, BiConsumer<String, String> entryConsumer) {
 		try (InputStream stream = Files.newInputStream(path)) {
 			LOGGER.debug("Loading translations from {}", path);
diff --git a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/mixin/transfer/FluidMixin.java b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/mixin/transfer/FluidMixin.java
index 9db06bfb50..1253a71bc3 100644
--- a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/mixin/transfer/FluidMixin.java
+++ b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/mixin/transfer/FluidMixin.java
@@ -19,6 +19,7 @@
 import java.util.Optional;
 
 import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Unique;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@@ -43,12 +44,13 @@
 @Mixin(Fluid.class)
 @SuppressWarnings("unused")
 public class FluidMixin implements FluidVariantCache {
+	@Unique
 	@SuppressWarnings("ConstantConditions")
-	private final FluidVariant fabric_cachedFluidVariant = new FluidVariantImpl((Fluid) (Object) this, ComponentChanges.EMPTY);
+	private final FluidVariant cachedFluidVariant = new FluidVariantImpl((Fluid) (Object) this, ComponentChanges.EMPTY);
 
 	@Override
 	public FluidVariant fabric_getCachedFluidVariant() {
-		return fabric_cachedFluidVariant;
+		return cachedFluidVariant;
 	}
 
 	@Inject(
diff --git a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/mixin/transfer/ItemMixin.java b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/mixin/transfer/ItemMixin.java
index 0d63053802..8a967a9309 100644
--- a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/mixin/transfer/ItemMixin.java
+++ b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/mixin/transfer/ItemMixin.java
@@ -17,6 +17,7 @@
 package net.fabricmc.fabric.mixin.transfer;
 
 import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Unique;
 
 import net.minecraft.component.ComponentChanges;
 import net.minecraft.item.Item;
@@ -30,11 +31,12 @@
  */
 @Mixin(Item.class)
 public class ItemMixin implements ItemVariantCache {
+	@Unique
 	@SuppressWarnings("ConstantConditions")
-	private final ItemVariant fabric_cachedItemVariant = new ItemVariantImpl((Item) (Object) this, ComponentChanges.EMPTY);
+	private final ItemVariant cachedItemVariant = new ItemVariantImpl((Item) (Object) this, ComponentChanges.EMPTY);
 
 	@Override
 	public ItemVariant fabric_getCachedItemVariant() {
-		return fabric_cachedItemVariant;
+		return cachedItemVariant;
 	}
 }


Reviews:
user: login: haykam821
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T21:53:40Z
user: login: modmuss50
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T21:54:39Z
user: login: Earthcomputer
body: It would be nice to create better error messages for these MatchXpath rules, because sometimes it's hard for new contributors to understand why checkstyle is failing. But that's a problem for later.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T14:39:32Z
user: login: Earthcomputer
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T19:57:18Z

Review comments:
diff_hunk: @@ -213,5 +213,66 @@
         <module name="MatchXpath">
             <property name="query" value="//RECORD_DEF[./MODIFIERS/LITERAL_PUBLIC][//PACKAGE_DEF//DOT/IDENT[@text='api']]"/>
         </module>
+
+        <!-- Prevent none @Unique fields in Mixin classes -->
path: checkstyle.xml
user: login: haykam821
body: ```suggestion
        <!-- Prevent non-@Unique fields in Mixin classes -->
```
created_at: 2025-05-06T21:53:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -213,5 +213,66 @@
         <module name="MatchXpath">
             <property name="query" value="//RECORD_DEF[./MODIFIERS/LITERAL_PUBLIC][//PACKAGE_DEF//DOT/IDENT[@text='api']]"/>
         </module>
+
+        <!-- Prevent none @Unique fields in Mixin classes -->
+        <module name="MatchXpath">
+            <property name="query" value="
+                //OBJBLOCK/VARIABLE_DEF[
+                    not(
+                        ./MODIFIERS/ANNOTATION/IDENT[
+                            @text='Unique'
+                            or @text='Shadow'
+                        ]
+                    )
+                    and
+                    ../../../CLASS_DEF[
+                        ./MODIFIERS/ANNOTATION/IDENT[@text='Mixin']
+                    ]
+                ]
+            "/>
+        </module>
+
+        <!-- Prevent none @Unique methods in Mixin classes -->
+        <!-- All Mixin and Mixin extras annotations must be listed here -->
path: checkstyle.xml
user: login: haykam821
body: ```suggestion
        <!-- Prevent non-@Unique methods in Mixin classes -->
        <!-- All Mixin and MixinExtras annotations must be listed here -->
```
created_at: 2025-05-06T21:53:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -213,5 +213,66 @@
         <module name="MatchXpath">
             <property name="query" value="//RECORD_DEF[./MODIFIERS/LITERAL_PUBLIC][//PACKAGE_DEF//DOT/IDENT[@text='api']]"/>
         </module>
+
+        <!-- Prevent none @Unique fields in Mixin classes -->
path: checkstyle.xml
user: login: modmuss50
body: I didnt learn from last time...
created_at: 2025-05-06T21:54:39Z
author_association: MEMBER
diff_hunk: @@ -207,11 +207,13 @@
         <!-- Prevent var for all cases other than new instance creation -->
         <module name="MatchXpath">
             <property name="query" value="//VARIABLE_DEF[./TYPE/IDENT[@text='var'] and not(./ASSIGN/EXPR/LITERAL_NEW)]"/>
+            <message key="matchxpath.match" value="var is not allowed before new instance creation"/>
path: checkstyle.xml
user: login: Earthcomputer
body: This rule does the opposite,
```suggestion
            <message key="matchxpath.match" value="var is only allowed before new instance creation"/>
```
created_at: 2025-05-07T19:56:35Z
author_association: CONTRIBUTOR

PR comments:
user: login: modmuss50
created_at: 2025-05-07T15:13:59Z
body: > It would be nice to create better error messages for these MatchXpath rules, because sometimes it's hard for new contributors to understand why checkstyle is failing. But that's a problem for later.

I 100% agree, I think we looked into this last time I added these and couldnt find a way to do it. If anyone knows how to do this please please let me know.
author_association: MEMBER
user: login: modmuss50
created_at: 2025-05-07T16:04:41Z
body: > It would be nice to create better error messages for these MatchXpath rules, because sometimes it's hard for new contributors to understand why checkstyle is failing. But that's a problem for later.

Added custom messages, I missed the fact that our checkstyle.xml file was using an older spec that didnt support them.
author_association: MEMBER
user: login: cputnam-a11y
created_at: 2025-05-07T16:07:00Z
body: That is a major improvement.
author_association: CONTRIBUTOR