name: checkstyle description: Check and fix Checkstyle violations in jhelm modules argument-hint: [module] allowed-tools: Bash(./mvnw *), Bash(python3 *)
Check and Fix Checkstyle Violations
Configuration
- Plugin:
maven-checkstyle-plugin3.6.0 withspring-javaformat-checkstyle0.0.43 - Suppressions:
checkstyle-suppressions.xmlin project root - Violations fail the build at
validatephase (before compile)
Step 1: Check violations
All modules:
./mvnw validate 2>&1 | grep -E "violations|ERROR.*\.java"
Specific module (e.g., /checkstyle jhelm-core):
./mvnw validate -pl $ARGUMENTS 2>&1 | grep "^\[ERROR\]"
Step 2: Auto-format first
Always run spring-javaformat:apply before checking violations — it fixes indentation/spacing automatically:
./mvnw spring-javaformat:apply
Step 3: Fix remaining violations with Python script
Save this script as /tmp/fix_violations.py and run for any module:
#!/usr/bin/env python3
"""Fix checkstyle violations: SpringCatch, NeedBraces, SpringLambda, SpringTernary,
AvoidStarImport, UnusedImports, AnnotationUseStyle."""
import re, os
from pathlib import Path
JAVA_KEYWORDS = {
'abstract','assert','boolean','break','byte','case','catch','char','class',
'const','continue','default','do','double','else','enum','extends','final',
'finally','float','for','goto','if','implements','import','instanceof','int',
'interface','long','native','new','package','private','protected','public',
'return','short','static','strictfp','super','switch','synchronized','this',
'throw','throws','transient','try','void','volatile','while',
'null','true','false','var','record','sealed','permits','yield',
}
STATIC_STAR_IMPORTS = {
'org.junit.jupiter.api.Assertions': [
'assertEquals','assertNotEquals','assertTrue','assertFalse','assertNull',
'assertNotNull','assertThrows','assertDoesNotThrow','assertSame','assertNotSame',
'fail','assertAll','assertArrayEquals','assertIterableEquals','assertInstanceOf',
],
'org.mockito.Mockito': [
'mock','when','verify','doReturn','doThrow','doNothing','doAnswer','spy',
'times','never','atLeast','atLeastOnce','atMost','reset',
'verifyNoInteractions','verifyNoMoreInteractions','lenient','inOrder',
'mockConstruction','mockStatic','any','anyString','anyInt','anyLong',
'anyBoolean','anyList','anyMap','anySet','eq','isNull','isNotNull','same','argThat',
],
'org.mockito.ArgumentMatchers': [
'any','anyString','anyInt','anyLong','anyBoolean','anyList','anyMap',
'anySet','eq','isNull','isNotNull','same','argThat',
],
}
REGULAR_STAR_IMPORTS = {
'java.util': ['List','ArrayList','Map','HashMap','LinkedHashMap','TreeMap',
'Set','HashSet','LinkedHashSet','TreeSet','Collections','Arrays','Optional',
'Iterator','Queue','Deque','LinkedList','Objects','Comparator','UUID',],
'java.io': ['File','InputStream','OutputStream','IOException','FileInputStream',
'FileOutputStream','BufferedInputStream','BufferedReader','BufferedWriter',
'StringWriter','StringReader','Closeable','ByteArrayInputStream','ByteArrayOutputStream',],
'org.alexmond.jhelm.core': ['Engine','Chart','ChartMetadata','ChartLoader',
'ChartLock','Dependency','DependencyResolver','Release','RepositoryConfig',
'KubeService','ListAction','InstallAction','UpgradeAction','UninstallAction',
'RollbackAction','GetAction','StatusAction','HistoryAction','ShowAction',
'TemplateAction','CreateAction','RepoManager','RegistryManager',
'HelmChartTemplates','CoreConfig',],
'io.kubernetes.client.openapi.models': None, # auto-detect V1* classes
}
def fix_spring_catch(content):
lines = content.split('\n')
result, i = [], 0
while i < len(lines):
line = lines[i]
m = re.match(r'^(\s*catch\s*\(\s*(?:\w+(?:\s*\|\s*\w+)*)\s+)(e)(\s*\)\s*)\{(.*)$', line)
if m:
result.append(m.group(1) + 'ex' + m.group(3) + '{' + m.group(4))
i += 1
depth = 1
while i < len(lines) and depth > 0:
bl = lines[i]
for ch in bl:
if ch == '{': depth += 1
elif ch == '}': depth -= 1
if depth > 0:
bl = re.sub(r'\be\.', 'ex.', bl)
bl = re.sub(r'(?<=[,\s(])\be\b(?=[,\s);])', 'ex', bl)
bl = re.sub(r'\bthrow\s+e\s*;', 'throw ex;', bl)
result.append(bl)
i += 1
else:
result.append(line)
i += 1
return '\n'.join(result)
def count_net_parens(line):
depth, in_str, str_ch, esc = 0, False, None, False
for ch in line:
if esc: esc = False; continue
if ch == '\\': esc = True; continue
if in_str:
if ch == str_ch: in_str = False
else:
if ch in ('"', "'"): in_str, str_ch = True, ch
elif ch == '(': depth += 1
elif ch == ')': depth -= 1
return depth
def fix_need_braces(content):
lines = content.split('\n')
result, i = [], 0
CTRL = re.compile(r'^\s*(?:(?:else\s+)?if|for|while)\s*\(')
ELSE = re.compile(r'^(\s*)else\s*$')
while i < len(lines):
line = lines[i]
stripped = line.rstrip()
em = ELSE.match(stripped)
if em and i + 1 < len(lines):
indent = em.group(1)
ns = lines[i+1].strip()
if ns and not ns.startswith(('{','//','*','@')):
result.extend([line + ' {', lines[i+1], indent + '}'])
i += 2; continue
if CTRL.match(stripped):
net = count_net_parens(stripped)
if net == 0 and not stripped.endswith('{') and stripped.endswith(')'):
if i + 1 < len(lines):
ns = lines[i+1].strip()
if ns and not ns.startswith(('{','//','*','@')) and not CTRL.match(ns):
indent = re.match(r'^(\s*)', line).group(1)
result.extend([line + ' {', lines[i+1], indent + '}'])
i += 2; continue
result.append(line); i += 1
return '\n'.join(result)
def fix_lambda_blocks(content):
lines = content.split('\n')
result, i = [], 0
while i < len(lines):
line = lines[i]
m = re.match(r'^(.*?)\s*->\s*\{\s*$', line.rstrip())
if m and i + 2 < len(lines):
body = lines[i+1].strip()
close = lines[i+2].strip()
if re.match(r'^\}\s*[\);]*\s*$', close) and body.endswith(';'):
val = body[7:-1] if body.startswith('return ') else body[:-1]
suffix = close[1:]
result.append(f'{m.group(1)} -> {val}{suffix}')
i += 3; continue
result.append(line); i += 1
return '\n'.join(result)
def fix_lambda_parens(content):
def fix_line(line):
def replacer(m):
ident = m.group(1)
if ident in JAVA_KEYWORDS: return m.group(0)
pre = line[:m.start(1)]
if re.search(r'\bcase\b', pre): return m.group(0)
if pre.rstrip() and re.search(r'\b[A-Z]\w*(?:<[^>]*>)?\s*$', pre.rstrip()): return m.group(0)
if line[m.end(1):m.end(1)+1] == ')': return m.group(0)
return f'({ident}) ->'
return re.sub(r'\b([a-zA-Z_]\w*)\s+->', replacer, line)
return '\n'.join(fix_line(l) for l in content.split('\n'))
def fix_spring_ternary(content):
def fix_line(line):
if '?' not in line or line.strip().startswith(('//','*')): return line
# != null
line = re.sub(r'(?<!\()(\b[\w][\w.()]*(?:\(\))?)\s+!=\s+null\s+\?(?!\s*\))',
r'(\1 != null) ?', line)
# == null with return (swap branches)
def swap(m):
p,v,tb,eb = m.group(1),m.group(2),m.group(3).strip(),m.group(4).strip()
sfx = ';' if eb.endswith(';') else ''
if sfx: eb = eb[:-1].rstrip()
return f'{p}({v} != null) ? {eb} : {tb}{sfx}'
line = re.sub(r'(\breturn\s+)(\w+)\s+==\s+null\s+\?\s+([\w".\(\)]+)\s*:\s*([\w".\(\)]+\s*;?)', swap, line)
# instanceof
line = re.sub(r'(?<!\()(\b[\w][\w.]*)\s+(instanceof\s+[\w<>?.]+(?:\s+\w+)?)\s+\?(?!\s*\))',
r'(\1 \2) ?', line)
# simple comparison <
line = re.sub(r'(?<!\()(\b\w+)\s+(<)\s+([\w.]+)\s+\?(?!\s*\))', r'(\1 \2 \3) ?', line)
return line
return '\n'.join(fix_line(l) for l in content.split('\n'))
def fix_star_imports(content):
lines = content.split('\n')
imp_lines = [i for i,l in enumerate(lines) if l.strip().startswith('import ')]
if not imp_lines: return content
s, e = imp_lines[0], imp_lines[-1]
body = '\n'.join(lines[:s] + lines[e+1:])
new_imps = []
for i in range(s, e+1):
stripped = lines[i].strip()
ms = re.match(r'^import\s+static\s+([\w.]+)\.\*\s*;', stripped)
if ms:
pkg = ms.group(1)
if pkg in STATIC_STAR_IMPORTS:
for m in STATIC_STAR_IMPORTS[pkg]:
if re.search(r'\b' + re.escape(m) + r'\s*\(', body):
new_imps.append(f'import static {pkg}.{m};')
else: new_imps.append(lines[i])
continue
mr = re.match(r'^import\s+([\w.]+)\.\*\s*;', stripped)
if mr:
pkg = mr.group(1)
classes = REGULAR_STAR_IMPORTS.get(pkg)
if classes is None and pkg == 'io.kubernetes.client.openapi.models':
used = sorted(set(re.findall(r'\bV1[A-Z]\w+\b', body)))
for cls in used: new_imps.append(f'import {pkg}.{cls};')
elif classes is not None:
for cls in classes:
if re.search(r'\b' + re.escape(cls) + r'\b', body):
new_imps.append(f'import {pkg}.{cls};')
else: new_imps.append(lines[i])
continue
new_imps.append(lines[i])
seen, deduped = set(), []
for imp in new_imps:
if imp.strip() not in seen:
seen.add(imp.strip()); deduped.append(imp.strip())
return '\n'.join(lines[:s] + deduped + lines[e+1:])
def process(src_dirs):
for src_dir in src_dirs:
for root, dirs, files in os.walk(src_dir):
dirs[:] = [d for d in dirs if not d.startswith('.')]
for fname in files:
if not fname.endswith('.java'): continue
fp = os.path.join(root, fname)
with open(fp) as f: orig = f.read()
c = orig
c = fix_spring_catch(c)
c = fix_need_braces(c)
c = fix_lambda_blocks(c)
c = fix_lambda_parens(c)
c = fix_spring_ternary(c)
if '.*' in c: c = fix_star_imports(c)
if c != orig:
with open(fp, 'w') as f: f.write(c)
print(f' Fixed: {fp}')
if __name__ == '__main__':
base = Path('/Users/alex.mondshain/claude/jhelm')
process([str(base / m / 'src') for m in ['jhelm-core','jhelm-kube','jhelm-app','jhelm-gotemplate']])
Step 4: Handle special violations manually
| Violation | Fix |
|-----------|-----|
| InnerTypeLast | Move inner classes AFTER all methods |
| SpringHideUtilityClassConstructor | Add private Constructor() {} AND make class final |
| FinalClass | Add final to class declaration |
| NestedIfDepth > 3 | Extract helper method or add to checkstyle-suppressions.xml |
| SpringMethodVisibility (interface impl) | Add suppression to checkstyle-suppressions.xml |
| AnnotationUseStyle trailing comma | Remove , before }) in @CsvSource etc. |
Step 5: Re-format and validate
./mvnw spring-javaformat:apply && ./mvnw validate
Critical Pitfalls
- Lambda parens vs switch patterns:
case Boolean b -> b— do NOT add parens to switch pattern variables. Check if preceded by uppercase type name orcasekeyword. - Lambda parens vs switch default:
default -> value— do NOT add parens.defaultis a Java keyword. - Ternary in lambda body:
(args) -> condition ? a : b— the->arrow is a boundary; the condition starts AFTER->. Do NOT wrap the lambda params in the ternary condition. ==vs=in find_condition_start: When scanning backward for ternary condition start,==at positionihasline[i-1]='='— skip it. ANDline[i+1]='='also means it's==, skip.- Star import for
removeIf(r ->):ris preceded by(from the method call, NOT from lambda parens. Check if char AFTER identifier is)to detect already-parenthesized. - Missing imports after star import expansion: Manually check for
Base64,BufferedInputStream,VersionInfo, etc. that may not be in the predefined class lists.
Suppressions file (checkstyle-suppressions.xml)
Located at project root. Current suppressions:
SpringHeader,SpringTestFileName,JavadocPackage— project conventions differRegexpSinglelineJava— JUnit 5 assertions used instead of AssertJSpringImportOrder— handled byspring-javaformat:applyRequireThis— not enforced in this project- All
Javadoc*andSpringJavadoc— not required SpringMethodVisibilityforKpsComparisonTest.java— TrustManager interface requires publicNestedIfDepthforRepoManager.java— intentional deep YAML parsing
Next.js App Router Expert
Development
A skill that turns Claude into a Next.js App Router expert.
README Generator
Development
Creates professional and comprehensive README.md files for your projects.
API Documentation Writer
Development
Generates comprehensive API documentation in OpenAPI/Swagger format.