-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Description
The following is a bash program to solve golangci-lint errors/warnings one by one by using --json flag and jq.
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
MAGENTA='\033[0;35m'
NC='\033[0m' # No Color
# Configuration
DRY_RUN=false
FILTER_LINTER=""
FILTER_SEVERITY=""
FILTER_FILE=""
MAX_ISSUES=0
DELAY_BETWEEN_CALLS=1
# Function to print colored messages
log_info() {
echo -e "${BLUE}[INFO]${NC} $*" >&2
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $*" >&2
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $*" >&2
}
log_error() {
echo -e "${RED}[ERROR]${NC} $*" >&2
}
log_debug() {
echo -e "${MAGENTA}[DEBUG]${NC} $*" >&2
}
# Function to show usage
show_usage() {
cat << EOF
Usage: $0 [OPTIONS]
A script to automatically fix golangci-lint issues using zlaude.
OPTIONS:
-h, --help Show this help message
-d, --dry-run Show what would be done without calling zlaude
-l, --linter LINTER Filter issues by specific linter (e.g., errcheck, revive)
-s, --severity LEVEL Filter issues by severity (debug, error, warning)
-f, --file PATTERN Filter issues by file pattern (e.g., "pkg/nar/*.go")
-m, --max-issues N Process at most N issues (default: all)
-w, --wait SECONDS Delay between zlaude calls in seconds (default: 1)
--list-linters List all linters found in the output and exit
--list-files List all files with issues and exit
EXAMPLES:
# Process all issues
$0
# Dry run to see what would be fixed
$0 --dry-run
# Fix only errcheck issues
$0 --linter errcheck
# Fix only error-severity issues in pkg/nar/
$0 --severity error --file "pkg/nar/*"
# Process first 5 issues with 2 second delay
$0 --max-issues 5 --wait 2
EOF
}
# Parse command line arguments
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_usage
exit 0
;;
-d|--dry-run)
DRY_RUN=true
shift
;;
-l|--linter)
FILTER_LINTER="$2"
shift 2
;;
-s|--severity)
FILTER_SEVERITY="$2"
shift 2
;;
-f|--file)
FILTER_FILE="$2"
shift 2
;;
-m|--max-issues)
MAX_ISSUES="$2"
shift 2
;;
-w|--wait)
DELAY_BETWEEN_CALLS="$2"
shift 2
;;
--list-linters)
list_linters
exit 0
;;
--list-files)
list_files
exit 0
;;
*)
log_error "Unknown option: $1"
show_usage
exit 1
;;
esac
done
}
# Check dependencies
check_dependencies() {
if ! command -v jq &> /dev/null; then
log_error "jq is required but not installed. Please install jq first."
exit 1
fi
if ! command -v zlaude &> /dev/null && [ "$DRY_RUN" = false ]; then
log_error "zlaude is required but not found in PATH."
exit 1
fi
}
# Get golangci-lint output (FIRST LINE only)
get_lint_output() {
log_info "Running golangci-lint..."
local lint_output=""
# Try standard JSON output format and get first line
if ! lint_output=$(golangci-lint run . --out-format json:stdout 2>&1 | head -n 1); then
log_info "golangci-lint completed with issues found"
fi
# If the above doesn't produce valid JSON, try alternative format
if [ -z "$lint_output" ] || ! echo "$lint_output" | jq empty 2>/dev/null; then
log_info "Trying alternative command format..."
if ! lint_output=$(golangci-lint run . --output.json.path stdout 2>&1 | head -n 1); then
log_info "golangci-lint completed"
fi
fi
if ! echo "$lint_output" | jq empty 2>/dev/null; then
log_error "Failed to parse golangci-lint output as JSON"
log_error "First line of output: $lint_output"
exit 1
fi
echo "$lint_output"
}
# List all linters in the output
list_linters() {
local output=$(get_lint_output)
log_info "Linters found in output:"
while IFS= read -r linter; do
local count=$(echo "$output" | jq "[.Issues[] | select(.FromLinter == \"$linter\")] | length")
echo " - $linter ($count issues)" >&2
done < <(echo "$output" | jq -r '.Issues[].FromLinter' | sort -u)
}
# List all files with issues
list_files() {
local output=$(get_lint_output)
log_info "Files with issues:"
while IFS= read -r file; do
local count=$(echo "$output" | jq "[.Issues[] | select(.Pos.Filename == \"$file\")] | length")
echo " - $file ($count issues)" >&2
done < <(echo "$output" | jq -r '.Issues[].Pos.Filename' | sort -u)
}
# Filter issues based on criteria
filter_issues() {
local issues="$1"
# Apply linter filter
if [ -n "$FILTER_LINTER" ]; then
log_info "Filtering by linter: $FILTER_LINTER"
issues=$(echo "$issues" | jq --arg linter "$FILTER_LINTER" '.Issues = [.Issues[] | select(.FromLinter == $linter)]')
fi
# Apply severity filter
if [ -n "$FILTER_SEVERITY" ]; then
log_info "Filtering by severity: $FILTER_SEVERITY"
issues=$(echo "$issues" | jq --arg severity "$FILTER_SEVERITY" '.Issues = [.Issues[] | select(.Severity == $severity)]')
fi
# Apply file filter
if [ -n "$FILTER_FILE" ]; then
log_info "Filtering by file pattern: $FILTER_FILE"
# Convert shell glob to regex
local pattern="${FILTER_FILE//\*/.*}"
issues=$(echo "$issues" | jq --arg pattern "$pattern" '.Issues = [.Issues[] | select(.Pos.Filename | test($pattern))]')
fi
# Apply max issues limit
if [ "$MAX_ISSUES" -gt 0 ]; then
log_info "Limiting to first $MAX_ISSUES issues"
issues=$(echo "$issues" | jq --argjson max "$MAX_ISSUES" '.Issues = .Issues[:$max]')
fi
echo "$issues"
}
# Sanitize problem text for shell command
sanitize_problem() {
local text="$1"
text=$(echo "$text" | tr '\n' ' ')
text="${text//\"/\\\"}"
text=$(echo "$text" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
echo "$text"
}
# Process a single issue
process_issue() {
local issue_json="$1"
local issue_num="$2"
local total_issues="$3"
local linter=$(echo "$issue_json" | jq -r '.FromLinter')
local text=$(echo "$issue_json" | jq -r '.Text')
local filename=$(echo "$issue_json" | jq -r '.Pos.Filename')
local line=$(echo "$issue_json" | jq -r '.Pos.Line')
local column=$(echo "$issue_json" | jq -r '.Pos.Column')
local severity=$(echo "$issue_json" | jq -r '.Severity')
log_info "Issue [$issue_num/$total_issues]"
echo " Linter: $linter" >&2
echo " File: $filename:$line:$column" >&2
echo " Severity: $severity" >&2
echo " Problem: $text" >&2
echo "" >&2
local sanitized_problem=$(sanitize_problem "$text")
local problem_context="In file $filename at line $line, column $column: $sanitized_problem (detected by $linter)"
local PROMPT="fix this problem: $filename:$line:$column $text"
if [ "$DRY_RUN" = true ]; then
zlaude < <(echo "$PROMPT")
log_warning "[DRY RUN] Would call: zlaude \"$PROMPT\""
else
log_info "Calling zlaude..."
if zlaude "$PROMPT"; then
log_success "zlaude completed for issue $issue_num"
else
log_warning "zlaude failed or returned non-zero exit code for issue $issue_num"
fi
fi
echo "" >&2
echo "---" >&2
echo "" >&2
}
# Main script
main() {
check_dependencies
local lint_output=$(get_lint_output)
local filtered_output=$(filter_issues "$lint_output")
local total_issues=$(echo "$filtered_output" | jq '.Issues | length')
if [ "$total_issues" -eq 0 ]; then
log_success "No issues found matching your criteria! Code is clean."
exit 0
fi
log_info "Found $total_issues issues to process"
local counter=1
while IFS= read -r issue; do
process_issue "$issue" "$counter" "$total_issues"
counter=$((counter + 1))
if [ "$counter" -le "$total_issues" ] && [ "$DRY_RUN" = false ]; then
sleep "$DELAY_BETWEEN_CALLS"
fi
done < <(echo "$filtered_output" | jq -c '.Issues[]')
log_success "Finished processing all issues"
}
# Parse arguments and run
parse_args "$@"
mainalso:
type AutoGenerated struct {
Issues []struct {
FromLinter string `json:"FromLinter"`
Text string `json:"Text"`
Severity string `json:"Severity"`
SourceLines []string `json:"SourceLines"`
Pos struct {
Filename string `json:"Filename"`
Offset int `json:"Offset"`
Line int `json:"Line"`
Column int `json:"Column"`
} `json:"Pos"`
ExpectNoLint bool `json:"ExpectNoLint"`
ExpectedNoLintLinter string `json:"ExpectedNoLintLinter"`
SuggestedFixes []struct {
Message string `json:"Message"`
TextEdits []struct {
Pos int `json:"Pos"`
End int `json:"End"`
NewText string `json:"NewText"`
} `json:"TextEdits"`
} `json:"SuggestedFixes,omitempty"`
LineRange struct {
From int `json:"From"`
To int `json:"To"`
} `json:"LineRange,omitempty"`
} `json:"Issues"`
Report struct {
Linters []struct {
Name string `json:"Name"`
Enabled bool `json:"Enabled,omitempty"`
} `json:"Linters"`
} `json:"Report"`
}
``Metadata
Metadata
Assignees
Labels
No labels