Directory walkers pruned .git/vendor/node_modules but not .worktrees/, so a repo scan descended into nested git worktrees (e.g. an orchestrator worktree under cms/.worktrees/). Their Go/TS files surfaced as false positives in the standalone-plugin import check and noise in the any-usage warnings. Add ".worktrees" to the skip set across the implicated and common walkers: proto RBAC proto-scan, standalone-plugin imports, frontend extras, go-lint, sqlc-uuid, presets.json, and plugin segmentation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
239 lines
5.7 KiB
Go
239 lines
5.7 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"git.dev.alexdunmow.com/block/check-safety/internal/helpers"
|
|
)
|
|
|
|
// --- Check: No useState for tab state in route files ---
|
|
|
|
type tabStateViolation struct {
|
|
file string
|
|
line int
|
|
snippet string
|
|
}
|
|
|
|
type eslintDirectiveViolation struct {
|
|
file string
|
|
line int
|
|
snippet string
|
|
}
|
|
|
|
var reUseStateTab = regexp.MustCompile(`useState\s*[<(].*(?i:tab|activeTab|selectedTab|currentTab)`)
|
|
var reUseStateTabAlt = regexp.MustCompile(`(?:activeTab|selectedTab|currentTab|tabValue)\s*,\s*set\w+\]\s*=\s*useState`)
|
|
|
|
// False positives: layout preferences that are not content tabs
|
|
var tabStateIgnorePatterns = []string{"preferTabs"}
|
|
|
|
var reESLintDisableNextLine = regexp.MustCompile(`eslint-disable-next-line`)
|
|
|
|
func checkTabState(webSrcDir string) []tabStateViolation {
|
|
var violations []tabStateViolation
|
|
|
|
if err := filepath.Walk(webSrcDir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil || info.IsDir() {
|
|
return nil
|
|
}
|
|
if !strings.HasSuffix(path, ".tsx") {
|
|
return nil
|
|
}
|
|
if strings.Contains(path, "node_modules") || strings.Contains(path, "/dist/") || strings.Contains(path, "/.worktrees/") {
|
|
return nil
|
|
}
|
|
|
|
relPath, _ := filepath.Rel(webSrcDir, path)
|
|
relPath = filepath.ToSlash(relPath)
|
|
|
|
if !strings.HasPrefix(relPath, "routes/") {
|
|
return nil
|
|
}
|
|
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
defer helpers.LogDeferredError(nil, "close routable tab scan file", f.Close, "path", path)
|
|
|
|
scanner := bufio.NewScanner(f)
|
|
lineNum := 0
|
|
for scanner.Scan() {
|
|
lineNum++
|
|
line := scanner.Text()
|
|
trimmed := strings.TrimSpace(line)
|
|
if strings.HasPrefix(trimmed, "//") {
|
|
continue
|
|
}
|
|
|
|
isIgnored := false
|
|
for _, pat := range tabStateIgnorePatterns {
|
|
if strings.Contains(line, pat) {
|
|
isIgnored = true
|
|
break
|
|
}
|
|
}
|
|
if !isIgnored && (reUseStateTab.MatchString(line) || reUseStateTabAlt.MatchString(line)) {
|
|
violations = append(violations, tabStateViolation{
|
|
file: relPath,
|
|
line: lineNum,
|
|
snippet: strings.TrimSpace(line),
|
|
})
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}); err != nil {
|
|
violations = append(violations, tabStateViolation{
|
|
file: filepath.ToSlash(webSrcDir),
|
|
line: 0,
|
|
snippet: err.Error(),
|
|
})
|
|
}
|
|
|
|
return violations
|
|
}
|
|
|
|
func checkESLintDisableNextLine(root string) []eslintDirectiveViolation {
|
|
var violations []eslintDirectiveViolation
|
|
|
|
if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil || info.IsDir() {
|
|
return nil
|
|
}
|
|
|
|
ext := filepath.Ext(path)
|
|
if ext != ".ts" && ext != ".tsx" && ext != ".js" && ext != ".jsx" {
|
|
return nil
|
|
}
|
|
if strings.Contains(path, "node_modules") || strings.Contains(path, "/dist/") || strings.Contains(path, "/.worktrees/") {
|
|
return nil
|
|
}
|
|
|
|
lines, readErr := readSourceLines(path)
|
|
if readErr != nil {
|
|
return nil
|
|
}
|
|
|
|
relPath, _ := filepath.Rel(root, path)
|
|
relPath = filepath.ToSlash(relPath)
|
|
if relPath == "" {
|
|
relPath = filepath.ToSlash(path)
|
|
}
|
|
|
|
for idx, line := range lines {
|
|
if !reESLintDisableNextLine.MatchString(line) {
|
|
continue
|
|
}
|
|
violations = append(violations, eslintDirectiveViolation{
|
|
file: relPath,
|
|
line: idx + 1,
|
|
snippet: strings.TrimSpace(line),
|
|
})
|
|
}
|
|
|
|
return nil
|
|
}); err != nil {
|
|
violations = append(violations, eslintDirectiveViolation{
|
|
file: filepath.ToSlash(root),
|
|
line: 0,
|
|
snippet: err.Error(),
|
|
})
|
|
}
|
|
|
|
return violations
|
|
}
|
|
|
|
// --- Check: Import from @block-ninja/api subpaths only ---
|
|
|
|
type bareImportViolation struct {
|
|
file string
|
|
line int
|
|
snippet string
|
|
}
|
|
|
|
// Matches: from '@block-ninja/api' (bare, without /types, /queries, /services suffix)
|
|
var reBareAPIImport = regexp.MustCompile(`from\s+['"]@block-ninja/api['"]`)
|
|
|
|
func checkBareImports(webSrcDir string) []bareImportViolation {
|
|
var violations []bareImportViolation
|
|
|
|
if err := filepath.Walk(webSrcDir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil || info.IsDir() {
|
|
return nil
|
|
}
|
|
ext := filepath.Ext(path)
|
|
if ext != ".ts" && ext != ".tsx" {
|
|
return nil
|
|
}
|
|
if strings.Contains(path, "node_modules") || strings.Contains(path, "/dist/") || strings.Contains(path, "/.worktrees/") {
|
|
return nil
|
|
}
|
|
|
|
relPath, _ := filepath.Rel(webSrcDir, path)
|
|
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
defer helpers.LogDeferredError(nil, "close bare API import scan file", f.Close, "path", path)
|
|
|
|
scanner := bufio.NewScanner(f)
|
|
lineNum := 0
|
|
for scanner.Scan() {
|
|
lineNum++
|
|
line := scanner.Text()
|
|
if reBareAPIImport.MatchString(line) {
|
|
violations = append(violations, bareImportViolation{
|
|
file: relPath,
|
|
line: lineNum,
|
|
snippet: strings.TrimSpace(line),
|
|
})
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}); err != nil {
|
|
violations = append(violations, bareImportViolation{
|
|
file: filepath.ToSlash(webSrcDir),
|
|
line: 0,
|
|
snippet: err.Error(),
|
|
})
|
|
}
|
|
|
|
return violations
|
|
}
|
|
|
|
// --- Check: No npm/yarn lockfiles (pnpm only) ---
|
|
|
|
type lockfileViolation struct {
|
|
file string
|
|
}
|
|
|
|
func checkLockfiles(repoRoot string) []lockfileViolation {
|
|
var violations []lockfileViolation
|
|
|
|
if err := filepath.Walk(repoRoot, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil || info.IsDir() {
|
|
return nil
|
|
}
|
|
if strings.Contains(path, "node_modules") || strings.Contains(path, "/.worktrees/") {
|
|
return filepath.SkipDir
|
|
}
|
|
base := filepath.Base(path)
|
|
if base == "package-lock.json" || base == "yarn.lock" {
|
|
relPath, _ := filepath.Rel(repoRoot, path)
|
|
violations = append(violations, lockfileViolation{file: relPath})
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
violations = append(violations, lockfileViolation{file: filepath.ToSlash(repoRoot)})
|
|
}
|
|
|
|
return violations
|
|
}
|
|
|