package main import ( "fmt" "sort" "strings" ) // hintForColor returns a likely semantic replacement for a hardcoded color snippet. // This is intentionally approximate and is used only in the consolidated fix block. func hintForColor(snippet string) string { s := strings.ToLower(snippet) for _, prefix := range []string{ "bg-", "text-", "border-", "ring-", "shadow-", "outline-", "decoration-", "divide-", "from-", "to-", "via-", "fill-", "stroke-", "accent-", "caret-", "placeholder-", } { _, after, ok := strings.Cut(s, prefix) if !ok { continue } twPrefix := prefix[:len(prefix)-1] isLowShadeBg := false if twPrefix == "bg" || twPrefix == "from" || twPrefix == "to" || twPrefix == "via" { for _, low := range []string{"-50", "-100", "-200"} { if strings.Contains(after, low) { isLowShadeBg = true break } } } switch { case strings.HasPrefix(after, "green") || strings.HasPrefix(after, "emerald") || strings.HasPrefix(after, "lime"): if isLowShadeBg { return twPrefix + "-success/10" } return twPrefix + "-success" case strings.HasPrefix(after, "red") || strings.HasPrefix(after, "rose"): if isLowShadeBg { return twPrefix + "-destructive/10" } return twPrefix + "-destructive" case strings.HasPrefix(after, "amber") || strings.HasPrefix(after, "yellow") || strings.HasPrefix(after, "orange"): if isLowShadeBg { return twPrefix + "-warning/10" } return twPrefix + "-warning" case strings.HasPrefix(after, "blue") || strings.HasPrefix(after, "sky") || strings.HasPrefix(after, "cyan"): if isLowShadeBg { return twPrefix + "-info/10" } return twPrefix + "-info" case strings.HasPrefix(after, "indigo"): return twPrefix + "-primary" case strings.HasPrefix(after, "purple") || strings.HasPrefix(after, "violet") || strings.HasPrefix(after, "fuchsia") || strings.HasPrefix(after, "pink") || strings.HasPrefix(after, "teal"): return twPrefix + "-accent" case strings.HasPrefix(after, "gray") || strings.HasPrefix(after, "slate") || strings.HasPrefix(after, "zinc") || strings.HasPrefix(after, "neutral") || strings.HasPrefix(after, "stone"): if twPrefix == "text" { return "text-muted-foreground" } return twPrefix + "-muted" } } if strings.Contains(s, "#") || strings.Contains(s, "rgb") || strings.Contains(s, "hsl") { return "hsl(var(--token))" } return "" } func formatColorViolations(scope string, violations []colorViolation) string { if len(violations) == 0 { return "" } sorted := append([]colorViolation(nil), violations...) sort.Slice(sorted, func(i, j int) bool { if sorted[i].file != sorted[j].file { return sorted[i].file < sorted[j].file } if sorted[i].line != sorted[j].line { return sorted[i].line < sorted[j].line } if sorted[i].rule != sorted[j].rule { return sorted[i].rule < sorted[j].rule } return sorted[i].snippet < sorted[j].snippet }) var out strings.Builder fmt.Fprintf(&out, " FAIL: %d hardcoded color(s) in %s:\n", len(sorted), scope) currentFile := "" for _, v := range sorted { if v.file != currentFile { fmt.Fprintf(&out, " %s\n", v.file) currentFile = v.file } if v.line > 0 { fmt.Fprintf(&out, " %d [%s] %s\n", v.line, v.rule, v.snippet) continue } fmt.Fprintf(&out, " [%s] %s\n", v.rule, v.snippet) } return strings.TrimRight(out.String(), "\n") } func collectColorHints(violationGroups ...[]colorViolation) []string { seen := make(map[string]bool) var hints []string for _, violations := range violationGroups { for _, v := range violations { hint := hintForColor(v.snippet) if hint == "" || seen[hint] { continue } seen[hint] = true hints = append(hints, hint) } } sort.Strings(hints) if len(hints) > 5 { hints = append(hints[:5], "...") } return hints } func printColorFixInstructions(violationGroups ...[]colorViolation) { fmt.Println(" Fix:") if hints := collectColorHints(violationGroups...); len(hints) > 0 { fmt.Printf(" 1. Likely replacements for this batch: %s\n", strings.Join(hints, ", ")) fmt.Println(" 2. Replace literals with semantic tokens or CSS vars where needed.") } else { fmt.Println(" 1. Replace literals with semantic tokens or CSS vars.") } fmt.Println(" 3. Tailwind token surface: tools/tailwind-service/input.base.css") fmt.Println(" 4. Runtime theme variable generation: internal/theme/css.go") fmt.Println(" 5. Plugin CSS examples: internal/plugins/defaultplugin/assets/style.css") }