Repository: bazel-contrib/bazel-gazelle (Go)

Issue:
bazel visibility extension should allow override default_visibility
Let's say I have a setup like this
```
foo/BUILD
foo/bar/BUILD
```

with these file contents
```
cat foo/BUILD
# gazelle:default_visibility //foo:__subpackages
```

```
cat foo/bar/BUILD
# gazelle:default_visibility //foo/bar:__subpackages
```

Running gazelle `bazel/visibility` extension would generate something like this
```
cat foo/bar/BUILD
# gazelle:default_visibility //foo/bar:__subpackages
package(default_visibility = [
    "//foo:__subpackages__",
    "//foo/bar:__subpackages__",
])
```

while the sensible expected result should be
```
cat foo/bar/BUILD
# gazelle:default_visibility //foo/bar:__subpackages
package(default_visibility = ["//foo/bar:__subpackages__"])
```




Discussion:
cc: @linzhp @moisesvega @dnathe4th 
Yup you're right. I accumulated visibilities [here](https://github.com/bazelbuild/bazel-gazelle/blob/master/language/bazel/visibility/config.go#L57) with the idea that it would allow someone to define multiple back to back directives, but I did not consider the case that it is even appending directive values it finds in other files.

Maybe it is better to only accumulate within the same file (i.e. same call to `Configure`) and overwrite the config's list of targets at the end of that method?
Yeah, I think it's better to treat a subpackage's `default_visibility` directive as an override of the parent package's `default_visibility` directive. In my mind, I cannot think of a use case where I would want to inherit the parent's visibility config. Usually, it's always going to be some complete override.

If a change were made to parent scope visibility, the change's author should be responsible to replicate such changes to children's packages that have overridden parents' visibility. That way, such change would impact code ownership and trigger various code review systems.

So it's gonna be a +1 for me to get rid of the accumulation.

Side note: Imo this behavior is relatively safe to change. Although the language plugin was part of the last release and we recently added documentation for the new directive, the language is not yet included as part of `DEFAULT_LANGUAGE` exported in `def.bzl` so folks are not enabling this by default. Given that (a) this is a relatively new feature and (b) it's an opt-in feature without clear documentation, I think an update to this behavior should be ok.



Patch:
diff --git a/language/bazel/visibility/config.go b/language/bazel/visibility/config.go
index 64385b3c3..81781fdcf 100644
--- a/language/bazel/visibility/config.go
+++ b/language/bazel/visibility/config.go
@@ -50,14 +50,21 @@ func (*visibilityExtension) Configure(c *config.Config, _ string, f *rule.File)
 		return
 	}
 
+	var newVisTargets []string
 	for _, d := range f.Directives {
 		switch d.Key {
 		case _directiveName:
 			for _, target := range strings.Split(d.Value, ",") {
-				cfg.visibilityTargets = append(cfg.visibilityTargets, target)
+				newVisTargets = append(newVisTargets, target)
 			}
 		}
 	}
+
+	// if visibility targets were specified, overwrite the config
+	if len(newVisTargets) != 0 {
+		cfg.visibilityTargets = newVisTargets
+	}
+
 	c.Exts[_extName] = cfg
 }
 


Test patch:
diff --git a/language/bazel/visibility/lang_test.go b/language/bazel/visibility/lang_test.go
index f90bc67ac..476ca5239 100644
--- a/language/bazel/visibility/lang_test.go
+++ b/language/bazel/visibility/lang_test.go
@@ -213,3 +213,64 @@ func Test_NoRuleIfNoBuildFile(t *testing.T) {
 		t.Fatal("expected array of length 0")
 	}
 }
+
+func Test_MultipleDirectivesAcrossFilesSupercede(t *testing.T) {
+	testVis1 := "//src:__subpackages__"
+	testVis2 := "//src2:__subpackages__"
+	file1, err := rule.LoadData("path", "pkg", []byte(fmt.Sprintf(`
+# gazelle:default_visibility %s
+`, testVis1)))
+	if err != nil {
+		t.Fatalf("expected not nil - %+v", err)
+	}
+	file2, err := rule.LoadData("path/path", "pkg", []byte(fmt.Sprintf(`
+# gazelle:default_visibility %s
+`, testVis2)))
+	if err != nil {
+		t.Fatalf("expected not nil - %+v", err)
+	}
+
+	cfg := config.New()
+	ext := visibility.NewLanguage()
+	ext.Configure(cfg, "path", file1)
+
+	// clone the config as if we were decending through Walk
+	cfg2 := cfg.Clone()
+	ext.Configure(cfg2, "path/path", file2)
+
+	res2 := ext.GenerateRules(language.GenerateArgs{
+		Config: cfg2,
+		File:   rule.EmptyFile("path/path/file", "pkg"),
+	})
+
+	if len(res2.Gen) != 1 {
+		t.Fatal("expected array of length 1")
+	}
+	if len(res2.Imports) != 1 {
+		t.Fatal("expected array of length 1")
+	}
+	if len(res2.Gen[0].AttrStrings("default_visibility")) != 1 {
+		t.Fatal("expected array of length 1")
+	}
+	if testVis2 != res2.Gen[0].AttrStrings("default_visibility")[0] {
+		t.Fatal("expected returned visibility to match '//src2:__subpackages__'")
+	}
+
+	res1 := ext.GenerateRules(language.GenerateArgs{
+		Config: cfg,
+		File:   rule.EmptyFile("path/file", "pkg"),
+	})
+
+	if len(res1.Gen) != 1 {
+		t.Fatal("expected array of length 1")
+	}
+	if len(res1.Imports) != 1 {
+		t.Fatal("expected array of length 1")
+	}
+	if len(res1.Gen[0].AttrStrings("default_visibility")) != 1 {
+		t.Fatal("expected array of length 1")
+	}
+	if testVis1 != res1.Gen[0].AttrStrings("default_visibility")[0] {
+		t.Fatal("expected returned visibility to match '//src:__subpackages__'")
+	}
+}