mirror of
https://github.com/JamesTheGiblet/BuddAI.git
synced 2026-01-08 21:58:40 +00:00
- Implemented comprehensive unit tests for the BuddAI Analytics module, covering fallback statistics calculations. - Created tests for the FallbackClient to ensure proper escalation to various AI models and handling of missing API keys. - Developed unit tests for the refactored validator system, validating various hardware and coding standards. - Established a base validator interface and implemented specific validators for ESP32, Arduino, motor control, memory safety, and more. - Enhanced the validator registry to auto-discover and manage validators effectively. - Included detailed validation logic for common issues in embedded systems programming, such as unused variables, safety timeouts, and coding style violations.
143 lines
7.6 KiB
Python
143 lines
7.6 KiB
Python
import re
|
|
from . import BaseValidator
|
|
|
|
class StyleValidator(BaseValidator):
|
|
def refactor_loop_to_function(self, code: str) -> str:
|
|
loop_match = re.search(r'void\s+loop\s*\(\s*\)\s*\{', code)
|
|
if not loop_match: return code
|
|
|
|
start_idx = loop_match.end()
|
|
brace_count = 1
|
|
loop_body_end = -1
|
|
|
|
for i, char in enumerate(code[start_idx:], start=start_idx):
|
|
if char == '{': brace_count += 1
|
|
elif char == '}': brace_count -= 1
|
|
|
|
if brace_count == 0:
|
|
loop_body_end = i
|
|
break
|
|
|
|
if loop_body_end == -1: return code
|
|
|
|
body = code[start_idx:loop_body_end]
|
|
new_code = code[:start_idx] + "\n runSystemLogic();\n" + code[loop_body_end:]
|
|
new_code += "\n\nvoid runSystemLogic() {" + body + "}\n"
|
|
return new_code
|
|
|
|
def validate(self, code: str, hardware: str, user_message: str) -> list[dict]:
|
|
issues = []
|
|
|
|
# Check 11: Feature Bloat (Unrequested Button)
|
|
if user_message:
|
|
msg_lower = user_message.lower()
|
|
if not any(w in msg_lower for w in ['button', 'switch', 'input', 'trigger']):
|
|
for match in re.finditer(r'(?:int|bool|byte)\s+(\w*(?:button|btn|switch)\w*)\s*=\s*digitalRead\s*\([^;]+;', code, re.IGNORECASE):
|
|
issues.append({
|
|
"severity": "error",
|
|
"line": self.find_line(code, match.group(0)),
|
|
"message": f"Feature Bloat: Unrequested button code detected ('{match.group(1)}').",
|
|
"fix": lambda c, m=match.group(0): c.replace(m, "")
|
|
})
|
|
|
|
for match in re.finditer(r'digitalRead\s*\(\s*(\w*(?:BUTTON|BTN|SWITCH)\w*)\s*\)', code, re.IGNORECASE):
|
|
issues.append({
|
|
"severity": "error",
|
|
"line": self.find_line(code, match.group(0)),
|
|
"message": f"Feature Bloat: Unrequested button check detected ('{match.group(1)}').",
|
|
"fix": lambda c, m=match.group(0): c.replace(m, "0")
|
|
})
|
|
|
|
for match in re.finditer(r'pinMode\s*\(\s*\w+\s*,\s*INPUT(?:_PULLUP)?\s*\);', code):
|
|
issues.append({
|
|
"severity": "error",
|
|
"line": self.find_line(code, match.group(0)),
|
|
"message": "Feature Bloat: Unrequested input pin configuration.",
|
|
"fix": lambda c, m=match.group(0): c.replace(m, "")
|
|
})
|
|
|
|
for match in re.finditer(r'(?:int|bool|byte)\s+(\w*(?:button|btn|switch)\w*)\s*=\s*(?:LOW|HIGH|0|1|false|true)\s*;', code, re.IGNORECASE):
|
|
issues.append({
|
|
"severity": "error",
|
|
"line": self.find_line(code, match.group(0)),
|
|
"message": f"Feature Bloat: Unused button variable '{match.group(1)}'.",
|
|
"fix": lambda c, m=match.group(0): c.replace(m, "")
|
|
})
|
|
|
|
# Check 15: Function Naming Conventions
|
|
func_defs = re.finditer(r'\b(void|int|bool|float|double|String|char|long|unsigned(?:\s+long)?)\s+([a-zA-Z0-9_]+)\s*\(', code)
|
|
for match in func_defs:
|
|
func_name = match.group(2)
|
|
if func_name in ['setup', 'loop', 'main']: continue
|
|
|
|
if not re.match(r'^[a-z][a-zA-Z0-9]*$', func_name):
|
|
suggestion = func_name
|
|
if '_' in func_name:
|
|
components = func_name.split('_')
|
|
suggestion = components[0].lower() + ''.join(x.title() for x in components[1:])
|
|
elif func_name[0].isupper():
|
|
suggestion = func_name[0].lower() + func_name[1:]
|
|
|
|
issues.append({
|
|
"severity": "warning",
|
|
"line": self.find_line(code, match.group(0)),
|
|
"message": f"Style: Function '{func_name}' should be camelCase (e.g., '{suggestion}').",
|
|
"fix": lambda c, old=func_name, new=suggestion: c.replace(old, new)
|
|
})
|
|
|
|
# Check 16: Monolithic Code Structure
|
|
if "function" in user_message.lower() or "naming" in user_message.lower() or "modular" in user_message.lower():
|
|
has_custom_funcs = False
|
|
for match in re.finditer(r'\b(void|int|bool|float|double|String|char|long|unsigned(?:\s+long)?)\s+([a-zA-Z0-9_]+)\s*\(', code):
|
|
if match.group(2) not in ['setup', 'loop', 'main']:
|
|
has_custom_funcs = True
|
|
break
|
|
|
|
if not has_custom_funcs:
|
|
issues.append({
|
|
"severity": "error",
|
|
"message": "Structure Violation: Request asked for functions but code is monolithic.",
|
|
"fix": lambda c: c.replace("void loop() {", "void loop() {\n runSystemLogic();\n}\n\nvoid runSystemLogic() {") + "\n}"
|
|
})
|
|
|
|
# Check 17: Loop Length
|
|
if "function" in user_message.lower() or "naming" in user_message.lower() or "modular" in user_message.lower():
|
|
loop_match = re.search(r'void\s+loop\s*\(\s*\)\s*\{', code)
|
|
if loop_match:
|
|
start_idx = loop_match.end()
|
|
brace_count = 1
|
|
loop_body = ""
|
|
for char in code[start_idx:]:
|
|
if char == '{': brace_count += 1
|
|
elif char == '}': brace_count -= 1
|
|
if brace_count == 0: break
|
|
loop_body += char
|
|
|
|
lines = [line.strip() for line in loop_body.split('\n')]
|
|
significant_lines = [l for l in lines if l and not l.startswith('//') and not l.startswith('/*') and l != '']
|
|
|
|
if len(significant_lines) >= 10:
|
|
issues.append({
|
|
"severity": "error",
|
|
"message": f"Modularity Violation: loop() has {len(significant_lines)} lines (limit 10). Move logic to functions.",
|
|
"fix": lambda c: self.refactor_loop_to_function(c)
|
|
})
|
|
|
|
# Check 21: Status LED Pattern
|
|
if "status" in user_message.lower() and ("led" in user_message.lower() or "indicator" in user_message.lower()):
|
|
breathing_match = re.search(r'(?:dutyCycle|brightness)\s*(\+=|\+\+|\-=|\-\-)', code)
|
|
if breathing_match:
|
|
issues.append({
|
|
"severity": "error",
|
|
"line": self.find_line(code, breathing_match.group(0)),
|
|
"message": "Wrong Pattern: Status indicators should use Blink Patterns (States), not Breathing/Fading.",
|
|
"fix": lambda c: c + "\n// [Fix Required] Implement setStatusLED(LEDStatus state) instead of fading."
|
|
})
|
|
|
|
if not re.search(r'enum\s+(?:StatusState|LEDStatus)\s*\{', code):
|
|
issues.append({
|
|
"severity": "error",
|
|
"message": "Missing Status Enum: Status LEDs require a state machine (enum LEDStatus {OFF, IDLE, ACTIVE, ERROR}).",
|
|
"fix": lambda c: c.replace("void setup", "\n// [AUTO-FIX] Status Enum\nenum LEDStatus { OFF, IDLE, ACTIVE, ERROR };\nLEDStatus currentStatus = IDLE;\nunsigned long lastBlink = 0;\n\nvoid setup") if "void setup" in c else "// [AUTO-FIX] Status Enum\nenum LEDStatus { OFF, IDLE, ACTIVE, ERROR };\nLEDStatus currentStatus = IDLE;\nunsigned long lastBlink = 0;\n" + c
|
|
})
|
|
return issues
|