From f7ae58b5073d343a7dab76d113a021fa2280895e Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Thu, 29 Jan 2026 21:22:06 +0000 Subject: [PATCH] Add validators module --- src/validators/best_practices.py | 124 +++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 src/validators/best_practices.py diff --git a/src/validators/best_practices.py b/src/validators/best_practices.py new file mode 100644 index 0000000..3462c06 --- /dev/null +++ b/src/validators/best_practices.py @@ -0,0 +1,124 @@ +"""Best practices validation module.""" + +from typing import List, Optional + +from ..models import Rule, Finding + + +class BestPracticesValidator: + """Validates shell scripts for best practices.""" + + def __init__(self, custom_rules: Optional[List[Rule]] = None): + """Initialize the best practices validator with optional custom rules.""" + self.rules = self.DEFAULT_PATTERNS.copy() + if custom_rules: + self.rules.extend(custom_rules) + + DEFAULT_PATTERNS = [ + Rule( + id="BEST001", + name="Missing set -e", + pattern=r"^[^#!]*set\s+-e", + severity="medium", + message="Script missing 'set -e' for automatic error handling", + suggestion="Add 'set -e' at the beginning of the script to exit on errors", + ), + Rule( + id="BEST002", + name="Missing set -u", + pattern=r"set\s+-[aeu]", + severity="medium", + message="Script missing 'set -u' to catch undefined variables", + suggestion="Add 'set -u' to catch usage of undefined variables", + ), + Rule( + id="BEST003", + name="Missing set -o pipefail", + pattern=r"set\s+-o\s+pipefail", + severity="medium", + message="Script missing 'set -o pipefail' for proper pipeline error handling", + suggestion="Add 'set -o pipefail' to capture errors in pipelines", + ), + Rule( + id="BEST004", + name="No error handling for command", + pattern=r"^(?!.*(\|\||\&\&|;|exit\s+\d+)).*$", + severity="medium", + message="Command without error handling", + suggestion="Add error handling with '|| exit 1' or '&&' operator", + ), + Rule( + id="BEST005", + name="Dangerous cp without -i", + pattern=r"cp\s+(?!.*-i)(?!.*--interactive)\s+\S+\s+\S+", + severity="medium", + message="cp without -i can overwrite files without warning", + suggestion="Use 'cp -i' for interactive mode or 'cp -n' for no-clobber", + ), + Rule( + id="BEST006", + name="Dangerous mv without -i", + pattern=r"mv\s+(?!.*-i)(?!.*--interactive)\s+\S+\s+\S+", + severity="medium", + message="mv without -i can overwrite files without warning", + suggestion="Use 'mv -i' for interactive mode or 'mv -n' for no-clobber", + ), + Rule( + id="STYLE001", + name="Use of deprecated backticks", + pattern=r"`[^`]+`", + severity="low", + message="Backticks for command substitution are deprecated", + suggestion="Use $() instead of backticks for command substitution", + ), + Rule( + id="STYLE002", + name="Long line detected", + pattern=r"^.{121,}$", + severity="low", + message="Line exceeds 120 characters", + suggestion="Consider breaking long lines for better readability", + ), + Rule( + id="STYLE003", + name="Use of cat with grep", + pattern=r"cat\s+\S+\s*\|\s*grep", + severity="low", + message="Useless use of cat", + suggestion="Use grep directly on the file instead of piping cat output", + ), + Rule( + id="STYLE004", + name="Missing shebang", + pattern=r"^[^#!].*$", + severity="low", + message="Script missing shebang line", + suggestion="Add appropriate shebang like #!/bin/bash or #!/usr/bin/env bash", + ), + ] + + def validate(self, content: str, line_number: Optional[int] = None) -> List[Finding]: + """Validate content for best practices.""" + findings = [] + for rule in self.rules: + if not rule.enabled: + continue + matches = rule._compiled_pattern.finditer(content) if rule._compiled_pattern else [] + for match in matches: + finding = Finding.from_match(rule, match, line_number, content) + findings.append(finding) + return findings + + def validate_lines(self, lines: List[str]) -> List[Finding]: + """Validate multiple lines, tracking line numbers.""" + findings = [] + for line_number, line in enumerate(lines, start=1): + line_findings = self.validate(line, line_number) + for finding in line_findings: + finding.context = line + findings.extend(line_findings) + return findings + + def check(self, command: str) -> List[Finding]: + """Check a single command for best practices issues.""" + return self.validate(command)