syntaxis/.claude/guidelines/nushell/audit-nu-scripts.nu

322 lines
10 KiB
Plaintext
Raw Normal View History

#!/usr/bin/env nu
# audit-nu-scripts.nu - Automated Nushell script auditor for 0.109+ compatibility
# Usage: nu .claude/guidelines/nushell/audit-nu-scripts.nu [--fix] [--verbose]
def main [
--fix # Automatically fix simple issues
--verbose # Show detailed output
--output: string = "" # Output report to file
] {
print_header
# Find all Nushell scripts
let scripts = glob **/*.nu | where {|path| not ($path | str starts-with "target/")}
print $"Found ($scripts | length) Nushell script(s) to audit\n"
mut issues = []
mut summary = {
total_scripts: ($scripts | length)
scripts_with_issues: 0
total_issues: 0
critical: 0
warning: 0
info: 0
}
# Audit each script
for script_path in $scripts {
let script_issues = audit_script $script_path $verbose
if ($script_issues | length) > 0 {
$summary.scripts_with_issues = $summary.scripts_with_issues + 1
$summary.total_issues = $summary.total_issues + ($script_issues | length)
# Count by severity
for issue in $script_issues {
match $issue.severity {
"critical" => { $summary.critical = $summary.critical + 1 }
"warning" => { $summary.warning = $summary.warning + 1 }
"info" => { $summary.info = $summary.info + 1 }
_ => {}
}
}
# Add to issues list
$issues = ($issues | append {
script: $script_path
issues: $script_issues
})
}
}
# Print results
print_divider
print_summary $summary
print_divider
if ($issues | length) > 0 {
print "\n📋 DETAILED FINDINGS:\n"
for script_result in $issues {
print_script_issues $script_result.script $script_result.issues
}
}
# Generate report if requested
if $output != "" {
generate_report $issues $summary $output
print $"\n✓ Report saved to: ($output)"
}
# Exit code based on critical issues
if $summary.critical > 0 {
exit 1
}
}
# Audit a single script
def audit_script [path: string, verbose: bool] -> list {
if $verbose {
print $"Auditing: ($path)"
}
mut issues = []
# Read script content
let content = try {
open --raw $path
} catch {
return [{
line: 0
severity: "critical"
category: "file_access"
message: $"Cannot read file: ($path)"
suggestion: "Check file permissions"
}]
}
let lines = $content | lines
# Check 1: Missing shebang
if (($lines | first) !~ "#!/usr/bin/env nu") {
$issues = ($issues | append {
line: 1
severity: "warning"
category: "shebang"
message: "Missing or incorrect shebang"
suggestion: "Add: #!/usr/bin/env nu"
})
}
# Check 2: Old closure syntax (using $in in where/each)
for (idx, line) in ($lines | enumerate) {
let line_num = $idx + 1
# Check for old-style closures
if ($line =~ '\| where \{ \$in') or ($line =~ '\| each \{ \$in') {
$issues = ($issues | append {
line: $line_num
severity: "warning"
category: "closure_syntax"
message: "Old-style closure using $in"
suggestion: "Use explicit parameter: {|x| $x ...} instead of { $in ... }"
})
}
# Check for missing type annotations on exported functions
if ($line =~ 'export def .+\[') and not ($line =~ ': \w+') {
$issues = ($issues | append {
line: $line_num
severity: "info"
category: "type_annotations"
message: "Function parameters missing type annotations"
suggestion: "Add type annotations: [param: type]"
})
}
# Check for unwrap-like patterns (get without -i)
if ($line =~ '\| get [a-zA-Z_]') and not ($line =~ 'get -i') {
if not ($line =~ '\| get \w+ \|') {
# Potentially unsafe get (not in pipeline continuation)
$issues = ($issues | append {
line: $line_num
severity: "info"
category: "null_safety"
message: "Potentially unsafe 'get' without -i flag"
suggestion: "Use 'get -i' for safe access or add error handling"
})
}
}
# Check for hardcoded paths (common anti-pattern)
if ($line =~ '/Users/[^/]+/') or ($line =~ 'C:\\\\') {
$issues = ($issues | append {
line: $line_num
severity: "warning"
category: "hardcoded_paths"
message: "Hardcoded absolute path detected"
suggestion: "Use $env.HOME or relative paths"
})
}
# Check for missing error handling in external commands
if ($line =~ '\^[a-zA-Z_-]+') and not ($line =~ 'try \{') {
# External command not wrapped in try-catch
# Check if previous or next line has try
let has_try = if $idx > 0 {
($lines | get ($idx - 1) | str contains "try")
} else {
false
}
if not $has_try {
$issues = ($issues | append {
line: $line_num
severity: "info"
category: "error_handling"
message: "External command without error handling"
suggestion: "Wrap in try-catch block"
})
}
}
# Check for deprecated mut syntax
if ($line =~ 'let mut ') {
# This is actually fine in 0.109, just noting it
# $issues = append with info if we want to track mutable usage
}
# Check for missing return type annotations
if ($line =~ 'export def .+\]') and not ($line =~ '-> \w+') and not ($line =~ '\{$') {
$issues = ($issues | append {
line: $line_num
severity: "info"
category: "type_annotations"
message: "Function missing return type annotation"
suggestion: "Add return type: ] -> type {"
})
}
}
# Check 3: No error handling in main functions
let has_try = $content | str contains "try"
let has_catch = $content | str contains "catch"
if not ($has_try and $has_catch) {
$issues = ($issues | append {
line: 0
severity: "info"
category: "error_handling"
message: "Script may lack error handling (no try-catch found)"
suggestion: "Add try-catch blocks for robust error handling"
})
}
$issues
}
# Print header
def print_header [] {
print ""
print "╔════════════════════════════════════════════════════════╗"
print "║ Nushell 0.109+ Compatibility Audit Tool ║"
print "║ syntaxis Project - Script Quality Checker ║"
print "╚════════════════════════════════════════════════════════╝"
print ""
}
# Print divider
def print_divider [] {
print "─────────────────────────────────────────────────────────"
}
# Print summary
def print_summary [summary: record] {
print "\n📊 AUDIT SUMMARY:\n"
print $" Total scripts: ($summary.total_scripts)"
print $" Scripts with issues: ($summary.scripts_with_issues)"
print $" Total issues: ($summary.total_issues)"
print ""
print " By Severity:"
print $" (ansi red)● Critical: ($summary.critical)(ansi reset)"
print $" (ansi yellow)● Warnings: ($summary.warning)(ansi reset)"
print $" (ansi cyan)● Info: ($summary.info)(ansi reset)"
}
# Print issues for a script
def print_script_issues [script: string, issues: list] {
print $"\n(ansi cyan)📄 ($script)(ansi reset)"
for issue in $issues {
let severity_icon = match $issue.severity {
"critical" => $"(ansi red)✗(ansi reset)"
"warning" => $"(ansi yellow)⚠(ansi reset)"
"info" => $"(ansi cyan)(ansi reset)"
_ => "•"
}
let line_info = if $issue.line > 0 {
$" (Line ($issue.line))"
} else {
""
}
print $" ($severity_icon) [($issue.category)]($line_info): ($issue.message)"
print $" → ($issue.suggestion)"
}
}
# Generate markdown report
def generate_report [issues: list, summary: record, output_path: string] {
let timestamp = date now | format date "%Y-%m-%d %H:%M:%S"
mut report = $"# Nushell Script Audit Report
Generated: ($timestamp)
## Summary
- **Total Scripts**: ($summary.total_scripts)
- **Scripts with Issues**: ($summary.scripts_with_issues)
- **Total Issues**: ($summary.total_issues)
- Critical: ($summary.critical)
- Warnings: ($summary.warning)
- Info: ($summary.info)
## Detailed Findings
"
for script_result in $issues {
$report = $report + $"\n### ($script_result.script)\n\n"
for issue in $script_result.issues {
let line_ref = if $issue.line > 0 {
$" (Line ($issue.line))"
} else {
""
}
$report = $report + $"- **[($issue.severity | str upcase)] ($issue.category)**($line_ref)
- Issue: ($issue.message)
- Suggestion: ($issue.suggestion)
"
}
}
$report = $report + $"\n## Recommendations
1. Review all **critical** issues immediately
2. Address **warnings** before next release
3. Consider **info** items for code quality improvements
4. Follow guidelines in `.claude/guidelines/nushell/NUSHELL_0.109_GUIDELINES.md`
---
*Generated by syntaxis Nushell Audit Tool*
"
$report | save --force $output_path
}
# Run main if executed directly
main