package configkit import ( "go/ast" "go/parser" "go/token" "io/fs" "path/filepath" "strconv" "strings" "testing" "unicode" ) func TestDependencyImportBoundary(t *testing.T) { t.Parallel() err := filepath.WalkDir(".", func(path string, entry fs.DirEntry, walkErr error) error { if walkErr != nil { return walkErr } if entry.IsDir() { if path == ".git" { return filepath.SkipDir } return nil } if filepath.Ext(path) != ".go" { return nil } file, err := parser.ParseFile(token.NewFileSet(), path, nil, parser.ImportsOnly) if err != nil { return err } for _, imported := range file.Imports { importPath, err := strconv.Unquote(imported.Path.Value) if err != nil { return err } if strings.HasPrefix(importPath, "go.yaml.in/yaml/") && !strings.HasPrefix(filepath.ToSlash(path), "internal/yaml/") { t.Errorf("%s imports private YAML dependency %s", path, importPath) } if strings.HasPrefix(importPath, "github.com/google/go-cmp/") && !strings.HasSuffix(path, "_test.go") { t.Errorf("%s imports test-only dependency %s", path, importPath) } production := !strings.HasSuffix(path, "_test.go") rootProduction := production && filepath.Dir(path) == "." internalProduction := production && strings.HasPrefix(filepath.ToSlash(path), "internal/") if rootProduction && strings.HasPrefix(importPath, "gitea.maximumdirect.net/eric/ConfigKit/internal/") && importPath != "gitea.maximumdirect.net/eric/ConfigKit/internal/engine" { t.Errorf("%s bypasses the engine boundary through %s", path, importPath) } if internalProduction && importPath == "gitea.maximumdirect.net/eric/ConfigKit" { t.Errorf("%s imports the public root package", path) } } return nil }) if err != nil { t.Fatal(err) } } func TestPublicFacadeBoundary(t *testing.T) { t.Parallel() entries, err := filepath.Glob("*.go") if err != nil { t.Fatal(err) } for _, path := range entries { if strings.HasSuffix(path, "_test.go") { continue } file, err := parser.ParseFile(token.NewFileSet(), path, nil, 0) if err != nil { t.Fatal(err) } imports := importedPaths(file) for _, declaration := range file.Decls { switch declaration := declaration.(type) { case *ast.FuncDecl: if exportedIdentifier(declaration.Name.Name) && usesInternalType(declaration.Type, imports) { t.Errorf("%s exposes an internal type in its signature", declaration.Name) } case *ast.GenDecl: for _, specification := range declaration.Specs { typeSpec, ok := specification.(*ast.TypeSpec) if !ok || !exportedIdentifier(typeSpec.Name.Name) { continue } if typeSpec.Assign.IsValid() { t.Errorf("%s is an exported type alias", typeSpec.Name) } structure, ok := typeSpec.Type.(*ast.StructType) if !ok { continue } for _, field := range structure.Fields.List { exposed := len(field.Names) == 0 for _, name := range field.Names { exposed = exposed || exportedIdentifier(name.Name) } if exposed && usesInternalType(field.Type, imports) { t.Errorf("%s exposes an internal type through a field", typeSpec.Name) } } } } } } } func importedPaths(file *ast.File) map[string]string { paths := make(map[string]string, len(file.Imports)) for _, imported := range file.Imports { path, err := strconv.Unquote(imported.Path.Value) if err != nil { continue } name := filepath.Base(path) if imported.Name != nil { name = imported.Name.Name } paths[name] = path } return paths } func usesInternalType(expression ast.Expr, imports map[string]string) bool { internal := false ast.Inspect(expression, func(node ast.Node) bool { selector, ok := node.(*ast.SelectorExpr) if !ok { return true } packageName, ok := selector.X.(*ast.Ident) if ok && strings.Contains(imports[packageName.Name], "/internal/") { internal = true } return !internal }) return internal } func exportedIdentifier(name string) bool { for _, character := range name { return unicode.IsUpper(character) } return false }