chore: add PMD 7.8.0 and fix warnings/antipatterns

- build.gradle.kts: add pmd plugin with security/bestpractices ruleset (UnusedPrivateField, PreserveStackTrace, AvoidPrintStackTrace, CloseResource, etc), html/xml reports
- fix 8 deprecation warnings: Bukkit.getOfflinePlayer(String) suppressed via @SuppressWarnings(deprecation) in ColorCommandHandler, PrefixCommandHandler, GradientAdminCommand, PlayerUtil
- fix 9 try-resource warnings in IgnoreServiceTest via @SuppressWarnings({"try","PMD.UnusedLocalVariable"})
- PMD fixes: AvoidPrintStackTrace -> logger.log in BaseCommand, MuteHistoryManager, CustomCommandManager; CloseResource via try-with-resources in BaseConfig, GradientMessages, GradientDataManager, DiscordConfig; UnusedPrivateField removed plugin field in PlayerEventListener; MuteHistoryManager System.err -> logger
- verified: gradle clean build pmdMain pmdTest checkstyleMain checkstyleTest test shadowJar all green, zero javac warnings, no secrets found, antipatterns (Thread.sleep in async webhook/rate-limiter kept isolated, printStackTrace removed)
This commit is contained in:
loki5512344 2026-09-20 16:56:02 +02:00
parent 83e980d1bb
commit bd39118b14
Signed by: boba
GPG key ID: 253067914055423B
14 changed files with 85 additions and 34 deletions

View file

@ -3,6 +3,7 @@ plugins {
id("com.gradleup.shadow") version "9.0.0" id("com.gradleup.shadow") version "9.0.0"
jacoco jacoco
checkstyle checkstyle
pmd
} }
group = "com.loki" group = "com.loki"
@ -57,6 +58,37 @@ tasks.withType<Checkstyle>().configureEach {
} }
} }
pmd {
toolVersion = "7.8.0"
ruleSetConfig = resources.text.fromString(
"""
<?xml version="1.0"?>
<ruleset name="LoChat PMD"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
<description>LoChat security + best practices (no style nit)</description>
<rule ref="category/java/bestpractices.xml/UnusedPrivateField"/>
<rule ref="category/java/bestpractices.xml/UnusedLocalVariable"/>
<rule ref="category/java/bestpractices.xml/PreserveStackTrace"/>
<rule ref="category/java/bestpractices.xml/AvoidPrintStackTrace"/>
<rule ref="category/java/bestpractices.xml/UseCollectionIsEmpty"/>
<rule ref="category/java/errorprone.xml/CloseResource"/>
<rule ref="category/java/errorprone.xml/BrokenNullCheck"/>
<rule ref="category/java/security.xml/HardCodedCryptoKey"/>
</ruleset>
""".trimIndent()
)
isIgnoreFailures = false
}
tasks.withType<org.gradle.api.plugins.quality.Pmd>().configureEach {
reports {
xml.required.set(true)
html.required.set(true)
}
}
java { java {
toolchain { toolchain {
languageVersion.set(JavaLanguageVersion.of(21)) languageVersion.set(JavaLanguageVersion.of(21))

View file

@ -11,6 +11,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
import java.util.logging.Level;
/** /**
* Базовый класс для всех команд плагина * Базовый класс для всех команд плагина
@ -30,8 +31,7 @@ public abstract class BaseCommand implements CommandExecutor, TabCompleter {
try { try {
return executeCommand(sender, command, label, args); return executeCommand(sender, command, label, args);
} catch (Exception e) { } catch (Exception e) {
plugin.getLogger().severe("Ошибка в команде " + command.getName() + ": " + e.getMessage()); plugin.getLogger().log(Level.WARNING, "Ошибка в команде " + command.getName() + ": " + e.getMessage(), e);
e.printStackTrace();
sender.sendMessage("§cПроизошла ошибка при выполнении команды!"); sender.sendMessage("§cПроизошла ошибка при выполнении команды!");
return true; return true;
} }

View file

@ -68,14 +68,14 @@ public abstract class BaseConfig {
if (!configFile.exists()) { if (!configFile.exists()) {
try { try {
String resourcePath = inConfigFolder ? "config/" + fileName : fileName; String resourcePath = inConfigFolder ? "config/" + fileName : fileName;
InputStream in = plugin.getResource(resourcePath); try (InputStream in = plugin.getResource(resourcePath)) {
if (in != null) { if (in != null) {
Files.copy(in, configFile.toPath()); Files.copy(in, configFile.toPath());
in.close();
plugin.getLogger().info("Created config file: " + (inConfigFolder ? "config/" : "") + fileName); plugin.getLogger().info("Created config file: " + (inConfigFolder ? "config/" : "") + fileName);
} else { } else {
plugin.getLogger().warning("Resource not found: " + resourcePath); plugin.getLogger().warning("Resource not found: " + resourcePath);
} }
}
} catch (Exception e) { } catch (Exception e) {
plugin.getLogger().warning("Failed to copy " + fileName + ": " + e.getMessage()); plugin.getLogger().warning("Failed to copy " + fileName + ": " + e.getMessage());
} }

View file

@ -24,6 +24,8 @@ import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
public class MuteHistoryManager { public class MuteHistoryManager {
@ -112,7 +114,7 @@ public class MuteHistoryManager {
} }
moveIntoPlace(tmp); moveIntoPlace(tmp);
} catch (IOException e) { } catch (IOException e) {
System.err.println("Failed to save mute history: " + e.getMessage()); Logger.getLogger(MuteHistoryManager.class.getName()).log(Level.WARNING, "Failed to save mute history", e);
} finally { } finally {
lock.unlock(); lock.unlock();
} }
@ -140,7 +142,7 @@ public class MuteHistoryManager {
history.putAll(loaded); history.putAll(loaded);
} }
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); Logger.getLogger(MuteHistoryManager.class.getName()).log(Level.WARNING, "Failed to load mute history", e);
} }
} }
} }

View file

@ -95,6 +95,7 @@ public class GradientAdminCommand implements CommandExecutor, TabCompleter {
sender.sendMessage("§e/aprefix updateall §7— обновить display names всех игроков"); sender.sendMessage("§e/aprefix updateall §7— обновить display names всех игроков");
} }
@SuppressWarnings("deprecation")
private void handleInfo(CommandSender sender, String playerName, GradientMessages msg) { private void handleInfo(CommandSender sender, String playerName, GradientMessages msg) {
OfflinePlayer target = Bukkit.getOfflinePlayer(playerName); OfflinePlayer target = Bukkit.getOfflinePlayer(playerName);
if (!target.hasPlayedBefore() && !target.isOnline()) { if (!target.hasPlayedBefore() && !target.isOnline()) {

View file

@ -29,6 +29,7 @@ public class ColorCommandHandler {
/** /**
* Установить цвет игроку * Установить цвет игроку
*/ */
@SuppressWarnings("deprecation")
public void handleSetColor(CommandSender sender, String[] args, GradientMessages msg) { public void handleSetColor(CommandSender sender, String[] args, GradientMessages msg) {
if (args.length < 3) { if (args.length < 3) {
sender.sendMessage("§cИспользование: /aprefix setcolor <игрок> <цвет1> [цвет2] ..."); sender.sendMessage("§cИспользование: /aprefix setcolor <игрок> <цвет1> [цвет2] ...");
@ -69,6 +70,7 @@ public class ColorCommandHandler {
/** /**
* Включить/выключить цвет * Включить/выключить цвет
*/ */
@SuppressWarnings("deprecation")
public void handleColorToggle(CommandSender sender, String[] args, boolean enable, GradientMessages msg) { public void handleColorToggle(CommandSender sender, String[] args, boolean enable, GradientMessages msg) {
if (args.length < 2) { if (args.length < 2) {
sender.sendMessage("§cИспользование: /aprefix " + (enable ? "coloron" : "coloroff") + " <игрок>"); sender.sendMessage("§cИспользование: /aprefix " + (enable ? "coloron" : "coloroff") + " <игрок>");
@ -93,6 +95,7 @@ public class ColorCommandHandler {
/** /**
* Сбросить цвет * Сбросить цвет
*/ */
@SuppressWarnings("deprecation")
public void handleResetColor(CommandSender sender, String[] args, GradientMessages msg) { public void handleResetColor(CommandSender sender, String[] args, GradientMessages msg) {
if (args.length < 2) { if (args.length < 2) {
sender.sendMessage("§cИспользование: /aprefix resetcolor <игрок>"); sender.sendMessage("§cИспользование: /aprefix resetcolor <игрок>");

View file

@ -25,6 +25,7 @@ public class PrefixCommandHandler {
/** /**
* Установить префикс игроку * Установить префикс игроку
*/ */
@SuppressWarnings("deprecation")
public void handleSetPrefix(CommandSender sender, String[] args, GradientMessages msg) { public void handleSetPrefix(CommandSender sender, String[] args, GradientMessages msg) {
if (args.length < 3) { if (args.length < 3) {
sender.sendMessage("§cИспользование: /aprefix setprefix <игрок> <префикс>"); sender.sendMessage("§cИспользование: /aprefix setprefix <игрок> <префикс>");
@ -65,6 +66,7 @@ public class PrefixCommandHandler {
/** /**
* Включить/выключить префикс * Включить/выключить префикс
*/ */
@SuppressWarnings("deprecation")
public void handlePrefixToggle(CommandSender sender, String[] args, boolean enable, GradientMessages msg) { public void handlePrefixToggle(CommandSender sender, String[] args, boolean enable, GradientMessages msg) {
if (args.length < 2) { if (args.length < 2) {
sender.sendMessage("§cИспользование: /aprefix " + (enable ? "prefixon" : "prefixoff") + " <игрок>"); sender.sendMessage("§cИспользование: /aprefix " + (enable ? "prefixon" : "prefixoff") + " <игрок>");
@ -89,6 +91,7 @@ public class PrefixCommandHandler {
/** /**
* Сбросить префикс * Сбросить префикс
*/ */
@SuppressWarnings("deprecation")
public void handleResetPrefix(CommandSender sender, String[] args, GradientMessages msg) { public void handleResetPrefix(CommandSender sender, String[] args, GradientMessages msg) {
if (args.length < 2) { if (args.length < 2) {
sender.sendMessage("§cИспользование: /aprefix resetprefix <игрок>"); sender.sendMessage("§cИспользование: /aprefix resetprefix <игрок>");

View file

@ -37,12 +37,15 @@ public class GradientMessages {
} }
messages = YamlConfiguration.loadConfiguration(messagesFile); messages = YamlConfiguration.loadConfiguration(messagesFile);
InputStream defStream = plugin.getResource("messages.yml"); try (InputStream defStream = plugin.getResource("messages.yml")) {
if (defStream != null) { if (defStream != null) {
YamlConfiguration defConfig = YamlConfiguration.loadConfiguration( YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(
new InputStreamReader(defStream, StandardCharsets.UTF_8)); new InputStreamReader(defStream, StandardCharsets.UTF_8));
messages.setDefaults(defConfig); messages.setDefaults(defConfig);
} }
} catch (java.io.IOException e) {
plugin.getLogger().warning("Failed to load default messages.yml: " + e.getMessage());
}
} }
public void reload() { public void reload() {

View file

@ -104,7 +104,7 @@ public class GradientDataManager {
"SELECT prefix, colors, color_enabled, prefix_enabled, " "SELECT prefix, colors, color_enabled, prefix_enabled, "
+ "prefix_purchased, last_color_change, last_prefix_change FROM gradient_data WHERE uuid = ?")) { + "prefix_purchased, last_color_change, last_prefix_change FROM gradient_data WHERE uuid = ?")) {
stmt.setString(1, uuid.toString()); stmt.setString(1, uuid.toString());
ResultSet rs = stmt.executeQuery(); try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) { if (rs.next()) {
data.setPrefix(rs.getString("prefix")); data.setPrefix(rs.getString("prefix"));
String colorsStr = rs.getString("colors"); String colorsStr = rs.getString("colors");
@ -117,6 +117,7 @@ public class GradientDataManager {
data.setLastColorChange(rs.getLong("last_color_change")); data.setLastColorChange(rs.getLong("last_color_change"));
data.setLastPrefixChange(rs.getLong("last_prefix_change")); data.setLastPrefixChange(rs.getLong("last_prefix_change"));
} }
}
} catch (SQLException e) { } catch (SQLException e) {
plugin.getLogger().warning("Ошибка загрузки gradient данных: " + e.getMessage()); plugin.getLogger().warning("Ошибка загрузки gradient данных: " + e.getMessage());
} }

View file

@ -25,11 +25,9 @@ public class DiscordConfig {
File configFile = new File(configFolder, "discord.yml"); File configFile = new File(configFolder, "discord.yml");
if (!configFile.exists()) { if (!configFile.exists()) {
try { try (java.io.InputStream in = plugin.getResource("config/discord.yml")) {
java.io.InputStream in = plugin.getResource("config/discord.yml");
if (in != null) { if (in != null) {
java.nio.file.Files.copy(in, configFile.toPath()); java.nio.file.Files.copy(in, configFile.toPath());
in.close();
} }
} catch (Exception e) { } catch (Exception e) {
plugin.getLogger().warning("Failed to copy config/discord.yml: " + e.getMessage()); plugin.getLogger().warning("Failed to copy config/discord.yml: " + e.getMessage());

View file

@ -23,7 +23,6 @@ import org.bukkit.inventory.ItemStack;
* Слушатель событий игрока * Слушатель событий игрока
*/ */
public class PlayerEventListener implements Listener { public class PlayerEventListener implements Listener {
private final LoChat plugin;
private final PlayerService playerService; private final PlayerService playerService;
private final MessagingService messagingService; private final MessagingService messagingService;
private final NickService nickService; private final NickService nickService;
@ -32,7 +31,6 @@ public class PlayerEventListener implements Listener {
public PlayerEventListener(LoChat plugin, PlayerService playerService, MessagingService messagingService, public PlayerEventListener(LoChat plugin, PlayerService playerService, MessagingService messagingService,
NickService nickService, AdvancedMessageFilter advancedFilter) { NickService nickService, AdvancedMessageFilter advancedFilter) {
this.plugin = plugin;
this.playerService = playerService; this.playerService = playerService;
this.messagingService = messagingService; this.messagingService = messagingService;
this.nickService = nickService; this.nickService = nickService;

View file

@ -19,6 +19,7 @@ import java.io.File;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.logging.Level;
public class CustomCommandManager { public class CustomCommandManager {
@ -101,8 +102,7 @@ public class CustomCommandManager {
} }
} catch (Exception e) { } catch (Exception e) {
plugin.getLogger().severe("Ошибка регистрации команды " + data.name() + ": " + e.getMessage()); plugin.getLogger().log(Level.WARNING, "Ошибка регистрации команды " + data.name() + ": " + e.getMessage(), e);
e.printStackTrace();
} }
} }

View file

@ -54,6 +54,7 @@ public final class PlayerUtil {
* @param name имя игрока * @param name имя игрока
* @return UUID игрока или null если не найден * @return UUID игрока или null если не найден
*/ */
@SuppressWarnings("deprecation")
public static UUID findPlayerUUID(String name) { public static UUID findPlayerUUID(String name) {
// Сначала ищем онлайн игрока // Сначала ищем онлайн игрока
Player onlinePlayer = Bukkit.getPlayer(name); Player onlinePlayer = Bukkit.getPlayer(name);

View file

@ -30,6 +30,7 @@ class IgnoreServiceTest {
} }
@Test @Test
@SuppressWarnings({"try", "PMD.UnusedLocalVariable"})
void isIgnoring_notIgnored() { void isIgnoring_notIgnored() {
try (MockedStatic<com.loki.lochat.utils.platform.FoliaUtil> ignored = Mockito.mockStatic(com.loki.lochat.utils.platform.FoliaUtil.class)) { try (MockedStatic<com.loki.lochat.utils.platform.FoliaUtil> ignored = Mockito.mockStatic(com.loki.lochat.utils.platform.FoliaUtil.class)) {
UUID player = UUID.randomUUID(); UUID player = UUID.randomUUID();
@ -39,6 +40,7 @@ class IgnoreServiceTest {
} }
@Test @Test
@SuppressWarnings({"try", "PMD.UnusedLocalVariable"})
void addIgnore_thenIsIgnoring() { void addIgnore_thenIsIgnoring() {
try (MockedStatic<com.loki.lochat.utils.platform.FoliaUtil> fu = Mockito.mockStatic(com.loki.lochat.utils.platform.FoliaUtil.class)) { try (MockedStatic<com.loki.lochat.utils.platform.FoliaUtil> fu = Mockito.mockStatic(com.loki.lochat.utils.platform.FoliaUtil.class)) {
UUID player = UUID.randomUUID(); UUID player = UUID.randomUUID();
@ -49,6 +51,7 @@ class IgnoreServiceTest {
} }
@Test @Test
@SuppressWarnings({"try", "PMD.UnusedLocalVariable"})
void addIgnore_duplicateReturnsFalse() { void addIgnore_duplicateReturnsFalse() {
try (MockedStatic<com.loki.lochat.utils.platform.FoliaUtil> fu = Mockito.mockStatic(com.loki.lochat.utils.platform.FoliaUtil.class)) { try (MockedStatic<com.loki.lochat.utils.platform.FoliaUtil> fu = Mockito.mockStatic(com.loki.lochat.utils.platform.FoliaUtil.class)) {
UUID player = UUID.randomUUID(); UUID player = UUID.randomUUID();
@ -59,6 +62,7 @@ class IgnoreServiceTest {
} }
@Test @Test
@SuppressWarnings({"try", "PMD.UnusedLocalVariable"})
void removeIgnore() { void removeIgnore() {
try (MockedStatic<com.loki.lochat.utils.platform.FoliaUtil> fu = Mockito.mockStatic(com.loki.lochat.utils.platform.FoliaUtil.class)) { try (MockedStatic<com.loki.lochat.utils.platform.FoliaUtil> fu = Mockito.mockStatic(com.loki.lochat.utils.platform.FoliaUtil.class)) {
UUID player = UUID.randomUUID(); UUID player = UUID.randomUUID();
@ -70,6 +74,7 @@ class IgnoreServiceTest {
} }
@Test @Test
@SuppressWarnings({"try", "PMD.UnusedLocalVariable"})
void removeIgnore_notIgnored() { void removeIgnore_notIgnored() {
try (MockedStatic<com.loki.lochat.utils.platform.FoliaUtil> fu = Mockito.mockStatic(com.loki.lochat.utils.platform.FoliaUtil.class)) { try (MockedStatic<com.loki.lochat.utils.platform.FoliaUtil> fu = Mockito.mockStatic(com.loki.lochat.utils.platform.FoliaUtil.class)) {
assertFalse(ignoreService.removeIgnore(UUID.randomUUID(), UUID.randomUUID())); assertFalse(ignoreService.removeIgnore(UUID.randomUUID(), UUID.randomUUID()));
@ -77,6 +82,7 @@ class IgnoreServiceTest {
} }
@Test @Test
@SuppressWarnings({"try", "PMD.UnusedLocalVariable"})
void getIgnoredCount() { void getIgnoredCount() {
try (MockedStatic<com.loki.lochat.utils.platform.FoliaUtil> fu = Mockito.mockStatic(com.loki.lochat.utils.platform.FoliaUtil.class)) { try (MockedStatic<com.loki.lochat.utils.platform.FoliaUtil> fu = Mockito.mockStatic(com.loki.lochat.utils.platform.FoliaUtil.class)) {
UUID player = UUID.randomUUID(); UUID player = UUID.randomUUID();
@ -89,6 +95,7 @@ class IgnoreServiceTest {
} }
@Test @Test
@SuppressWarnings({"try", "PMD.UnusedLocalVariable"})
void getIgnoredPlayers_returnsSet() { void getIgnoredPlayers_returnsSet() {
try (MockedStatic<com.loki.lochat.utils.platform.FoliaUtil> fu = Mockito.mockStatic(com.loki.lochat.utils.platform.FoliaUtil.class)) { try (MockedStatic<com.loki.lochat.utils.platform.FoliaUtil> fu = Mockito.mockStatic(com.loki.lochat.utils.platform.FoliaUtil.class)) {
UUID player = UUID.randomUUID(); UUID player = UUID.randomUUID();
@ -99,6 +106,7 @@ class IgnoreServiceTest {
} }
@Test @Test
@SuppressWarnings({"try", "PMD.UnusedLocalVariable"})
void clearIgnores() { void clearIgnores() {
try (MockedStatic<com.loki.lochat.utils.platform.FoliaUtil> fu = Mockito.mockStatic(com.loki.lochat.utils.platform.FoliaUtil.class)) { try (MockedStatic<com.loki.lochat.utils.platform.FoliaUtil> fu = Mockito.mockStatic(com.loki.lochat.utils.platform.FoliaUtil.class)) {
UUID player = UUID.randomUUID(); UUID player = UUID.randomUUID();
@ -109,6 +117,7 @@ class IgnoreServiceTest {
} }
@Test @Test
@SuppressWarnings({"try", "PMD.UnusedLocalVariable"})
void multiplePlayersIndepedent() { void multiplePlayersIndepedent() {
try (MockedStatic<com.loki.lochat.utils.platform.FoliaUtil> fu = Mockito.mockStatic(com.loki.lochat.utils.platform.FoliaUtil.class)) { try (MockedStatic<com.loki.lochat.utils.platform.FoliaUtil> fu = Mockito.mockStatic(com.loki.lochat.utils.platform.FoliaUtil.class)) {
UUID a = UUID.randomUUID(); UUID a = UUID.randomUUID();