package architecture import ( "go/ast" "go/parser" "go/token" "io/fs" "path/filepath" "strconv" "testing" ) // Keep the backend error-model guideline executable, including aliased imports. func TestErrorConstructorsUseProjectModel(t *testing.T) { root := filepath.Join("..", "..") err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { if err != nil { return err } if d.IsDir() || filepath.Ext(path) != ".go" { return nil } fset := token.NewFileSet() file, err := parser.ParseFile(fset, path, nil, 0) if err != nil { return err } imports := map[string]string{} for _, imp := range file.Imports { pkg, err := strconv.Unquote(imp.Path.Value) if err != nil { return err } if pkg != "errors" && pkg != "fmt" { continue } alias := pkg if imp.Name != nil { alias = imp.Name.Name } if alias == "." { t.Errorf("%s: dot import obscures error constructors", path) } imports[alias] = pkg } ast.Inspect(file, func(n ast.Node) bool { call, ok := n.(*ast.CallExpr) if !ok { return true } sel, ok := call.Fun.(*ast.SelectorExpr) if !ok { return true } ident, ok := sel.X.(*ast.Ident) if !ok { return true } if (imports[ident.Name] == "errors" && sel.Sel.Name == "New") || (imports[ident.Name] == "fmt" && sel.Sel.Name == "Errorf") { t.Errorf("%s: use backend/shared/errors", fset.Position(call.Pos())) } return true }) return nil }) if err != nil { t.Fatal(err) } }