package main import ( "bufio" "os" "path/filepath" "regexp" "sort" "git.dev.alexdunmow.com/block/check-safety/internal/helpers" "strings" ) type todoViolation struct { file string line int snippet string } type todoScanRoot struct { root string displayPrefix string } var todoFileExts = map[string]bool{ ".css": true, ".go": true, ".html": true, ".js": true, ".jsx": true, ".scss": true, ".sql": true, ".templ": true, ".ts": true, ".tsx": true, } var todoDirSkips = map[string]bool{ ".git": true, "dist": true, "node_modules": true, "vendor": true, } var reTODO = regexp.MustCompile(`(?i)\bTODO(?:\([^)]*\))?\b`) func checkTODOs(roots []todoScanRoot) []todoViolation { var violations []todoViolation for _, scanRoot := range roots { if scanRoot.root == "" { continue } if err := filepath.Walk(scanRoot.root, func(path string, info os.FileInfo, err error) error { if err != nil { return nil } if info.IsDir() { if todoDirSkips[info.Name()] { return filepath.SkipDir } return nil } slashPath := filepath.ToSlash(path) ext := filepath.Ext(path) if !todoFileExts[ext] { return nil } if strings.Contains(slashPath, "cmd/check-safety/") { return nil } if strings.HasSuffix(path, "_test.go") || strings.HasSuffix(path, ".test.ts") || strings.HasSuffix(path, ".test.tsx") || strings.HasSuffix(path, ".spec.ts") || strings.HasSuffix(path, ".spec.tsx") { return nil } f, openErr := os.Open(path) if openErr != nil { return nil } defer helpers.LogDeferredError(nil, "close TODO scan file", f.Close, "path", path) relPath, _ := filepath.Rel(scanRoot.root, path) if relPath == "" { relPath = path } relPath = filepath.ToSlash(relPath) if scanRoot.displayPrefix != "" { relPath = scanRoot.displayPrefix + "/" + relPath } scanner := bufio.NewScanner(f) lineNum := 0 for scanner.Scan() { lineNum++ line := scanner.Text() if !reTODO.MatchString(line) { continue } violations = append(violations, todoViolation{ file: relPath, line: lineNum, snippet: strings.TrimSpace(line), }) } return nil }); err != nil { _ = err // Walk errors are non-fatal for TODO checks } } sort.Slice(violations, func(i, j int) bool { if violations[i].file == violations[j].file { return violations[i].line < violations[j].line } return violations[i].file < violations[j].file }) return violations }