@@ -10,7 +10,7 @@ import (
1010// FixParser extracts and validates code fixes from AI responses.
1111// It provides methods to parse markdown-formatted AI responses, identify which code blocks
1212// represent actual fixes (versus examples or explanations), and validate syntax for different
13- // file types (bash, shell, powershell, markdown, python).
13+ // file types (bash, shell, powershell, markdown, python, go ).
1414//
1515// The parser uses heuristics to distinguish fix blocks from explanatory code:
1616// - Blocks matching the target file type are prioritized
@@ -22,6 +22,7 @@ import (
2222// - PowerShell: Checks for unmatched quotes, brackets, and try/catch/finally blocks
2323// - Markdown: Checks for unmatched code block markers
2424// - Python: Checks for unmatched quotes, brackets, and basic indentation consistency
25+ // - Go: Checks for unmatched brackets and basic go file structure
2526type FixParser struct {
2627 debug bool // Enable debug logging
2728}
@@ -59,43 +60,44 @@ func (p *FixParser) logError(format string, args ...interface{}) {
5960// Returns a slice of CodeBlock structs
6061func (p * FixParser ) ExtractCodeBlocks (response string ) []CodeBlock {
6162 p .logDebug ("Extracting code blocks from response (length: %d chars)" , len (response ))
62-
63+
6364 var blocks []CodeBlock
64-
65+
6566 // Regular expression to match markdown code blocks
6667 // Matches: ```optional-language\ncode content\n```
6768 codeBlockRegex := regexp .MustCompile ("(?s)```([a-zA-Z0-9_+-]*)\n (.*?)```" )
68-
69+
6970 matches := codeBlockRegex .FindAllStringSubmatch (response , - 1 )
7071 p .logDebug ("Found %d potential code blocks" , len (matches ))
71-
72+
7273 for i , match := range matches {
7374 if len (match ) >= 3 {
7475 language := strings .TrimSpace (match [1 ])
7576 code := match [2 ]
76-
77+
7778 // Skip empty code blocks
7879 if strings .TrimSpace (code ) == "" {
7980 p .logDebug ("Skipping empty code block %d" , i + 1 )
8081 continue
8182 }
82-
83+
8384 // Remove trailing newline if present
8485 code = strings .TrimRight (code , "\n " )
85-
86+
8687 p .logDebug ("Extracted code block %d: language=%s, size=%d bytes" , i + 1 , language , len (code ))
87-
88+
8889 blocks = append (blocks , CodeBlock {
8990 Language : language ,
9091 Code : code ,
9192 IsWhole : false , // Will be determined by IdentifyFixBlocks
9293 })
9394 }
9495 }
95-
96+
9697 p .logDebug ("Extracted %d valid code blocks" , len (blocks ))
9798 return blocks
9899}
100+
99101// IdentifyFixBlocks determines which code blocks represent fixes versus explanations
100102// It analyzes the blocks and their context to distinguish actual code fixes from examples
101103// or explanatory code snippets. Returns only the blocks that should be applied as fixes.
@@ -107,7 +109,7 @@ func (p *FixParser) ExtractCodeBlocks(response string) []CodeBlock {
107109// - Blocks with generic/unspecified language may be fixes if they match file content patterns
108110func (p * FixParser ) IdentifyFixBlocks (blocks []CodeBlock , fileType string ) []CodeBlock {
109111 p .logDebug ("Identifying fix blocks for file type: %s (total blocks: %d)" , fileType , len (blocks ))
110-
112+
111113 if len (blocks ) == 0 {
112114 return []CodeBlock {}
113115 }
@@ -130,12 +132,12 @@ func (p *FixParser) IdentifyFixBlocks(blocks []CodeBlock, fileType string) []Cod
130132 lineCount := countLines (block .Code )
131133 block .IsWhole = lineCount > 3
132134
133- p .logDebug ("Block %d matches file type (language: %s, lines: %d, isWhole: %v)" ,
135+ p .logDebug ("Block %d matches file type (language: %s, lines: %d, isWhole: %v)" ,
134136 i + 1 , block .Language , lineCount , block .IsWhole )
135-
137+
136138 fixBlocks = append (fixBlocks , block )
137139 } else {
138- p .logDebug ("Block %d does not match file type (language: %s vs %s)" ,
140+ p .logDebug ("Block %d does not match file type (language: %s vs %s)" ,
139141 i + 1 , normalizedLang , normalizedFileType )
140142 }
141143 }
@@ -176,6 +178,8 @@ func normalizeFileType(fileType string) string {
176178 return "markdown"
177179 case "python" , "py" :
178180 return "python"
181+ case "go" :
182+ return "go"
179183 default :
180184 return fileType
181185 }
@@ -195,6 +199,8 @@ func normalizeLanguage(language string) string {
195199 return "markdown"
196200 case "py" , "python" :
197201 return "python"
202+ case "go" , "golang" :
203+ return "go"
198204 default :
199205 return language
200206 }
@@ -244,11 +250,11 @@ func countLines(code string) int {
244250
245251// ValidateFixSyntax performs basic syntax validation on a fix
246252// Validates that the code is syntactically appropriate for the file type
247- // Supports: bash, shell, powershell, markdown, python
253+ // Supports: bash, shell, powershell, markdown, python, go
248254// Returns an error if validation fails, nil if validation passes
249255func (p * FixParser ) ValidateFixSyntax (code string , fileType string ) error {
250256 p .logDebug ("Validating syntax for file type: %s (code length: %d bytes)" , fileType , len (code ))
251-
257+
252258 if strings .TrimSpace (code ) == "" {
253259 p .logError ("Code is empty" )
254260 return fmt .Errorf ("code cannot be empty" )
@@ -269,6 +275,9 @@ func (p *FixParser) ValidateFixSyntax(code string, fileType string) error {
269275 case "python" :
270276 p .logDebug ("Performing Python syntax validation" )
271277 return validatePythonSyntax (code )
278+ case "go" :
279+ p .logDebug ("Performing Go syntax validation" )
280+ return validateGoSyntax (code )
272281 default :
273282 // For unknown file types, perform minimal validation
274283 p .logDebug ("Unknown file type, skipping syntax validation" )
@@ -279,7 +288,7 @@ func (p *FixParser) ValidateFixSyntax(code string, fileType string) error {
279288// validateBashSyntax performs basic bash/shell syntax validation
280289func validateBashSyntax (code string ) error {
281290 // Basic checks for common bash syntax errors
282-
291+
283292 // Check for unmatched quotes
284293 if err := checkUnmatchedQuotes (code ); err != nil {
285294 return fmt .Errorf ("bash syntax error: %w" , err )
@@ -301,7 +310,7 @@ func validateBashSyntax(code string) error {
301310// validatePowerShellSyntax performs basic PowerShell syntax validation
302311func validatePowerShellSyntax (code string ) error {
303312 // Basic checks for common PowerShell syntax errors
304-
313+
305314 // Check for unmatched quotes
306315 if err := checkUnmatchedQuotes (code ); err != nil {
307316 return fmt .Errorf ("powershell syntax error: %w" , err )
@@ -323,7 +332,7 @@ func validatePowerShellSyntax(code string) error {
323332// validateMarkdownSyntax performs basic markdown syntax validation
324333func validateMarkdownSyntax (code string ) error {
325334 // Basic checks for markdown syntax
326-
335+
327336 // Check for unmatched code block markers
328337 backtickCount := strings .Count (code , "```" )
329338 if backtickCount % 2 != 0 {
@@ -394,17 +403,17 @@ func checkUnmatchedBrackets(code string) error {
394403 }
395404
396405 lines := strings .Split (code , "\n " )
397-
406+
398407 for _ , line := range lines {
399408 trimmed := strings .TrimSpace (line )
400-
409+
401410 // Check if we're in a case statement pattern (ends with ')')
402411 // Case patterns look like: pattern) command;;
403412 if strings .Contains (trimmed , ";;" ) {
404413 // This line likely contains case patterns, skip bracket checking for ')' in patterns
405414 inCasePattern = true
406415 }
407-
416+
408417 for _ , ch := range line {
409418 // Skip characters inside quotes
410419 if escaped {
@@ -446,7 +455,7 @@ func checkUnmatchedBrackets(code string) error {
446455 }
447456 continue
448457 }
449-
458+
450459 if len (stack ) == 0 {
451460 return fmt .Errorf ("unmatched closing bracket: %c" , ch )
452461 }
@@ -460,7 +469,7 @@ func checkUnmatchedBrackets(code string) error {
460469 }
461470 }
462471 }
463-
472+
464473 // Reset case pattern flag at end of line
465474 inCasePattern = false
466475 }
@@ -476,7 +485,7 @@ func checkUnmatchedBrackets(code string) error {
476485func checkBashControlStructures (code string ) error {
477486 // Count control structure keywords
478487 lines := strings .Split (code , "\n " )
479-
488+
480489 ifCount := 0
481490 fiCount := 0
482491 doCount := 0
@@ -486,7 +495,7 @@ func checkBashControlStructures(code string) error {
486495
487496 for _ , line := range lines {
488497 trimmed := strings .TrimSpace (line )
489-
498+
490499 // Skip comments
491500 if strings .HasPrefix (trimmed , "#" ) {
492501 continue
@@ -533,16 +542,16 @@ func checkBashControlStructures(code string) error {
533542func checkPowerShellControlStructures (code string ) error {
534543 // PowerShell uses braces for control structures, which are already checked
535544 // by checkUnmatchedBrackets. We can add more specific checks here if needed.
536-
545+
537546 // For now, we'll just ensure basic structure
538547 // PowerShell is case-insensitive, so we normalize to lowercase
539548 lowerCode := strings .ToLower (code )
540-
549+
541550 // Check for incomplete try/catch/finally blocks
542551 tryCount := strings .Count (lowerCode , "try" )
543552 catchCount := strings .Count (lowerCode , "catch" )
544553 finallyCount := strings .Count (lowerCode , "finally" )
545-
554+
546555 // Try blocks should have at least one catch or finally
547556 if tryCount > 0 && (catchCount + finallyCount ) == 0 {
548557 return fmt .Errorf ("try block without catch or finally" )
@@ -554,7 +563,7 @@ func checkPowerShellControlStructures(code string) error {
554563// validatePythonSyntax performs basic Python syntax validation
555564func validatePythonSyntax (code string ) error {
556565 // Basic checks for common Python syntax errors
557-
566+
558567 // Check for unmatched quotes
559568 if err := checkUnmatchedQuotes (code ); err != nil {
560569 return fmt .Errorf ("python syntax error: %w" , err )
@@ -576,39 +585,54 @@ func validatePythonSyntax(code string) error {
576585// checkPythonIndentation performs basic Python indentation validation
577586func checkPythonIndentation (code string ) error {
578587 lines := strings .Split (code , "\n " )
579-
588+
580589 // Track if we're expecting an indented block
581590 expectIndent := false
582591 prevIndent := 0
583-
592+
584593 for i , line := range lines {
585594 trimmed := strings .TrimSpace (line )
586-
595+
587596 // Skip empty lines and comments
588597 if trimmed == "" || strings .HasPrefix (trimmed , "#" ) {
589598 continue
590599 }
591-
600+
592601 // Calculate indentation level
593602 indent := len (line ) - len (strings .TrimLeft (line , " \t " ))
594-
603+
595604 // Check if line ends with colon (starts a block)
596605 if strings .HasSuffix (trimmed , ":" ) {
597606 expectIndent = true
598607 prevIndent = indent
599608 continue
600609 }
601-
610+
602611 // If we expected indentation, check that it increased
603612 if expectIndent {
604613 if indent <= prevIndent {
605614 return fmt .Errorf ("expected indented block after line %d" , i )
606615 }
607616 expectIndent = false
608617 }
609-
618+
610619 prevIndent = indent
611620 }
612-
621+
622+ return nil
623+ }
624+
625+ // validateGoSyntax performs basic Go syntax validation
626+ func validateGoSyntax (code string ) error {
627+ // Check for unmatched quotes
628+ if err := checkUnmatchedQuotes (code ); err != nil {
629+ return fmt .Errorf ("go syntax error: %w" , err )
630+ }
631+
632+ // Check for unmatched brackets/parentheses
633+ if err := checkUnmatchedBrackets (code ); err != nil {
634+ return fmt .Errorf ("go syntax error: %w" , err )
635+ }
636+
613637 return nil
614638}
0 commit comments