Static safety/lint runner for the BlockNinja codebase. ~25 invariant
checks across Go and frontend sources. Was at git.dev.alexdunmow.com:block/ninja
in backend/cmd/check-safety/ until the 2026-06-06 consolidation moved
the BlockNinja repos under a shared ~/src/blockninja/ parent.
This repo is the standalone extraction:
- Own go.mod (git.dev.alexdunmow.com/block/check-safety, go 1.26.4)
- Vendored internal/{helpers,theme} from CMS (Go's internal/ rule
blocks cross-module imports; vendoring is the workaround)
- CLI contract unchanged: `check-safety <target-dir> [--flags]`
- CMS Makefile shells into ../check-safety for safety-check /
install-safety-checker targets
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
223 lines
5.4 KiB
Go
223 lines
5.4 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestCheckPostCSSConfig_BarePlugin(t *testing.T) {
|
|
dir := t.TempDir()
|
|
configPath := filepath.Join(dir, "postcss.config.js")
|
|
if err := os.WriteFile(configPath, []byte(`export default {
|
|
plugins: {
|
|
tailwindcss: {},
|
|
autoprefixer: {},
|
|
},
|
|
}
|
|
`), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
violations := checkPostCSSConfig(dir)
|
|
if len(violations) == 0 {
|
|
t.Fatal("expected violations for bare tailwindcss plugin")
|
|
}
|
|
|
|
foundPlugin := false
|
|
for _, v := range violations {
|
|
if v.rule == "postcss-v4-plugin" {
|
|
foundPlugin = true
|
|
}
|
|
}
|
|
if !foundPlugin {
|
|
t.Fatalf("expected postcss-v4-plugin violation, got %v", violations)
|
|
}
|
|
}
|
|
|
|
func TestCheckPostCSSConfig_V4Plugin_OK(t *testing.T) {
|
|
dir := t.TempDir()
|
|
configPath := filepath.Join(dir, "postcss.config.js")
|
|
if err := os.WriteFile(configPath, []byte(`export default {
|
|
plugins: {
|
|
'@tailwindcss/postcss': {},
|
|
},
|
|
}
|
|
`), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
violations := checkPostCSSConfig(dir)
|
|
if len(violations) > 0 {
|
|
t.Fatalf("unexpected violations for v4 postcss config: %v", violations)
|
|
}
|
|
}
|
|
|
|
func TestCheckPostCSSConfig_AutoprefixerWithV4(t *testing.T) {
|
|
dir := t.TempDir()
|
|
configPath := filepath.Join(dir, "postcss.config.js")
|
|
if err := os.WriteFile(configPath, []byte(`export default {
|
|
plugins: {
|
|
'@tailwindcss/postcss': {},
|
|
autoprefixer: {},
|
|
},
|
|
}
|
|
`), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
violations := checkPostCSSConfig(dir)
|
|
foundAutoprefixer := false
|
|
for _, v := range violations {
|
|
if v.rule == "postcss-no-autoprefixer" {
|
|
foundAutoprefixer = true
|
|
}
|
|
}
|
|
if !foundAutoprefixer {
|
|
t.Fatalf("expected postcss-no-autoprefixer violation, got %v", violations)
|
|
}
|
|
}
|
|
|
|
func TestCheckCSSDirectives_V3Syntax(t *testing.T) {
|
|
dir := t.TempDir()
|
|
cssPath := filepath.Join(dir, "index.css")
|
|
if err := os.WriteFile(cssPath, []byte(`@tailwind base;
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
`), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
violations := checkCSSDirectives(dir, dir)
|
|
if len(violations) != 3 {
|
|
t.Fatalf("expected 3 violations for @tailwind directives, got %d: %v", len(violations), violations)
|
|
}
|
|
for _, v := range violations {
|
|
if v.rule != "css-v4-import" {
|
|
t.Fatalf("expected css-v4-import rule, got %s", v.rule)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCheckCSSDirectives_V4Syntax_OK(t *testing.T) {
|
|
dir := t.TempDir()
|
|
cssPath := filepath.Join(dir, "index.css")
|
|
if err := os.WriteFile(cssPath, []byte(`@import "tailwindcss";
|
|
@config "../tailwind.config.ts";
|
|
`), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
parentDir := filepath.Dir(dir)
|
|
configPath := filepath.Join(parentDir, "tailwind.config.ts")
|
|
if err := os.WriteFile(configPath, []byte(`export default {}`), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
violations := checkCSSDirectives(dir, parentDir)
|
|
if len(violations) > 0 {
|
|
t.Fatalf("unexpected violations for v4 CSS: %v", violations)
|
|
}
|
|
}
|
|
|
|
func TestCheckCSSDirectives_MissingConfig(t *testing.T) {
|
|
dir := t.TempDir()
|
|
srcDir := filepath.Join(dir, "src")
|
|
if err := os.MkdirAll(srcDir, 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cssPath := filepath.Join(srcDir, "index.css")
|
|
if err := os.WriteFile(cssPath, []byte(`@import "tailwindcss";
|
|
`), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
configPath := filepath.Join(dir, "tailwind.config.ts")
|
|
if err := os.WriteFile(configPath, []byte(`export default {}`), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
violations := checkCSSDirectives(srcDir, dir)
|
|
foundMissing := false
|
|
for _, v := range violations {
|
|
if v.rule == "css-missing-config" {
|
|
foundMissing = true
|
|
}
|
|
}
|
|
if !foundMissing {
|
|
t.Fatalf("expected css-missing-config violation, got %v", violations)
|
|
}
|
|
}
|
|
|
|
func TestCheckPostCSSConfig_PostCSSRC_JSON(t *testing.T) {
|
|
dir := t.TempDir()
|
|
configPath := filepath.Join(dir, ".postcssrc.json")
|
|
if err := os.WriteFile(configPath, []byte(`{
|
|
"plugins": {
|
|
"tailwindcss": {},
|
|
"autoprefixer": {}
|
|
}
|
|
}
|
|
`), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
violations := checkPostCSSConfig(dir)
|
|
foundPlugin := false
|
|
for _, v := range violations {
|
|
if v.rule == "postcss-v4-plugin" {
|
|
foundPlugin = true
|
|
}
|
|
}
|
|
if !foundPlugin {
|
|
t.Fatalf("expected postcss-v4-plugin violation for .postcssrc.json, got %v", violations)
|
|
}
|
|
}
|
|
|
|
func TestCheckCSSDirectives_PlainCSS_NoViolation(t *testing.T) {
|
|
dir := t.TempDir()
|
|
srcDir := filepath.Join(dir, "src")
|
|
if err := os.MkdirAll(srcDir, 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cssPath := filepath.Join(srcDir, "components.css")
|
|
if err := os.WriteFile(cssPath, []byte(`.btn { padding: 8px 16px; }
|
|
.card { border: 1px solid #ccc; }
|
|
`), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
configPath := filepath.Join(dir, "tailwind.config.ts")
|
|
if err := os.WriteFile(configPath, []byte(`export default {}`), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
violations := checkCSSDirectives(srcDir, dir)
|
|
if len(violations) > 0 {
|
|
t.Fatalf("plain CSS file should not trigger violations, got %v", violations)
|
|
}
|
|
}
|
|
|
|
func TestCheckCSSDirectives_NodeModulesSkipped(t *testing.T) {
|
|
dir := t.TempDir()
|
|
nmDir := filepath.Join(dir, "node_modules", "some-lib")
|
|
if err := os.MkdirAll(nmDir, 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cssPath := filepath.Join(nmDir, "style.css")
|
|
if err := os.WriteFile(cssPath, []byte(`@tailwind base;
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
`), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
violations := checkCSSDirectives(dir, dir)
|
|
if len(violations) > 0 {
|
|
t.Fatalf("node_modules CSS should be skipped, got %v", violations)
|
|
}
|
|
}
|