Spaces:
Paused
Paused
| package syntax | |
| import ( | |
| "strings" | |
| "time" | |
| shared "plandex-shared" | |
| tree_sitter "github.com/smacker/go-tree-sitter" | |
| "context" | |
| "fmt" | |
| ) | |
| const parserTimeout = 500 * time.Millisecond | |
| type ValidationRes = struct { | |
| Lang shared.Language | |
| Parser *tree_sitter.Parser | |
| TimedOut bool | |
| Valid bool | |
| Errors []string | |
| } | |
| func ValidateFile(ctx context.Context, path string, file string) (*ValidationRes, error) { | |
| parser, lang, fallbackParser, fallbackLang := GetParserForPath(path) | |
| if parser == nil { | |
| return &ValidationRes{Lang: lang, Parser: nil}, nil | |
| } | |
| return ValidateWithParsers(ctx, lang, parser, fallbackLang, fallbackParser, file) | |
| } | |
| func ValidateWithParsers(ctx context.Context, lang shared.Language, parser *tree_sitter.Parser, fallbackLang shared.Language, fallbackParser *tree_sitter.Parser, file string) (*ValidationRes, error) { | |
| if file == "" { | |
| return &ValidationRes{Lang: lang, Parser: parser, Valid: true}, nil | |
| } | |
| // Set a timeout duration for the parsing operations | |
| ctx, cancel := context.WithTimeout(ctx, parserTimeout) | |
| defer cancel() | |
| // Parse the content | |
| tree, err := parser.ParseCtx(ctx, nil, []byte(file)) | |
| if err != nil || tree == nil { | |
| if err != nil && err.Error() == "operation limit was hit" { | |
| return &ValidationRes{Lang: lang, Parser: parser, TimedOut: true}, nil | |
| } | |
| return nil, fmt.Errorf("failed to parse the content: %v", err) | |
| } | |
| defer tree.Close() | |
| // Get the root node of the syntax tree and check for errors | |
| root := tree.RootNode() | |
| if root.HasError() { | |
| if fallbackParser != nil { | |
| fallbackTree, err := fallbackParser.ParseCtx(ctx, nil, []byte(file)) | |
| if err != nil || fallbackTree == nil { | |
| if err != nil && strings.Contains(err.Error(), "timeout") { | |
| return &ValidationRes{Lang: lang, Parser: parser, TimedOut: true}, nil | |
| } | |
| return nil, fmt.Errorf("failed to parse the content with fallback parser: %v", err) | |
| } | |
| defer fallbackTree.Close() | |
| root = fallbackTree.RootNode() | |
| if !root.HasError() { | |
| return &ValidationRes{Lang: fallbackLang, Parser: fallbackParser, Valid: true}, nil | |
| } | |
| } | |
| errorMarkers := insertErrorMarkers(file, root) | |
| return &ValidationRes{ | |
| Lang: lang, | |
| Parser: parser, | |
| Valid: false, | |
| Errors: errorMarkers, | |
| }, nil | |
| } | |
| return &ValidationRes{Lang: lang, Parser: parser, Valid: true}, nil | |
| } | |
| func insertErrorMarkers(source string, node *tree_sitter.Node) []string { | |
| if source == "" { | |
| return []string{} | |
| } | |
| var markers []string | |
| var uniqueMarkers = map[string]bool{} | |
| // Function to calculate line numbers | |
| calculateLineNumber := func(position int) int { | |
| return strings.Count(source[:position], "\n") + 1 | |
| } | |
| hasChildError := func(n *tree_sitter.Node) bool { | |
| for i := 0; i < int(n.ChildCount()); i++ { | |
| if n.Child(i).HasError() { | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| visitNodes(node, func(n *tree_sitter.Node) { | |
| if n.HasError() && !hasChildError(n) { | |
| startPosition := int(n.StartByte()) | |
| endPosition := int(n.EndByte()) | |
| startLineNumber := calculateLineNumber(startPosition) | |
| endLineNumber := calculateLineNumber(endPosition) | |
| if startLineNumber == endLineNumber { | |
| uniqueMarkers[fmt.Sprintf("Invalid syntax on line %d", startLineNumber)] = true | |
| } else { | |
| uniqueMarkers[fmt.Sprintf("Invalid syntax on lines %d to %d", startLineNumber, endLineNumber)] = true | |
| } | |
| } | |
| }) | |
| for marker := range uniqueMarkers { | |
| markers = append(markers, marker) | |
| } | |
| return markers | |
| } | |
| // visitNodes recursively visits nodes in the syntax tree | |
| func visitNodes(n *tree_sitter.Node, f func(node *tree_sitter.Node)) { | |
| f(n) | |
| for i := 0; i < int(n.ChildCount()); i++ { | |
| child := n.Child(i) | |
| visitNodes(child, f) | |
| } | |
| } | |