Add archtest: tiers, file length, comments, component signatures
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
a9096b8aa2
commit
727de5f110
3 files changed
+316
-1
No files matched your search
@@ -0,0 +1,156 @@
|
||||
package archtest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const root = "../.."
|
||||
|
||||
var skipDirs = map[string]bool{".git": true, "node_modules": true, "dist": true, "icon/svg": true}
|
||||
|
||||
// Generated lock files are exempt.
|
||||
var exempt = map[string]bool{"go.sum": true, "package-lock.json": true}
|
||||
|
||||
func walk(t *testing.T, visit func(rel string, src []byte)) {
|
||||
t.Helper()
|
||||
err := filepath.WalkDir(root, func(p string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rel, _ := filepath.Rel(root, p)
|
||||
rel = filepath.ToSlash(rel)
|
||||
if d.IsDir() {
|
||||
if skipDirs[rel] || skipDirs[d.Name()] {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
src, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
visit(rel, src)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileLength(t *testing.T) {
|
||||
walk(t, func(rel string, src []byte) {
|
||||
if exempt[filepath.Base(rel)] || !utf8.Valid(src) || bytes.IndexByte(src, 0) >= 0 {
|
||||
return
|
||||
}
|
||||
n := bytes.Count(src, []byte("\n"))
|
||||
if len(src) > 0 && src[len(src)-1] != '\n' {
|
||||
n++
|
||||
}
|
||||
if n >= 500 {
|
||||
t.Errorf("%s: %d lines, want under 500", rel, n)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGoRules(t *testing.T) {
|
||||
fset, files := token.NewFileSet(), 0
|
||||
walk(t, func(rel string, src []byte) {
|
||||
if filepath.Ext(rel) != ".go" {
|
||||
return
|
||||
}
|
||||
files++
|
||||
f, err := parser.ParseFile(fset, rel, src, parser.ParseComments)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
bad := checkComments(fset, f)
|
||||
if dir := filepath.ToSlash(filepath.Dir(rel)); dir != "internal/archtest" {
|
||||
bad = append(bad, checkImports(dir, f)...)
|
||||
if isCategory(dir) && !strings.HasSuffix(rel, "_test.go") {
|
||||
bad = append(bad, checkComponents(rel, f)...)
|
||||
}
|
||||
}
|
||||
for _, b := range bad {
|
||||
t.Error(b)
|
||||
}
|
||||
})
|
||||
if files == 0 {
|
||||
t.Fatal("no Go files found")
|
||||
}
|
||||
}
|
||||
|
||||
func parse(t *testing.T, src string) (*token.FileSet, *ast.File) {
|
||||
t.Helper()
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, "x.go", src, parser.ParseComments)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return fset, f
|
||||
}
|
||||
|
||||
func TestRulesCatch(t *testing.T) {
|
||||
const head = "package button\nimport \"github.com/egoist/mygo/ui\"\n"
|
||||
cases := []struct {
|
||||
name, file, src string
|
||||
rule func(*token.FileSet, *ast.File, string) []string
|
||||
want int
|
||||
}{
|
||||
{"same tier", "", "package button\nimport _ \"" + module + "/display\"\n", imports("button"), 1},
|
||||
{"upward", "", "package button\nimport _ \"" + module + "/form\"\n", imports("button"), 1},
|
||||
{"unknown pkg", "", "package x\n", imports("x"), 1},
|
||||
{"downward", "", "package button\nimport _ \"" + module + "/kit\"\n", imports("button"), 0},
|
||||
{"long comment", "", "package x\n// one two three four five six seven eight nine ten eleven\n", comments, 1},
|
||||
{"two lines", "", "package x\n// one\n// two\n", comments, 1},
|
||||
{"block", "", "package x\n/* one\ntwo */\n", comments, 1},
|
||||
{"directive", "", "package x\n// Files.\n//go:generate echo\n", comments, 0},
|
||||
{"good", "button/icon_button.go", head + "func IconButton(c *ui.Context, o IconButtonOptions) ui.Element\n" +
|
||||
"type IconButtonOptions struct{}\n", components, 0},
|
||||
{"result", "button/split.go", head + "func Split(c *ui.Context) SplitResult\ntype SplitResult struct{}\n",
|
||||
components, 0},
|
||||
{"two", "button/a.go", head + "func A(c *ui.Context) ui.Element\nfunc B(c *ui.Context) ui.Element\n",
|
||||
components, 1},
|
||||
{"file name", "button/b.go", head + "func KPIGrid(c *ui.Context) ui.Element\n", components, 1},
|
||||
{"no context", "button/a.go", head + "func A() ui.Element\n", components, 1},
|
||||
{"variadic", "button/a.go", head + "func A(c *ui.Context, o ...AOptions) ui.Element\n", components, 1},
|
||||
{"options not last", "button/a.go", head + "func A(c *ui.Context, o AOptions, n int) ui.Element\n",
|
||||
components, 1},
|
||||
{"returns", "button/a.go", head + "func A(c *ui.Context) int\n", components, 1},
|
||||
{"ctx name", "button/a.go", head + "func A(ctx *ui.Context) ui.Element\n", components, 1},
|
||||
{"grouped options", "button/a.go", head + "func A(c *ui.Context, x, y AOptions) ui.Element\n",
|
||||
components, 1},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
fset, f := parse(t, tc.src)
|
||||
if got := tc.rule(fset, f, tc.file); len(got) != tc.want {
|
||||
t.Errorf("%s: %d findings %q, want %d", tc.name, len(got), got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func imports(dir string) func(*token.FileSet, *ast.File, string) []string {
|
||||
return func(_ *token.FileSet, f *ast.File, _ string) []string { return checkImports(dir, f) }
|
||||
}
|
||||
|
||||
func comments(fset *token.FileSet, f *ast.File, _ string) []string { return checkComments(fset, f) }
|
||||
|
||||
func components(_ *token.FileSet, f *ast.File, file string) []string { return checkComponents(file, f) }
|
||||
|
||||
func TestSnake(t *testing.T) {
|
||||
for in, want := range map[string]string{"Stack": "stack", "KPIGrid": "kpi_grid", "Base64Tool": "base64_tool",
|
||||
"VDivider": "v_divider", "IDCard": "id_card", "PiPBox": "pi_p_box", "KPI": "kpi"} {
|
||||
if got := snake(in); got != want {
|
||||
t.Errorf("snake(%s) = %s, want %s", in, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user