buildozer: only remove visibility matching explicit default_visibility Only remove visibility attributes if default_visibility is explicitly defined in a package() declaration. Support unordered string list comparisons and identifier matching.
diff --git a/edit/fix.go b/edit/fix.go index 0643caa..e2fc221 100644 --- a/edit/fix.go +++ b/edit/fix.go
@@ -20,6 +20,7 @@ import ( "regexp" + "slices" "sort" "strings" @@ -100,60 +101,38 @@ return fixed } -// removeVisibility removes useless visibility attributes. +// removeVisibility removes useless visibility attributes (repeating default_visibility). func removeVisibility(f *build.File, r *build.Rule, pkg string) bool { - // Do not remove visibility from macros or loaded rules, as they may have custom - // default visibility logic (e.g. falling back to a non-private default if visibility - // is omitted). - if isMacroOrLoadedRule(f, r) { + pkgDecl := ExistingPackageDeclaration(f) + if pkgDecl == nil || pkgDecl.Attr("default_visibility") == nil { + // If default visibility is not explicit we do not replace other attrs. return false } - // If no default_visibility is given, it is implicitly private. - defaultVisibility := []string{"//visibility:private"} - if pkgDecl := ExistingPackageDeclaration(f); pkgDecl != nil { - if pkgDecl.Attr("default_visibility") != nil { - defaultVisibility = pkgDecl.AttrStrings("default_visibility") + defaultVis := pkgDecl.Attr("default_visibility") + vis := r.Attr("visibility") + + // Check equality if both are string lists. + if dvs := build.Strings(defaultVis); dvs != nil { + if vs := build.Strings(vis); vs != nil { + if slices.Equal(slices.Sorted(slices.Values(dvs)), slices.Sorted(slices.Values(vs))) { + r.DelAttr("visibility") + return true + } } } - - visibility := r.AttrStrings("visibility") - if len(visibility) == 0 || len(visibility) != len(defaultVisibility) { - return false - } - sort.Strings(defaultVisibility) - sort.Strings(visibility) - for i, vis := range visibility { - if vis != defaultVisibility[i] { - return false - } - } - r.DelAttr("visibility") - return true -} - -// isMacroOrLoadedRule reports whether the rule is a macro or was loaded from a .bzl file. -func isMacroOrLoadedRule(f *build.File, r *build.Rule) bool { - if f == nil || r == nil { - return false - } - kind := r.Kind() - if strings.Contains(kind, ".") { - return true - } - for _, stmt := range f.Stmt { - if load, ok := stmt.(*build.LoadStmt); ok { - for _, to := range load.To { - if to.Name == kind { - return true - } + // Check Name equality if both are idents. + if ai, aok := defaultVis.(*build.Ident); aok { + if bi, bok := vis.(*build.Ident); bok { + if ai.Name == bi.Name { + r.DelAttr("visibility") + return true } } } return false } - // removeTestOnly removes the useless testonly attributes. func removeTestOnly(f *build.File, r *build.Rule, pkg string) bool { pkgDecl := ExistingPackageDeclaration(f)
diff --git a/edit/fix_test.go b/edit/fix_test.go index ccce057..0cebe72 100644 --- a/edit/fix_test.go +++ b/edit/fix_test.go
@@ -112,74 +112,79 @@ `, }, { - name: "remove redundant visibility for native rule", + name: "preserve visibility when default_visibility is not set", input: `cc_library( name = "native_lib", visibility = ["//visibility:private"], ) `, - want: `cc_library(name = "native_lib") -`, - }, - { - name: "preserve private visibility for dotted macro", - input: `load("//foo:bar.bzl", "rules") - -rules.my_library( - name = "macro_lib", - visibility = ["//visibility:private"], -) -`, - want: `load("//foo:bar.bzl", "rules") - -rules.my_library( - name = "macro_lib", + want: `cc_library( + name = "native_lib", visibility = ["//visibility:private"], ) `, }, { - name: "preserve private visibility for loaded macro", - input: `load("//foo:bar.bzl", "my_macro") - -my_macro( - name = "macro_lib", - visibility = ["//visibility:private"], -) -`, - want: `load("//foo:bar.bzl", "my_macro") - -my_macro( - name = "macro_lib", - visibility = ["//visibility:private"], -) -`, - }, - { - name: "package default visibility removes for native rule but preserves for loaded macro", - input: `load("//foo:bar.bzl", "my_macro") - -package(default_visibility = ["//visibility:public"]) + name: "remove redundant visibility with different slice order", + input: `package(default_visibility = [ + "//foo", + "//bar", +]) cc_library( name = "native_lib", - visibility = ["//visibility:public"], -) - -my_macro( - name = "macro_lib", - visibility = ["//visibility:public"], + visibility = [ + "//bar", + "//foo", + ], ) `, - want: `load("//foo:bar.bzl", "my_macro") + want: `package(default_visibility = [ + "//bar", + "//foo", +]) -package(default_visibility = ["//visibility:public"]) +cc_library(name = "native_lib") +`, + }, + { + name: "remove redundant visibility matching ident", + input: `package(default_visibility = PUBLIC) + +cc_library( + name = "native_lib", + visibility = PUBLIC, +) + +cc_library( + name = "other_lib", + visibility = PRIVATE, +) +`, + want: `package(default_visibility = PUBLIC) cc_library(name = "native_lib") -my_macro( - name = "macro_lib", - visibility = ["//visibility:public"], +cc_library( + name = "other_lib", + visibility = PRIVATE, +) +`, + }, + { + name: "preserve non-matching visibility", + input: `package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "native_lib", + visibility = ["//visibility:private"], +) +`, + want: `package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "native_lib", + visibility = ["//visibility:private"], ) `, },