package gust import ( "bufio" "regexp" "strings" ) var ( errorWithColumn = regexp.MustCompile(`(?:\S+: )?([^:]+):(\d+):(\d+): (.+)`) errorWithoutColumn = regexp.MustCompile(`(?:\S+: )?([^:]+):(\d+): (.+)`) cantLoadPackage = regexp.MustCompile(`can't load package: (.+)`) continuation = regexp.MustCompile(`^\s+(.+)`) ) type CompilerMessage struct { Type string // "error" or "warning" Location Location Message string // error message Raw string // raw message Priority int // for sorting Context Lines } type Location struct { File string Line string Column string } func Parse(output string) ([]CompilerMessage, []string) { var messages []CompilerMessage var currentMultilineMsg *CompilerMessage var extra []string scanner := bufio.NewScanner(strings.NewReader(output)) for scanner.Scan() { line := scanner.Text() if strings.TrimSpace(line) == "" { continue } if strings.HasPrefix(line, "#") { extra = append(extra, line) continue } if strings.Contains(line, "panic:") { extra = append(extra, line) continue } if strings.HasPrefix(line, "go:") { msg := CompilerMessage{ Type: "error", Location: Location{}, Message: strings.TrimPrefix(line, "go: "), Raw: line, Priority: 999, } messages = append(messages, msg) } if currentMultilineMsg != nil && continuation.MatchString(line) { matches := continuation.FindStringSubmatch(line) if len(matches) >= 2 { currentMultilineMsg.Message += "\n" + matches[1] continue } } currentMultilineMsg = nil if matches := errorWithColumn.FindStringSubmatch(line); len(matches) >= 5 { msg := CompilerMessage{ Type: determineType(matches[4]), Location: Location{ File: matches[1], Line: matches[2], Column: matches[3], }, Message: matches[4], Raw: line, Priority: priorityFor(matches[4]), } messages = append(messages, msg) currentMultilineMsg = &messages[len(messages)-1] continue } if matches := errorWithoutColumn.FindStringSubmatch(line); len(matches) >= 4 { msg := CompilerMessage{ Type: determineType(matches[3]), Location: Location{ File: matches[1], Line: matches[2], }, Message: matches[3], Raw: line, Priority: priorityFor(matches[3]), } messages = append(messages, msg) currentMultilineMsg = &messages[len(messages)-1] continue } if matches := cantLoadPackage.FindStringSubmatch(line); len(matches) >= 2 { msg := CompilerMessage{ Type: "error", Message: "can't load package: " + matches[1], Raw: line, Priority: 1, // High priority error } messages = append(messages, msg) currentMultilineMsg = &messages[len(messages)-1] continue } if strings.Contains(line, "error") || strings.Contains(line, "failed to build") { msg := CompilerMessage{ Type: "error", Message: line, Raw: line, Priority: 10, // Lower priority for generic errors } messages = append(messages, msg) currentMultilineMsg = &messages[len(messages)-1] } } return messages, extra } func determineType(message string) string { lowercase := strings.ToLower(message) if strings.Contains(lowercase, "error") { return "error" } else if strings.Contains(lowercase, "warning") || strings.Contains(lowercase, "vet") { return "warning" } return "error" } func priorityFor(message string) int { lowercase := strings.ToLower(message) if strings.Contains(lowercase, "warning") { return 0 // Warnings first } else if strings.Contains(lowercase, "syntax error") { return 1 // Syntax errors next } else if strings.Contains(lowercase, "undefined") { return 2 // Undefined references next } else { return 5 // Other errors } }