1package gust
2
3import (
4 "bufio"
5 "regexp"
6 "strings"
7)
8
9var (
10 errorWithColumn = regexp.MustCompile(`(?:\S+: )?([^:]+):(\d+):(\d+): (.+)`)
11 errorWithoutColumn = regexp.MustCompile(`(?:\S+: )?([^:]+):(\d+): (.+)`)
12 cantLoadPackage = regexp.MustCompile(`can't load package: (.+)`)
13 continuation = regexp.MustCompile(`^\s+(.+)`)
14)
15
16type CompilerMessage struct {
17 Type string // "error" or "warning"
18 Location Location
19 Message string // error message
20 Raw string // raw message
21 Priority int // for sorting
22 Context Lines
23}
24
25type Location struct {
26 File string
27 Line string
28 Column string
29}
30
31func Parse(output string) ([]CompilerMessage, []string) {
32 var messages []CompilerMessage
33 var currentMultilineMsg *CompilerMessage
34 var extra []string
35
36 scanner := bufio.NewScanner(strings.NewReader(output))
37 for scanner.Scan() {
38 line := scanner.Text()
39
40 if strings.TrimSpace(line) == "" {
41 continue
42 }
43
44 if strings.HasPrefix(line, "#") {
45 extra = append(extra, line)
46 continue
47 }
48
49 if strings.Contains(line, "panic:") {
50 extra = append(extra, line)
51 continue
52 }
53
54 if strings.HasPrefix(line, "go:") {
55 msg := CompilerMessage{
56 Type: "error",
57 Location: Location{},
58 Message: strings.TrimPrefix(line, "go: "),
59 Raw: line,
60 Priority: 999,
61 }
62
63 messages = append(messages, msg)
64 }
65
66 if currentMultilineMsg != nil && continuation.MatchString(line) {
67 matches := continuation.FindStringSubmatch(line)
68 if len(matches) >= 2 {
69 currentMultilineMsg.Message += "\n" + matches[1]
70 continue
71 }
72 }
73
74 currentMultilineMsg = nil
75
76 if matches := errorWithColumn.FindStringSubmatch(line); len(matches) >= 5 {
77 msg := CompilerMessage{
78 Type: determineType(matches[4]),
79 Location: Location{
80 File: matches[1],
81 Line: matches[2],
82 Column: matches[3],
83 },
84 Message: matches[4],
85 Raw: line,
86 Priority: priorityFor(matches[4]),
87 }
88
89 messages = append(messages, msg)
90 currentMultilineMsg = &messages[len(messages)-1]
91 continue
92 }
93
94 if matches := errorWithoutColumn.FindStringSubmatch(line); len(matches) >= 4 {
95 msg := CompilerMessage{
96 Type: determineType(matches[3]),
97 Location: Location{
98 File: matches[1],
99 Line: matches[2],
100 },
101 Message: matches[3],
102 Raw: line,
103 Priority: priorityFor(matches[3]),
104 }
105
106 messages = append(messages, msg)
107 currentMultilineMsg = &messages[len(messages)-1]
108 continue
109 }
110
111 if matches := cantLoadPackage.FindStringSubmatch(line); len(matches) >= 2 {
112 msg := CompilerMessage{
113 Type: "error",
114 Message: "can't load package: " + matches[1],
115 Raw: line,
116 Priority: 1, // High priority error
117 }
118
119 messages = append(messages, msg)
120 currentMultilineMsg = &messages[len(messages)-1]
121 continue
122 }
123
124 if strings.Contains(line, "error") || strings.Contains(line, "failed to build") {
125 msg := CompilerMessage{
126 Type: "error",
127 Message: line,
128 Raw: line,
129 Priority: 10, // Lower priority for generic errors
130 }
131
132 messages = append(messages, msg)
133 currentMultilineMsg = &messages[len(messages)-1]
134 }
135 }
136
137 return messages, extra
138}
139
140func determineType(message string) string {
141 lowercase := strings.ToLower(message)
142 if strings.Contains(lowercase, "error") {
143 return "error"
144 } else if strings.Contains(lowercase, "warning") || strings.Contains(lowercase, "vet") {
145 return "warning"
146 }
147 return "error"
148}
149
150func priorityFor(message string) int {
151 lowercase := strings.ToLower(message)
152 if strings.Contains(lowercase, "warning") {
153 return 0 // Warnings first
154 } else if strings.Contains(lowercase, "syntax error") {
155 return 1 // Syntax errors next
156 } else if strings.Contains(lowercase, "undefined") {
157 return 2 // Undefined references next
158 } else {
159 return 5 // Other errors
160 }
161}