@@ -36,6 +36,7 @@ type LineError struct {
3636 Code string
3737 Severity string
3838 Message string
39+ Column int
3940}
4041
4142func getEnvOrDefault (key , defaultValue string ) string {
@@ -147,22 +148,31 @@ func parseShellcheckOutput(output string) []Annotation {
147148 lines := regexp .MustCompile (`\r?\n` ).Split (output , - 1 )
148149 scCodeRegex := regexp .MustCompile (`(SC\d+)\s+\((error|warning|info|style)\):\s*(.+)` )
149150 lineRegex := regexp .MustCompile (`\bline\s+(\d+):` )
151+ columnRegex := regexp .MustCompile (`^(\s*)\^` )
150152
151153 var currentLine int
154+ var currentColumn int
152155 for _ , line := range lines {
153156 // Extract line number
154- if lineMatch := lineRegex .FindStringSubmatch (line ); lineMatch != nil && len (lineMatch ) >= 2 {
157+ if lineMatch := lineRegex .FindStringSubmatch (line ); len (lineMatch ) >= 2 {
155158 if num , err := strconv .Atoi (lineMatch [1 ]); err == nil {
156159 currentLine = num
160+ currentColumn = 0
157161 }
158162 }
159163
164+ // Extract column position from ^-- marker
165+ if colMatch := columnRegex .FindStringSubmatch (line ); len (colMatch ) > 1 {
166+ currentColumn = len (colMatch [1 ])
167+ }
168+
160169 // Extract error code and message
161- if scMatch := scCodeRegex .FindStringSubmatch (line ); scMatch != nil && currentLine > 0 && len (scMatch ) >= 4 {
170+ if scMatch := scCodeRegex .FindStringSubmatch (line ); currentLine > 0 && len (scMatch ) >= 4 {
162171 lineErrors [currentLine ] = append (lineErrors [currentLine ], LineError {
163172 Code : scMatch [1 ],
164173 Severity : scMatch [2 ],
165174 Message : scMatch [3 ],
175+ Column : currentColumn ,
166176 })
167177 }
168178 }
@@ -175,6 +185,7 @@ func parseShellcheckOutput(output string) []Annotation {
175185
176186 // Determine annotation type based on most severe error
177187 annotationType := "info"
188+ column := 0
178189 for _ , err := range errors {
179190 if err .Severity == "error" {
180191 annotationType = "error"
@@ -184,6 +195,11 @@ func parseShellcheckOutput(output string) []Annotation {
184195 }
185196 }
186197
198+ // Use column from first error (they should all be the same for a given line)
199+ if len (errors ) > 0 {
200+ column = errors [0 ].Column
201+ }
202+
187203 // Build combined error message with one line per issue
188204 var messages []string
189205 for _ , err := range errors {
@@ -192,7 +208,7 @@ func parseShellcheckOutput(output string) []Annotation {
192208
193209 annotations = append (annotations , Annotation {
194210 Row : lineNum - 1 , // Ace uses 0-based indexing
195- Column : 0 ,
211+ Column : column ,
196212 Text : strings .Join (messages , "\n " ),
197213 Type : annotationType ,
198214 })
@@ -245,8 +261,8 @@ func formatShellcheckHTML(output string) string {
245261 formatted = regexp .MustCompile (`(?m)^(.+SC\d+.+\(info\):.+)$` ).ReplaceAllString (formatted , `<span class="text-blue-400">$1</span>` )
246262 formatted = regexp .MustCompile (`(?m)^(.+SC\d+.+\(style\):.+)$` ).ReplaceAllString (formatted , `<span class="text-green-400">$1</span>` )
247263
248- // Color line numbers (now just "Line X:")
249- formatted = regexp .MustCompile (`(?m)^Line (\d+):` ).ReplaceAllString (formatted , `<span class="text-cyan-400" >Line $1:</span >` )
264+ // Color line numbers and make them clickable (now just "Line X:")
265+ formatted = regexp .MustCompile (`(?m)^Line (\d+):` ).ReplaceAllString (formatted , `<a href="#" class="line-link text-cyan-400 hover:text-cyan-300 cursor-pointer underline" data-line="$1" >Line $1:</a >` )
250266
251267 return fmt .Sprintf (`<pre class="text-xs whitespace-pre-wrap font-mono">%s</pre>` , formatted )
252268}
0 commit comments