## Bash/Shell validator for Validatrix. ## ## Validates Bash/shell scripts, checking structural balance and common issues. import std/[json, strformat, tables] {.warning[UnusedImport]:off.} import ../core/types, ../core/tokenizerbase, ../core/validatorbase import ../tokenizers/bash_tokenizer type BashValidator* = ref object of ValidatorBase ## Validator for Bash/shell scripts. # --------------------------------------------------------------------------- # Construction # --------------------------------------------------------------------------- proc newBashValidator*(source: string, options: ValidationOptions = newValidationOptions(), sourcePath: string = ""): BashValidator = ## Create a new Bash validator. result = BashValidator( source: source, sourcePath: sourcePath, options: options, result: newValidationResult(), startTime: epochTime() ) result.result.flavor = options.flavor # --------------------------------------------------------------------------- # Tokenizer creation # --------------------------------------------------------------------------- method createTokenizer*(self: BashValidator): TokenizerBase = ## Create the Bash tokenizer. result = newBashTokenizer(self.source, self.options) # --------------------------------------------------------------------------- # Structural analysis # --------------------------------------------------------------------------- method analyzeTokens*(self: BashValidator) = ## Analyze Bash-specific patterns. when defined(validatrixDebug): if self.options.debugMode: debugEnter("BASH_ANALYZE", "Analyzing Bash structure") procCall ValidatorBase(self).analyzeTokens() let tokens = self.tokenizer.tokens # Check for unclosed if/fi, do/done, case/esac var keywords: seq[string] = @[] for tok in tokens: if tok.kind == tkKeyword: keywords.add(tok.value) if tok.value in ["if", "case", "for", "while", "until"]: self.result.moduleInfo.functions.add(FunctionInfo( name: tok.value & "_block", kind: "block", position: tok.position )) elif tok.kind == tkComment: self.result.moduleInfo.comments += 1 # Check for fi/done/esac matching var ifCount, forCount, caseCount = 0 var fiCount, doneCount, esacCount = 0 for kw in keywords: case kw of "if": ifCount += 1 of "fi": fiCount += 1 of "for", "while", "until": forCount += 1 of "done": doneCount += 1 of "case": caseCount += 1 of "esac": esacCount += 1 else: discard if ifCount != fiCount: if ifCount > fiCount: self.result.errors.add(newValidationError( esError, &"Unclosed 'if' block: {ifCount} 'if' but {fiCount} 'fi'", ErrUnclosedBlock, newSourcePosition(1, 1, 0), newSourceRange(newSourcePosition(1, 1, 0), newSourcePosition(1, 1, 0)), hint = "Add missing 'fi' to close the if block" )) if forCount != doneCount: if forCount > doneCount: self.result.errors.add(newValidationError( esError, &"Unclosed loop: {forCount} loop starters but {doneCount} 'done'", ErrUnclosedBlock, newSourcePosition(1, 1, 0), newSourceRange(newSourcePosition(1, 1, 0), newSourcePosition(1, 1, 0)), hint = "Add missing 'done' to close the loop" )) if caseCount != esacCount: if caseCount > esacCount: self.result.errors.add(newValidationError( esError, &"Unclosed 'case' block: {caseCount} 'case' but {esacCount} 'esac'", ErrUnclosedBlock, newSourcePosition(1, 1, 0), newSourceRange(newSourcePosition(1, 1, 0), newSourcePosition(1, 1, 0)), hint = "Add missing 'esac' to close the case block" )) when defined(validatrixDebug): if self.options.debugMode: debugLog("BASH_ANALYZE", &"if/fi: {ifCount}/{fiCount}, for/done: {forCount}/{doneCount}, case/esac: {caseCount}/{esacCount}") debugLeave("BASH_ANALYZE") # --------------------------------------------------------------------------- # Registration # --------------------------------------------------------------------------- proc init*() = ## Register the Bash/Shell validator. registerValidator("bash", proc(source: string, options: ValidationOptions, sourcePath: string): ValidatorBase = result = newBashValidator(source, options, sourcePath) ) registerValidator("shell", proc(source: string, options: ValidationOptions, sourcePath: string): ValidatorBase = result = newBashValidator(source, options, sourcePath) ) registerValidator("sh", proc(source: string, options: ValidationOptions, sourcePath: string): ValidatorBase = result = newBashValidator(source, options, sourcePath) ) # Auto-init init()