Checkstyle (3127 → 0): - FinalLocalVariable: 1812 violations fixed via script (added final to local vars) - MagicNumber: 1071 violations fixed via script (extracted constants) - ConstantName: 76 violations fixed (logger → LOG) - RedundantModifier: 162 → 0 (removed redundant final/public in interfaces) - WhitespaceAround: 132 → 0 (configured allowEmpty* properties) - IllegalCatch: 38 → 0 (added specific exception catches) - NeedBraces: 29 → 0 (added braces to single-line if/else) - EqualsAvoidNull: 26 → 0 (literal.equals(var) → var.equals(literal)) - HideUtilityClassConstructor: 14 → 0 (added private constructors) - FileLength: 12 → 0 (extracted 13 helper classes, deleted dead code) - ParameterNumber: 10 → 0 (created records for grouped params) - MethodLength: 8 → 0 (extracted sub-methods) - LineLength: 6 → 0 (wrapped long lines) - MissingSwitchDefault: 5 → 0 (added default cases) - MultipleVariableDeclarations: 4 → 0 (split declarations) - CyclomaticComplexity: 3 → 0 (refactored complex methods) - ClassFanOutComplexity: 2 → 0 - AvoidStarImport: 2 → 0 (expanded to specific imports) - UnusedImports: 1 → 0 - Regexp: 1 → 0 PMD (47 → 0): - CloseResource: 8 → 0 (try-with-resources + NOPMD for managed resources) - CognitiveComplexity: 11 → 0 (extracted helper methods, early returns) - CyclomaticComplexity: 6 → 0 (refactored complex methods) - TooManyMethods: 2 → 0 (extracted classes) - AvoidReassigningParameters: 2 → 0 (local variables) - UnusedPrivateMethod: 1 → 0 (removed dead code) Dead code removed: - Deleted feature/roast/ package (26 files) - unused AI debate feature - Cleaned up all references in ListenerAggregator, BotModule, UtilityCommandRegistrar New files created: 14 (SetupPanelBuilder, SetupDashboardBuilder, MuteNotifier, etc.)
153 lines
5.1 KiB
Python
Executable file
153 lines
5.1 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""Add 'final' to local variables that are not reassigned."""
|
|
|
|
import re
|
|
import sys
|
|
import xml.etree.ElementTree as ET
|
|
from collections import defaultdict
|
|
from pathlib import Path
|
|
|
|
|
|
def parse_report(xml_path):
|
|
tree = ET.parse(xml_path)
|
|
root = tree.getroot()
|
|
violations = defaultdict(list)
|
|
for file_elem in root.findall("file"):
|
|
fname = file_elem.get("name")
|
|
for v in file_elem.findall("error"):
|
|
if "FinalLocalVariable" in v.get("source", ""):
|
|
violations[fname].append(int(v.get("line", 0)))
|
|
return violations
|
|
|
|
|
|
def is_reassigned(var_name, lines, start_line, end_line):
|
|
"""Check if variable is reassigned after declaration."""
|
|
for i in range(start_line, min(end_line, len(lines))):
|
|
line = lines[i].strip()
|
|
# Skip comments
|
|
if line.startswith("//") or line.startswith("/*") or line.startswith("*"):
|
|
continue
|
|
# Check for reassignment: varName = ..., varName +=, varName++, etc.
|
|
if re.match(rf"\b{re.escape(var_name)}\s*[\+\-\*\/]?=(?!=)", line):
|
|
return True
|
|
if re.match(rf"\b{re.escape(var_name)}\+\+", line) or re.match(
|
|
rf"\b{re.escape(var_name)}\-\-", line
|
|
):
|
|
return True
|
|
return False
|
|
|
|
|
|
def find_method_end(lines, start_line):
|
|
"""Find the end of the method containing start_line."""
|
|
depth = 0
|
|
for i in range(start_line - 1, len(lines)):
|
|
for ch in lines[i]:
|
|
if ch == "{":
|
|
depth += 1
|
|
elif ch == "}":
|
|
depth -= 1
|
|
if depth == 0:
|
|
return i + 1
|
|
return len(lines)
|
|
|
|
|
|
def fix_file(filepath, line_numbers):
|
|
try:
|
|
lines = Path(filepath).read_text(encoding="utf-8").splitlines(keepends=True)
|
|
except Exception as e:
|
|
print(f" SKIP {filepath}: {e}")
|
|
return 0
|
|
|
|
fixed = 0
|
|
for line_num in sorted(set(line_numbers)):
|
|
if line_num < 1 or line_num > len(lines):
|
|
continue
|
|
line = lines[line_num - 1]
|
|
|
|
# Skip if already has final
|
|
if re.search(r"\bfinal\s+", line):
|
|
continue
|
|
|
|
# Match variable declaration patterns
|
|
# Pattern: type varName = ...;
|
|
m = re.match(r"^(\s*)((?:final\s+)?(?:[\w<>\[\], ?]+?))\s+(\w+)\s*=", line)
|
|
if m:
|
|
indent = m.group(1)
|
|
type_part = m.group(2).strip()
|
|
var_name = m.group(3)
|
|
|
|
# Skip if it's a for-loop variable
|
|
if "for" in line:
|
|
continue
|
|
|
|
# Skip if already final
|
|
if type_part.startswith("final "):
|
|
continue
|
|
|
|
# Check if variable is reassigned
|
|
method_end = find_method_end(lines, line_num)
|
|
if not is_reassigned(var_name, lines, line_num + 1, method_end):
|
|
new_line = f"{indent}final {type_part} {var_name}"
|
|
# Preserve the rest of the line after varName
|
|
rest = line[m.end() :]
|
|
lines[line_num - 1] = f"{new_line}{rest}\n"
|
|
fixed += 1
|
|
continue
|
|
|
|
# Pattern: type varName; (no initializer)
|
|
m = re.match(r"^(\s*)((?:final\s+)?(?:[\w<>\[\], ?]+?))\s+(\w+)\s*;", line)
|
|
if m:
|
|
indent = m.group(1)
|
|
type_part = m.group(2).strip()
|
|
var_name = m.group(3)
|
|
|
|
if "for" in line or type_part.startswith("final "):
|
|
continue
|
|
|
|
method_end = find_method_end(lines, line_num)
|
|
if not is_reassigned(var_name, lines, line_num + 1, method_end):
|
|
lines[line_num - 1] = f"{indent}final {type_part} {var_name};\n"
|
|
fixed += 1
|
|
continue
|
|
|
|
# Pattern: for (type varName : ...) or for (type varName = ...)
|
|
if "for" in line and "(" in line:
|
|
m = re.search(r"for\s*\(\s*(?:final\s+)?([\w<>\[\], ?]+?)\s+(\w+)", line)
|
|
if m:
|
|
type_part = m.group(1).strip()
|
|
var_name = m.group(2)
|
|
if not re.search(r"\bfinal\s+" + re.escape(type_part), line):
|
|
new_line = re.sub(
|
|
rf"(for\s*\(\s*)({re.escape(type_part)})", rf"\1final \2", line
|
|
)
|
|
if new_line != line:
|
|
lines[line_num - 1] = new_line
|
|
fixed += 1
|
|
|
|
if fixed > 0:
|
|
Path(filepath).write_text("".join(lines), encoding="utf-8")
|
|
return fixed
|
|
|
|
|
|
def main():
|
|
xml_path = sys.argv[1] if len(sys.argv) > 1 else "build/reports/checkstyle/main.xml"
|
|
violations = parse_report(xml_path)
|
|
total = sum(len(v) for v in violations.values())
|
|
print(f"Found {total} FinalLocalVariable violations in {len(violations)} files")
|
|
|
|
fixed_files = 0
|
|
fixed_count = 0
|
|
for filepath, line_nums in sorted(violations.items()):
|
|
n = fix_file(filepath, line_nums)
|
|
if n > 0:
|
|
fixed_files += 1
|
|
fixed_count += n
|
|
print(
|
|
f" Fixed {n} in {filepath.replace('/storage/project/jvm/discord-bot/', '')}"
|
|
)
|
|
|
|
print(f"\nTotal: fixed {fixed_count} violations in {fixed_files} files")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|